Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socket.getifaddrs returns incorrect interface list #2137

Closed
gfowley opened this Issue Nov 7, 2014 · 3 comments

Comments

Projects
None yet
3 participants
@gfowley
Copy link
Contributor

gfowley commented Nov 7, 2014

With ethernet and wifi interfaces disconnected, I expect to see 5 interfaces from Socket.getifaddrs.
Running ruby 2.2.0 head all is well:

$ ruby -v
ruby 2.2.0dev (2014-11-07 trunk 48311) [x86_64-linux]

5 interfaces...

head :042 > Socket.getifaddrs.count
 => 5

Interfaces listed: 1 each for physcial interfaces eth0, wlan0, and 3 for lo...

head :043 > Socket.getifaddrs.each { |i| puts i.inspect }
#<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000 PACKET[protocol=0 lo hatype=772 HOST hwaddr=00:00:00:00:00:00]>
#<Socket::Ifaddr eth0 UP,BROADCAST,MULTICAST PACKET[protocol=0 eth0 hatype=1 HOST hwaddr=78:24:af:18:87:93] broadcast=PACKET[protocol=0 eth0 hatype=1 HOST hwaddr=ff:ff:ff:ff:ff:ff]>
#<Socket::Ifaddr wlan0 BROADCAST,MULTICAST PACKET[protocol=0 wlan0 hatype=1 HOST hwaddr=a0:a8:cd:12:09:7b] broadcast=PACKET[protocol=0 wlan0 hatype=1 HOST hwaddr=ff:ff:ff:ff:ff:ff]>
#<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000 127.0.0.1 netmask=255.0.0.0>
#<Socket::Ifaddr lo UP,LOOPBACK,RUNNING,0x10000 ::1 netmask=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>

As expected only one (lo) has an IPv4 address...

head :044 > Socket.getifaddrs.each { |i| puts i.addr.ipv4? }                                                                                                                                  
false
false
false
true
false

Which can be retrieved...

head :045 > Socket.getifaddrs[3].addr.ip_address
 => "127.0.0.1" 

Running jruby-head, Socket.getifaddrs returns incorrect results:

$ jruby -v
jruby 9000.dev-SNAPSHOT (2.1.2p142) 2014-11-06 9af87db OpenJDK 64-Bit Server VM 24.65-b04 on 1.7.0_65-b32 +jit [linux-amd64]

Incorrect number of interfaces...

jruby-head :045 >   Socket.getifaddrs.count
 => 4 

Interfaces listed: 4 for lo, none for wlan0 and eth0...

jruby-head :046 > Socket.getifaddrs.each { |i| puts i.inspect }
#<Socket::Ifaddr: lo UP,LOOPBACK,MTU=65536 LINK[lo]>
#<Socket::Ifaddr: lo UP,LOOPBACK,MTU=65536 0:0:0:0:0:0:0:1 netmask=ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff>
#<Socket::Ifaddr: lo UP,LOOPBACK,MTU=65536 LINK[lo]>
#<Socket::Ifaddr: lo UP,LOOPBACK,MTU=65536 127.0.0.1>

It seems to think 2 interfaces have IPv4 addresses...

jruby-head :047 > Socket.getifaddrs.each { |i| puts i.addr.ipv4? }
false
false
true
true

One is 127.0.0.1 as expected...

jruby-head :049 > Socket.getifaddrs[3].addr.ip_address
 => "127.0.0.1" 

The other isn't actually...

jruby-head :050 > Socket.getifaddrs[2].addr.ip_address
SocketError: need IPv4 or IPv6 address
        from org/jruby/ext/socket/Addrinfo.java:291:in `ip_address'
        from (irb):50:in `evaluate'
        from org/jruby/RubyKernel.java:962:in `eval'
        from org/jruby/RubyKernel.java:1282:in `loop'
        from org/jruby/RubyKernel.java:1092:in `catch'
        from org/jruby/RubyKernel.java:1092:in `catch'
        from /home/gerard/.rvm/rubies/jruby-head/bin/irb:13:in `__script__'
@headius

This comment has been minimized.

Copy link
Member

headius commented Nov 7, 2014

This one is pretty straightforward and may be easy for a new dev (familiar with Java/JDK) to fix.

The logic is in RubySocket.getifaddrs, and basically just uses java.net.NetworkInterfaces. If the count is different, it may not be something we can fix, but the Addrinfos we create can probably be improved.

@gfowley

This comment has been minimized.

Copy link
Contributor Author

gfowley commented Nov 8, 2014

Ruby's Socket.getifaddrs lists addresses for all interfaces (UP or DOWN), including AF_PACKET (layer 2), AF_INET (layer 3), AF_INET6 (layer 3) addresses (and probably others if present).

java.net.NetworkInterfaces only lists layer 3 addresses and only for interfaces that are UP.

In the absence of a layer 2 interface address, JRuby's Socket.getifaddrs returns 2 Ifaddrs for each
NetworkInterface address. One is created with arg isLink == true, making it a 'fake' layer 2 Ifaddr.

That's close enough as long as all interfaces are UP and have a single IPv4 or IPv6 address. However, when interfaces are DOWN they are not listed, and interfaces with multiple layer 3 addresses (eg; IPv4 and IPv6) create multiple fake layer 2 Ifaddrs.

It seems that perfect emulation of Ruby's Socket.getifaddrs would be quite a bit of work. Even then it would be emulation of an incomplete API: the AF_PACKET Ifaddrs are not terribly useful - there doesn't seem to be a way to get at layer 2 info except by parsing the inspect. But if somebody has the time and inclination :-) .

So, in the meantime, what to do...

My use case is getting all interface IPv4 addresses on a system. My current ugly workaround is to rescue and ignore the exception that occurs when getting an IP address from a fake link layer 2 Ifaddr that still claims to be IPv4 but returns a broken Addrinfo...

@interfaces ||= Socket.getifaddrs.select do |intf|
  if intf.addr.ipv4? && ! intf.addr.ipv4_loopback?
    begin
      intf.addr.ip_address
      true
    rescue SocketError
      false
    end
  end
end

Options I see for a quick 'fix' are:

    1. Do not create fake link layer Ifaddrs, they're not useful and in the short term we can't match the Ruby Socket.getifaddrs behavior anyway.
    1. Continue to create the fake link layer Ifaddrs and:
    • a. Ensure only one fake link layer Ifaddr is created per interface.
    • b. Set afamily == AF_PACKET so #ipv4? etc.. return false (#ip_address will still cause an exception, but that's what the Ruby implementation does too).

These I can do with my rusty Java/JDK skills. Preferences ?

@gfowley

This comment has been minimized.

Copy link
Contributor Author

gfowley commented Nov 20, 2014

Discovered uses for fake link layer Ifaddr/Addrinfo - going with # 2.
Need AddressFamily.AF_PACKET (17) and ProtocolFamily.PF_PACKET (17) platform constants

I do not see them in the FFI platform stuff and my attempts to add them there for the build process have failed so far. How do I add them there ? Should I ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.