识别适配器的首选IPv6源地址

17
如果您有一个启用了IPv6的主机,拥有多个全局范围地址,那么您如何通过编程来识别bind()的首选地址?
示例地址列表:
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

在Solaris操作系统中,您可以使用接口标志指定首选地址,并且可以通过SIOCGLIFCONF以编程方式访问它。
/usr/include/net/if.h:
#define   IFF_PREFERRED   0x0400000000    /* Prefer as source address */

如接口列表中所列:

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 

这段代码无法在OSX、Linux、FreeBSD或Windows上使用。但是,从管理员的角度来看,Windows非常容易解决,因为它具有完全无用的基于UUID的适配器名称(取决于Windows版本)。

对于Linux,本文详细介绍了如何调整参数preferred_lft(其中lft缩写为“lifetime”),以通过内核加权选择过程。然而,在SIOCGIFCONFgetifaddrs()的结果中,此设置并不方便地显示出来。

所以我想要绑定到eth0eri0或其他可用的接口名称。选择有点单调:

  1. 当适配器名称解析为多个接口时失败。 我采用这种方法来处理组播传输(OpenPGM),因为协议必须具有仅一个发送地址。
  2. 绑定到所有内容。 这是一种投降,对用户来说也是意外的。
  3. 绑定到具有SO_BINDTODEVICE的适配器。 这需要Linux上的CAP_NET_RAW系统功能,这可能对管理员来说是相当繁琐的开销。
  4. 绑定到适配器上的第一个IPv6接口。 排序往往是完全错误的。
  5. 绑定到最后一个接口。 David Croft的文章暗示Linux会这样做,但也有点虚假。
  6. 枚举每个接口并为每个接口显式创建一个新套接字。

使用选项#6,我希望您通常可以更聪明地采取方法,即如果仅存在本地链接范围地址,则绑定到该地址,否则仅绑定到可用的全局链接范围地址。

连接到另一个主机时,然后可以使用RFC 3484,但是如您所见,所有选择都取决于匹配目标地址:

  1. 优先选择相同地址。(即目标是本地机器)
  2. 优先选择适当的范围。(即与目标共享最小范围)
  3. 避免使用已弃用的地址。
  4. 优先选择家庭地址。优先选择外发接口。(即优先选择我们正在发送的接口上的地址)
  5. 优先匹配标签。
  6. 优先选择公共地址。
  7. 使用最长匹配前缀。

在某些情况下,我们可以在这里使用#7,但在上面的接口示例中,全局作用域接口都具有64位前缀长度。

RFC 3484有以下相关行:

IPv6寻址架构5允许将多个单播地址分配给接口。这些地址可能具有不同的可达性范围(链路本地、站点本地或全局)。这些地址也可能是“首选”或“已弃用”6

链接到RFC 2462,类似地扩展:

首选地址-分配给接口的地址,其被上层协议的使用不受限制。首选地址可以用作从接口发送的数据包的源(或目的地)地址。

但是没有编程获取此详细信息的方法。

感谢Win32 API,它公开了一个ioctl SIO_ADDRESS_LIST_SORT,允许开发人员不仅使用RFC 3484排序,而且考虑任何系统管理员覆盖。Linux有/etc/gai.conf用于RFC 3484排序在getaddrinfo()中,但没有API直接访问排序。Solaris有ipaddrsel命令。OSX正在遵循FreeBSD,在10.7中添加了ip6addrctl

编辑: RFC 3484排序存在一些问题,这些问题在这个额外的IETF草案文件中列出并参考:

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

例如,Solaris 为每个分配给物理接口的新地址创建新的别名接口。因此,if_index 还可以用于唯一地标识特定于源地址的路由表。其他操作系统的工作方式不同。
作者喜欢 Solaris 的方法,为每个附加的 IPv6 接口提供一个新的别名,因此 eri0 将成为链路本地范围地址,必须指定 eri0:1 或 eri0:2 等才能使用全局范围地址。
显然,虽然这是一个好主意,但人们不能指望在相当长的时间内看到其他操作系统改变。

你为什么想要绑定到特定的地址,而不是让操作系统为你选择呢? - Sander Steffann
1
@Sander 部署在多NIC环境中,例如您可能拥有互联网、DMZ、内部网络,或者专用于某些流量的网络,例如原始传入数据、处理后数据和客户端流量。 - Steve-o
我明白。通常我会尝试确保默认行为正常工作,例如使用全局地址进行外部通信和ULA用于内部NIC,以便最长前缀匹配起作用,但这并非总是可能的。而且当发送到多播目标时,它不起作用,因为ULA始终具有更长的匹配。在配置文件中指定要绑定到的源地址是一种选择。 - Sander Steffann
我知道这有点过时了。但是你找到你的问题的答案了吗? - Sergei Nikulov
@Sergey 还没有,我会定期审核每个新平台,看是否出现了实现所述功能的新选项。 - Steve-o
1个回答

2
我不确定这是否是您正在寻找的方向,但是……
在Linux下查看iproute包中的ip代码(ip / ipaddress.c),可以发现ip命令从struct ifaddrmsg的成员ifa_flags中挖掘接口标志(如primary和secondary)。ifaddmsg似乎是通过struct nlmsghdr获得的,该结构在man 7 netlink中有文档说明,并通过sendmsg和recvmsg与内核交互,总体上听起来很麻烦,但至少是可编程的。主要和次要是否足够有用是一个单独的问题。

1
最终,通过 netlink 可以发送 RTM_GETADDR 请求来返回一个 IPv6 地址列表,其中包括属性 IFA_CACHEINFO,该属性包含参数 ifa_preferred,如果为零则表示不优先。 - Steve-o

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接