1. 程式人生 > >IPv6本地鏈路地址生成方式

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