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