IPv6本地链路地址生成方式
IPv6中定义的一种地址类别为本地链路地址Link-Local Addresses,协议中规定,每个IPv6接口必须要有本地链路地址,使用FE80::/10地址块,相同于IPv4中的169.254.0.0/16网段,尽在本地链路有效。Linux内核中定义了4中生成IPv6本地链路地址的方式,参见枚举类型in6_addr_gen_mode的定义:
enum in6_addr_gen_mode {
IN6_ADDR_GEN_MODE_EUI64,
IN6_ADDR_GEN_MODE_NONE,
IN6_ADDR_GEN_MODE_STABLE_PRIVACY,
IN6_ADDR_GEN_MODE_RANDOM,
};
默认情况下,内核使用EUI64(IN6_ADDR_GEN_MODE_EUI64)模式生成本地链路地址,即由接口的MAC地址生成IPv6本地链路地址,如需修改默认模式,可修改ipv6_devconf_dlft结构的成员addr_gen_mode的初始值。如果成员stable_secret的initialized为true真,默认使用IN6_ADDR_GEN_MODE_STABLE_PRIVACY模式生成本地链路地址。
static struct ipv6_devconf ipv6_devconf_dflt __read_mostly = {
.stable_secret = {
.initialized = false,
},
.addr_gen_mode = IN6_ADDR_GEN_MODE_EUI64,
};
使用ip命令查看接口的本地链路地址。接口ens38的MAC地址为00:0c:29:74:7f:0e,生成之后的IPv6地址为fe80::20c:29ff:fe74:7f0e/64。在MAC中间插入了fffe;将第一个字节的第二位置1(此位表示全局或本地,因MAC为全局唯一的,需置1);最后增加fe80链路地址网段头。
$ ip link
3: ens38: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
link/ether 00:0c:29:74:7f:0e brd ff:ff:ff:ff:ff:ff
$
$ ip -6 addr
3: ens38: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
inet6 fe80::20c:29ff:fe74:7f0e/64 scope link
valid_lft forever preferred_lft forever
内核中IPv6链路地址的添加在net/ipv6/addrconf.c文件中处理。首先注册网络设备(netdevice)的通知处理函数addrconf_notify,监听网络设备的各种通知事件。
static struct notifier_block ipv6_dev_notf = {
.notifier_call = addrconf_notify,
.priority = ADDRCONF_NOTIFY_PRIORITY,
};
int __init addrconf_init(void)
{
register_netdevice_notifier(&ipv6_dev_notf);
}
和IPv6本地链路地址相关的主要是NETDEV_UP和NETDEV_CHANGE两个通知事件。在接收到这两种通知时,调用addrconf_dev_config具体处理地址配置过程。
static int addrconf_notify(struct notifier_block *this, unsigned long event, void *ptr)
{
switch (event) {
case NETDEV_UP:
case NETDEV_CHANGE:
switch (dev->type) {
default:
addrconf_dev_config(dev);
}
}
}
目前IPv6本地链路地址配置仅支持Ethernet类型的网络设备。另外,对于没有二层地址的设备类型(ARPHRD_NONE),不支持EUI64地址生成模式,需要将其修改为IN6_ADDR_GEN_MODE_RANDOM模式。内核最终调用addrconf_addr_gen函数来生成地址。
static void addrconf_dev_config(struct net_device *dev)
{
struct inet6_dev *idev;
idev = addrconf_add_dev(dev);
/* this device type has no EUI support */
if (dev->type == ARPHRD_NONE &&
idev->cnf.addr_gen_mode == IN6_ADDR_GEN_MODE_EUI64)
idev->cnf.addr_gen_mode = IN6_ADDR_GEN_MODE_RANDOM;
addrconf_addr_gen(idev, false);
}
EUI64模式
在函数addrconf_addr_gen中,首先使用ipv6_addr_set函数生成一个0xFE800000开头的IPv6地址。接着使用ipv6_generate_eui64函数生成后半段的8字节地址。
ipv6_addr_set(&addr, htonl(0xFE800000), 0, 0, 0);
switch (idev->cnf.addr_gen_mode) {
case IN6_ADDR_GEN_MODE_EUI64:
if (ipv6_generate_eui64(addr.s6_addr + 8, idev->dev) == 0)
}
函数ipv6_generate_eui64最终调用addrconf_ifid_eui48生成地址。将设备的MAC地址dev_addr的前三个字节拷贝到IPv6地址的后半段(s6_addr+8)开始处;在接下来的第4和第5个字节处添加0xFF和0xFE值;拷贝MAC地址的后三个字节到接下来的IPv6地址的第6个字节开始处。
为避免地址重复,对于使用相同链路地址的不同设备,将IPv6地址的第4和第5个字节使用设备标识dev_id填充。否则取反IPv6的后半段开始的第一个字节的bit1位。至此,EUI64地址生成完毕。
static inline void addrconf_addr_eui48_base(u8 *eui, const char *const addr)
{
memcpy(eui, addr, 3);
eui[3] = 0xFF;
eui[4] = 0xFE;
memcpy(eui + 5, addr + 3, 3);
}
static inline int addrconf_ifid_eui48(u8 *eui, struct net_device *dev)
{
addrconf_addr_eui48_base(eui, dev->dev_addr);
if (dev->dev_id) {
eui[3] = (dev->dev_id >> 8) & 0xFF;
eui[4] = dev->dev_id & 0xFF;
} else
eui[0] ^= 2;
}
RANDOM模式
IPv6的本地链路地址随机生成。将随机生成的IPv6地址数据保存在结构体ipv6_stable_secret的成员(struct in6_addr)secret中。注意随机数据仅生成一次。接着利用IN6_ADDR_GEN_MODE_STABLE_PRIVACY模式算法,生成随机的IPv6地址。
static void ipv6_gen_mode_random_init(struct inet6_dev *idev)
{
struct ipv6_stable_secret *s = &idev->cnf.stable_secret;
if (s->initialized)
return;
s = &idev->cnf.stable_secret;
get_random_bytes(&s->secret, sizeof(s->secret));
s->initialized = true;
}
STABLE_PRIVACY模式
此模式使用SHA摘要算法生成IPv6地址的后8个字节。具体见函数ipv6_generate_stable_address中的实现。SHA摘要所依据的原始数据有:设备的永久MAC地址(要求设备MAC地址的复制方式为NET_ADDR_PERM,否则其值为0)、本地链路地址的8字节前半部(0xFE80 0000 0000 0000)、随机数secret(见RANDOM模式)和重复地址检测(Duplicate Address Detection)次数(此处为初始化阶段,此值为0)。生成的摘要数据赋值给IPv6地址的后半段8个字节。
sha_init(digest);
memset(&data, 0, sizeof(data));
memset(workspace, 0, sizeof(workspace));
memcpy(data.hwaddr, idev->dev->perm_addr, idev->dev->addr_len);
data.prefix[0] = address->s6_addr32[0];
data.prefix[1] = address->s6_addr32[1];
data.secret = secret;
data.dad_count = dad_count;
sha_transform(digest, data.__data, workspace);
temp = *address;
temp.s6_addr32[2] = (__force __be32)digest[0];
temp.s6_addr32[3] = (__force __be32)digest[1];
另外,随机模式或者STABLE_PRIVACY模式生成的IPv6地址后半段,不能够与系统保留的接口ID相同,否则需要重新计算IPv6地址,重新计算时,递增SHA摘要算法数据中的DAD值。
static bool ipv6_reserved_interfaceid(struct in6_addr address)
{
if ((address.s6_addr32[2] | address.s6_addr32[3]) == 0)
return true;
if (address.s6_addr32[2] == htonl(0x02005eff) &&
((address.s6_addr32[3] & htonl(0xfe000000)) == htonl(0xfe000000)))
return true;
if (address.s6_addr32[2] == htonl(0xfdffffff) &&
((address.s6_addr32[3] & htonl(0xffffff80)) == htonl(0xffffff80)))
return true;
}
内核版本
Linux-4.15
更多推荐
所有评论(0)