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

 

©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页