Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Identifying the preferred IPv6 source address for an adapter

If you have a IPv6 enabled host that has more than one global-scope address, how can you programmatically identify the preferred address for bind()?

Example address list:

eth0      Link encap:Ethernet  HWaddr 00:14:5e:bd:6d:da  
          inet addr:10.6.28.31  Bcast:10.6.28.255  Mask:255.255.255.0
          inet6 addr: 2002:dce8:d28e:0:214:5eff:febd:6dda/64 Scope:Global
          inet6 addr: fe80::214:5eff:febd:6dda/64 Scope:Link
          inet6 addr: 2002:dce8:d28e::31/64 Scope:Global
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1

On Solaris you can indicate a preferred address with an interface flag and it is available programmatically via SIOCGLIFCONF:

/usr/include/net/if.h:
#define   IFF_PREFERRED   0x0400000000    /* Prefer as source address */

As listed in the interface list:

eri0: flags=2104841<UP,RUNNING,MULTICAST,DHCP,ROUTER,IPv6> mtu 1500 index 2
        inet6 fe80::203:baff:fe4e:6cc8/10 
eri0:1: flags=402100841<UP,RUNNING,MULTICAST,ROUTER,IPv6,PREFERRED> mtu 1500 index 2
        inet6 2002:dce8:d28e::36/64 

This is not portable to OSX, Linux, FreeBSD, or Windows though. Windows is let off easy though as it has completely useless, from an administrators perspective, UUID based adapter names (depending upon the Windows version).

For Linux this article details how the parameter preferred_lft, where lft is short for "lifetime", can be altered to weight the selection process by the kernel. This setting doesn't appear conveniently available in the results of SIOCGIFCONF or getifaddrs() though.

So I want to bind to eth0, eri0, or whatever available interface name. The choices are a bit stark:

  1. Fail on adapter names resolving to multiple interfaces. I take this approach for handling multicast transports (OpenPGM) as the protocol MUST have one-only sending address.
  2. Bind to everything. This is a cop out and would be unexpected to users.
  3. Bind to the adapter with SO_BINDTODEVICE. This requires CAP_NET_RAW system capability on Linux which can be quite a cumbersome overhead for administrators.
  4. Bind to the first IPv6 interface on the adapter. The ordering tends to be completely bogus.
  5. Bind to the last interface. David Croft's article implies Linux does this, but is also a bit bogus.
  6. Enumerate over every interface and create a new socket explicitly for each.

With option #6 I would expect you could usually be smarter and take the approach that if only a link-local scope address is available bind to that, otherwise bind to just the available global-link scope addresses.

When connecting to another host then RFC 3484 can be used, but as you can see all the choices are dependent upon matching the destination address:

  1. Prefer same address. (i.e. destination is local machine)
  2. Prefer appropriate scope. (i.e. smallest scope shared with the destination)
  3. Avoid deprecated addresses.
  4. Prefer home addresses. Prefer outgoing interface. (i.e. prefer an address on the interface we're sending out of)
  5. Prefer matching label.
  6. Prefer public addresses.
  7. Use longest matching prefix.

In some circumstances we can use #7 here, but in the interface example above both global-scope interfaces have a 64-bit prefix length.

RFC 3484 has the following pertinent lines:

The IPv6 addressing architecture 5 allows multiple unicast
addresses to be assigned to interfaces. These addresses may have
different reachability scopes (link-local, site-local, or global).
These addresses may also be "preferred" or "deprecated" 6.

The link being to RFC 2462, similarly expanded:

preferred address - an address assigned to an interface whose use by upper layer protocols is unrestricted. Preferred addresses may be used as the source (or destination) address of packets sent from (or to) the interface.

But no methods to programmatically acquire this detail.

Props to Win32 API that exposes an ioctl SIO_ADDRESS_LIST_SORT that allows a developer to use not only RFC 3484 sorting but to take into consideration any system administrator overrides. Linux has /etc/gai.conf as used for RFC 3484 sorting in getaddrinfo() but no API for directly accessing the sorting. Solaris has the ipaddrsel command. OSX is following FreeBSD by adding ip6addrctl in 10.7.

edit: Some concerns with RFC 3484 sorting are listed and referred to in this additional IETF draft document:

https://datatracker.ietf.org/doc/html/draft-axu-addr-sel-01

Solaris, for example, creates new alias-interfaces for each new
address assigned to a physical interface. So if_index could also be
used to uniquely identify a source address specific routing table on
that platform. Other operating systems do not work the same way.

The author likes Solaris's approach of giving each additional IPv6 interface a new alias, so that eri0 would become the link-local scope address, and eri0:1 or eri0:2, etc, must be specified to use a global-scope address.

Clearly whilst a nice idea one couldn't expect to see other OS change for quite some time.

like image 610
Steve-o Avatar asked Aug 26 '11 08:08

Steve-o


1 Answers

I'm not sure this is in the direction you're seeking, but...

Poking around in the iproute bundle's ip code (ip/ipaddress.c) under linux shows that the ip command digs up interface flags like primary and secondary from a struct ifaddrmsg, member ifa_flags. The ifaddmsg seems to be acquired through a struct nlmsghdr which is documented in man 7 netlink, and used via sendmsg and recvmsg interaction with the kernel, which overall sounds like a royal pain but it's at least programmatic. Whether primary and secondary would be enough to be useful is a separate question.

like image 147
Alex North-Keys Avatar answered Sep 20 '22 00:09

Alex North-Keys