al45tair/netifaces

netmask and broadcast not returned for 169.254/16 address space on non-Windows

Opened this issue · 5 comments

The code for "UNIX" (Linux, MacOS, etc.) iterates over addrs returned by getifaddrs (&addrs). This look has special custom code for that skips over returning netmask and subnet broadcast for 169.254/16. This is the RFC 3927 link-local address space. For better or worse this address space is widely used for simple local networks with no DHCP server. Not returning the broadcast and netmask for this address space confuses people.

Reproduce problem on MacOS as follows:

  1. Set ip address, netmask, and broadcast for en1 to an address in 169.254/16 space:
sudo ifconfig en1 inet 169.254.3.2 netmask 255.255.0.0 broadcast 169.254.255.255
  1. get ifaddresses for en1:
>>> import netifaces
>>> netifaces.interfaces()
[u'lo0', u'gif0', u'stf0', u'XHC20', u'en0', u'p2p0', u'awdl0', u'en1', u'bridge0', u'utun0', u'utun1', u'utun2', u'utun3', u'EHC64', u'fw0']
>>> netifaces.ifaddresses('en1')
{18: [{'addr': u'32:00:12:ec:a0:00'}], 2: [{'addr': u'169.254.3.2'}]}

The "broadcast" and "netmask" keys are missing for 2: (AF_INET).

Correct behavior for en0 in another address space:

>>> netifaces.ifaddresses('en0')
{18: [{'addr': u'48:d7:05:e2:72:09'}], 2: [{'broadcast': u'192.168.1.255', 'netmask': u'255.255.255.0', 'addr': u'192.168.1.7'}], 30: [{'netmask': u'ffff:ffff:ffff:ffff::/64', 'flags': 1024L, 'addr': u'fe80::93:c446:cd4d:e56d%en0'}]}
>>>

The following code applies a policy to 169.254/16 with out regard to OS or correctness of subnet broadcast address. In particular when the broadcast is correctly set to 169.254.255.255, the code suppresses the broadcast address.

    /* Cygwin's implementation of getaddrinfo() is buggy and returns broadcast
       addresses for 169.254.0.0/16.  Nix them here. */
    if (addr->ifa_addr->sa_family == AF_INET) {
      struct sockaddr_in *sin = (struct sockaddr_in *)addr->ifa_addr;

      if ((ntohl(sin->sin_addr.s_addr) & 0xffff0000) == 0xa9fe0000) {
        Py_XDECREF (braddr);
        braddr = NULL;
      }
    }

The Cygwin comment is misleading. There is no specific check for Cygwin. There is no check to make sure a correct 169.254.255.255 is not suppressed. The above code should be removed.

The code with the Cygwin comment was actually added because Cygwin was unusual in that it did return a broadcast address when other systems didn't. Removing that code will not (necessarily) "fix" the problem you're claiming; I'm fairly certain that when I tested, Mac OS X didn't supply the broadcast address in this case, though I might be wrong (and it might have changed in the meantime).

It's possible that you're right and it might be more helpful for netifaces to always return 255.255.0.0 and 169.254.255.255 in this case, though I'm suspicious that link-local broadcast might not always function quite as expected… it should be fine (I think) for very simple networks with only a single network segment, but the moment you have a situation where there's more than one segment using link-local addresses, the exact behaviour of a broadcast is going to depend on which node is sending… I suspect this is the reason that various systems apparently decided not to advertise net masks and broadcast addresses for interfaces configured with link-local IP addresses.

On my Macbook, I get 169.254.255.255 for the subnet broadcast address in addr->ifa_broadaddr.
This result is expected, because that's what ifconfig says:

ifconfig en1 |grep inet
	inet 169.254.3.2 netmask 0xffff0000 broadcast 169.254.255.255

Page 15 of the RFC 3927 says, "... the address 169.254.255.255, which is the broadcast address for the Link-Local prefix."

I don't know of any widely-used operating systems that violate RFC 3927. I suspect what you saw before was an incorrectly configured interface. I think it is fine to not return a subnet broadcast when you don't get one. A sanity check that rejects subnet broadcasts that are non-RFC-compliant would be okay. For example, if you see netmask 0xffff0000 and don't see broadcast 169.254.255.255 you can skip the broadcast. Not having a sanity check would also be okay.

If you do get a subnet broadcast address and it is correct, it is bad practice to discard it. Silently violating an RFC is not good.

If you strongly disagree with RFC 3927, perhaps you could document your disagreement and suggest a work-around for people who want to use RFC 3927 networks.

I'm not advocating "silently violating an RFC", so please don't mischaracterise my position like that; nor have I said that I "disagree" (strongly or otherwise) with RFC 3927.

Perhaps, if you're trying to persuade the author of a piece of Open Source software that they maintain in their own (limited, precious) time to do something, you might consider the way you approach that a little more carefully?

Sorry. I did not mean to imply that your intent was to violate RFC 3927. No offense was intended by me. I think netifaces is a good module.

Here is a simple program I found on the Linux getifaddrs man page:

#include <arpa/inet.h>
#include <sys/socket.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
   struct ifaddrs *ifaddr, *ifa;
   int family, s;
   char buf[NI_MAXHOST];
   if (getifaddrs(&ifaddr) == -1) {
        perror("getifaddrs");
        exit(EXIT_FAILURE);
   }
   for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) {
       if (ifa->ifa_addr == NULL)
            continue;
       family = ifa->ifa_addr->sa_family;
       if (family != AF_INET)
            continue;
       s = getnameinfo(ifa->ifa_addr,
                       sizeof(struct sockaddr_in),
                       buf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
       if (s != 0) {
           printf("getnameinfo() failed: %s\n", gai_strerror(s));
           exit(EXIT_FAILURE);
       }
       printf("%s\taddress: %s", ifa->ifa_name,buf);
       s = getnameinfo(ifa->ifa_broadaddr,
                       sizeof(struct sockaddr_in),
                       buf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
       if (s != 0) {
           printf("getnameinfo() failed: %s\n", gai_strerror(s));
           exit(EXIT_FAILURE);
       }
       printf("\tbroadcast: %s\n", buf);
    }
    freeifaddrs(ifaddr);
    exit(EXIT_SUCCESS);
}

I run this on my Macbook and this is what I see:

$ ./a.out
lo0 address: 127.0.0.1 broadcast: 127.0.0.1
en0 address: 192.168.1.7 broadcast: 192.168.1.255
en1 address: 169.254.3.2 broadcast: 169.254.255.255

I can't set the IP address of eth0 on my Linux VM, but I can set the IP address of lo. This is what I see:

$ ./a.out
lo address: 169.254.3.2 broadcast: 169.254.3.2
eth0 address: 10.240.142.19 broadcast: 10.240.142.19

Thanks for your work on netifaces!

I am seeing this behaviour on a buildroot based raspberry PI image I made as well. ifconfig reports:

eth0:avahi Link encap:Ethernet HWaddr DC:A6:32:C9:0C:20
inet addr:169.254.7.223 Bcast:169.254.255.255 Mask:255.255.0.0
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1

But netifaces (0.11.0) reports:

>>> ni.ifaddresses('eth0:avahi')
{2: [{'addr': '169.254.7.223', 'netmask': '255.255.0.0'}]}