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:
- 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.
- Bind to everything. This is a cop out and would be unexpected to users.
- Bind to the adapter with
SO_BINDTODEVICE
. This requiresCAP_NET_RAW
system capability on Linux which can be quite a cumbersome overhead for administrators. - Bind to the first IPv6 interface on the adapter. The ordering tends to be completely bogus.
- Bind to the last interface. David Croft's article implies Linux does this, but is also a bit bogus.
- 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:
- Prefer same address. (i.e. destination is local machine)
- Prefer appropriate scope. (i.e. smallest scope shared with the destination)
- Avoid deprecated addresses.
- Prefer home addresses. Prefer outgoing interface. (i.e. prefer an address on the interface we're sending out of)
- Prefer matching label.
- Prefer public addresses.
- 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.
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.
精彩评论