IPv6 link-local address with ’embedded’ range on OSX
I wrote some simple code that uses ioctl SIOCGIFCONF to query all the network interfaces on the system and uses inet_ntop to return a text representation of the found address. Oddly enough, when a link-local IPv6 address is discovered, the OSX version of the code appears to embed the range into the address.
After the interface is automatically configured, this is a line (:) in /sbin/ifconfig on OSX
en1: flags=8963<UP,BROADCAST,SMART,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
ether 00:17:f2:0b:52:73
inet6 fe80::217:f2ff:fe0b:5273%en1 prefixlen 64 scopeid 0x5
and the IP address returned by ioctl SIOCGIFCONF:
IPv6 address: fe80:5::217:f2ff:fe0b:5273
It looks like the value of range (5) is inserted immediately after fe80.
The same code on Linux returns an ipv6 address without any extra data.
Two questions come to mind:
1) Is it legal to write the IPv6 address like this?
2) Is OSX behavior logged anywhere?
Please quote!
Solution
I’m not sure about your second question, but for your first question, yes, it’s common to see scopes for IPv6 addresses (e.g. link-local addresses) written like this, but definitely not cross-platform consistency. The reason is because without it, the link-local address will be ambiguous.
My answer to this other question might be helpful.
EDIT: I just realized the subtleties of this issue. The BSD IPv6 stack internally stores the interface index in the second 16-bit word of the link-local IPv6 address. This should disappear on the wire forever. This is actually a violation of the RFC, because link-local addresses are defined to have 0
bits in this area。 (by the way, that’s why they can store extra information here) I believe this is a bug and the scope should otherwise communicate with the rest of the system. So, you should probably check it and peel it off by hand.
Edit 2: I dug place in the kernel source where they set this value :
466 static int
467 in6_ifattach_linklocal(
468 struct ifnet *ifp,
469 struct ifnet *altifp, /* secondary EUI64 source */
470 struct in6_aliasreq *ifra_passed)
471 {
...
494 ifra.ifra_addr.sin6_family = AF_INET6;
495 ifra.ifra_addr.sin6_len = sizeof(struct sockaddr_in6);
496 ifra.ifra_addr.sin6_addr.s6_addr16[0] = htons(0xfe80);
497 #if SCOPEDROUTING
498 ifra.ifra_addr.sin6_addr.s6_addr16[1] = 0
499 #else
500 ifra.ifra_addr.sin6_addr.s6_addr16[1] = htons(ifp->if_index); /* XXX */
501 #endif
Note /* XXX */
on line 500. 😉 My guess is that this is some kind of temporary workaround/trick to get routing to work without rewriting part of the routing code. With link-local addresses, you need to make routing decisions based on the source and destination interfaces. By placing the if_index in that place in the
address, they might only do the longest prefix match for a 128-bit address instead of relying on some kind of metadata.