IPV6详解:Stable Privacy Address Kernel实现分析
Posted CQ小子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了IPV6详解:Stable Privacy Address Kernel实现分析相关的知识,希望对你有一定的参考价值。
主要分析Stable Privacy Address在kernel中的实现code
Kernel Version: 4.6
无状态地址配置过程
一般情况下,在对应的网卡启动的时候,如果支持IPV6,且软件的IPV6功能使能,就会自动配置一个Link local地址,这个link local地址就是以fe80开头,同时有了link local地址之后,系统就会发送一个RS的packet到网络中去,如果路由器支持IPV6,收到这个RS之后,会回复一个携带prefix信息的RA包,Host收到RA包之后,提取prefix,生成interface id,组成一个global地址,这个过程就是无状态地址配置的过程.
RS packet :
从图中看出,属于ICMPV6协议的封包,使用link local address发送,目的地址是路由器多播组
RA packet:
Host收到RA之后,就提取prefix,组成一个global地址
Kernel 实现分析
在kernel中专门提供了这样一个patch增加了对RFC7217的支持。Patch:ipv6: generation of stable privacy addresses for link-local and autoconf
@@ -2302,6 +2305,11 @@ void addrconf_prefix_rcv(struct net_device *dev, u8 *opt, int len, bool sllao)
in6_dev->token.s6_addr + 8, 8);
read_unlock_bh(&in6_dev->lock);
tokenized = true;
+ else if (in6_dev->addr_gen_mode ==
+ IN6_ADDR_GEN_MODE_STABLE_PRIVACY&&
+ !ipv6_generate_stable_address(&addr, 0,
+ in6_dev))
+ goto ok;
else if (ipv6_generate_eui64(addr.s6_addr + 8, dev) &&
ipv6_inherit_eui64(addr.s6_addr + 8, in6_dev))
in6_dev_put(in6_dev);
在收到prefix的时候,会判断下addr_gen_mode 生成地址的模式,如果是IN6_ADDR_GEN_MODE_STABLE_PRIVACY, 那么就调用ipv6_generate_stable_address 生成地址stable address.
static int ipv6_generate_stable_address(struct in6_addr *address,
u8 dad_count,
const struct inet6_dev *idev)
static DEFINE_SPINLOCK(lock);
static __u32 digest[SHA_DIGEST_WORDS]; //160bit消息摘要
static __u32 workspace[SHA_WORKSPACE_WORDS];
static union
char __data[SHA_MESSAGE_BYTES]; //512bit data block : 128bit secret + 64bit prefix + 256bit HW + 8bit DAD_Count;
struct
struct in6_addr secret;
__be32 prefix[2];
unsigned char hwaddr[MAX_ADDR_LEN];
u8 dad_count;
__packed;
data;
struct in6_addr secret;
struct in6_addr temp;
struct net *net = dev_net(idev->dev); // 取得网卡的device结构体
BUILD_BUG_ON(sizeof(data.__data) != sizeof(data));
//获取初始化的scret
if (idev->cnf.stable_secret.initialized)
secret = idev->cnf.stable_secret.secret;
else if (net->ipv6.devconf_dflt->stable_secret.initialized)
secret = net->ipv6.devconf_dflt->stable_secret.secret;
else
return -1; //如果没有scret,直接返回-1
retry:
spin_lock_bh(&lock);
sha_init(digest); //初始化SHA-1的原始消息摘要值
memset(&data, 0, sizeof(data)); //清0,data区域
memset(workspace, 0, sizeof(workspace));//清0,workspace
memcpy(data.hwaddr, idev->dev->perm_addr, idev->dev->addr_len); //将设备的mac地址复制到data中
data.prefix[0] = address->s6_addr32[0]; //prefix信息
data.prefix[1] = address->s6_addr32[1];
data.secret = secret; // secret 信息 到data
data.dad_count = dad_count; //dad_count 传入的值,赋值给data
sha_transform(digest, data.__data, workspace); //使用SHA-1算法进行加密,并产生160bit的摘要信息
temp = *address;
//这个地方就是使用160bit的摘要信息的前64bit,赋值给IPV6地址的后64bit,这样就组成了一个prefix + interface id的完整地址
temp.s6_addr32[2] = (__force __be32)digest[0];
temp.s6_addr32[3] = (__force __be32)digest[1];
spin_unlock_bh(&lock);
//判断是否是保留的interface id,这些interface id不能被使用
if (ipv6_reserved_interfaceid(temp))
dad_count++;// 如果和保留interface id 一致,则dad_count加1
//如果dad_count > idgen_retries 生成stable address失败,否则retry
if (dad_count > dev_net(idev->dev)->ipv6.sysctl.idgen_retries)
return -1;
goto retry;
*address = temp;
return 0;
从ipv6_generate_stable_address函数中可以总结出以下步骤:
1. 初始化消息摘要
2. 填充需要加密的512数据块:128bit secret + 64bit prefix + 256bit HW + 8bit DAD_Count 这个地方不足512字节,应该后面的补0
3. 使用SHA-1对data进行加密,并产生160bit的消息摘要
4. 将消息摘要的前64bit,放置到IPV6地址的后64bit
5. 检查生成的interface id是否保留的interface id,如果是保留的,需要重新生成或者直接失败
6. 生成整个地址,返回。
1-4步都是设计到SHA-1算法,如果不了解可以参考文章: IPv6详解:SHA1算法实现及详解 了解 FIPS 180-4的算法流程 初始化消息摘要,这个地方就是将初始值复制给160bit的消息摘要
/**
* sha_init - initialize the vectors for a SHA1 digest
* @buf: vector to initialize
*/
void sha_init(__u32 *buf)
buf[0] = 0x67452301;
buf[1] = 0xefcdab89;
buf[2] = 0x98badcfe;
buf[3] = 0x10325476;
buf[4] = 0xc3d2e1f0;
sha_transform
这个函数是SHA-1的核心算法,对单个的512 block使用SHA-1算法进行转换,这个函数最后产生一个160bit的消息摘要,但是这个地方不要与文章 IPv6详解:SHA1算法实现及详解 中的FIS 180-4 SHA-1算法混淆,两者差异的地方在于Linux kernel实现的 不再进行补位和补长度的操作,应该不够512个字节就会填0,剩下的步骤2者一致 1 .首先将512 data block分成 80个双字,W(0~79),如下提供了2个宏,在t为0~15的时候,使用SHA_SRC, 计算出W0~W15 在16~79的时候,使用SHA_MAX计算出W16~W79/*
* Where do we get the source from? The first 16 iterations get it from
* the input data, the next mix it from the 512-bit array.
*/
#define SHA_SRC(t) get_unaligned_be32((__u32 *)data + t)
#define SHA_MIX(t) rol32(W(t+13) ^ W(t+8) ^ W(t+2) ^ W(t), 1)
2. 有了W0~W79, 分别使用函数对每个双字进行计算,更新A,B,C,D,E,计算的宏如下,看起来还是比较麻烦的,不同的T使用不同的常量
#define SHA_ROUND(t, input, fn, constant, A, B, C, D, E) do \\
__u32 TEMP = input(t); setW(t, TEMP); \\
E += TEMP + rol32(A,5) + (fn) + (constant); \\
B = ror32(B, 2); while (0)
#define T_0_15(t, A, B, C, D, E) SHA_ROUND(t, SHA_SRC, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
#define T_16_19(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (((C^D)&B)^D) , 0x5a827999, A, B, C, D, E )
#define T_20_39(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0x6ed9eba1, A, B, C, D, E )
#define T_40_59(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, ((B&C)+(D&(B^C))) , 0x8f1bbcdc, A, B, C, D, E )
#define T_60_79(t, A, B, C, D, E) SHA_ROUND(t, SHA_MIX, (B^C^D) , 0xca62c1d6, A, B, C, D, E )
具体的算法如下,比较长,进行80次运算
void sha_transform(__u32 *digest, const char *data, __u32 *array)
__u32 A, B, C, D, E;
A = digest[0];
B = digest[1];
C = digest[2];
D = digest[3];
E = digest[4];
/* Round 1 - iterations 0-16 take their input from 'data' */
T_0_15( 0, A, B, C, D, E);
T_0_15( 1, E, A, B, C, D);
T_0_15( 2, D, E, A, B, C);
T_0_15( 3, C, D, E, A, B);
T_0_15( 4, B, C, D, E, A);
T_0_15( 5, A, B, C, D, E);
T_0_15( 6, E, A, B, C, D);
T_0_15( 7, D, E, A, B, C);
T_0_15( 8, C, D, E, A, B);
T_0_15( 9, B, C, D, E, A);
T_0_15(10, A, B, C, D, E);
T_0_15(11, E, A, B, C, D);
T_0_15(12, D, E, A, B, C);
T_0_15(13, C, D, E, A, B);
T_0_15(14, B, C, D, E, A);
T_0_15(15, A, B, C, D, E);
/* Round 1 - tail. Input from 512-bit mixing array */
T_16_19(16, E, A, B, C, D);
T_16_19(17, D, E, A, B, C);
T_16_19(18, C, D, E, A, B);
T_16_19(19, B, C, D, E, A);
/* Round 2 */
T_20_39(20, A, B, C, D, E);
T_20_39(21, E, A, B, C, D);
T_20_39(22, D, E, A, B, C);
T_20_39(23, C, D, E, A, B);
T_20_39(24, B, C, D, E, A);
T_20_39(25, A, B, C, D, E);
T_20_39(26, E, A, B, C, D);
T_20_39(27, D, E, A, B, C);
T_20_39(28, C, D, E, A, B);
T_20_39(29, B, C, D, E, A);
T_20_39(30, A, B, C, D, E);
T_20_39(31, E, A, B, C, D);
T_20_39(32, D, E, A, B, C);
T_20_39(33, C, D, E, A, B);
T_20_39(34, B, C, D, E, A);
T_20_39(35, A, B, C, D, E);
T_20_39(36, E, A, B, C, D);
T_20_39(37, D, E, A, B, C);
T_20_39(38, C, D, E, A, B);
T_20_39(39, B, C, D, E, A);
/* Round 3 */
T_40_59(40, A, B, C, D, E);
T_40_59(41, E, A, B, C, D);
T_40_59(42, D, E, A, B, C);
T_40_59(43, C, D, E, A, B);
T_40_59(44, B, C, D, E, A);
T_40_59(45, A, B, C, D, E);
T_40_59(46, E, A, B, C, D);
T_40_59(47, D, E, A, B, C);
T_40_59(48, C, D, E, A, B);
T_40_59(49, B, C, D, E, A);
T_40_59(50, A, B, C, D, E);
T_40_59(51, E, A, B, C, D);
T_40_59(52, D, E, A, B, C);
T_40_59(53, C, D, E, A, B);
T_40_59(54, B, C, D, E, A);
T_40_59(55, A, B, C, D, E);
T_40_59(56, E, A, B, C, D);
T_40_59(57, D, E, A, B, C);
T_40_59(58, C, D, E, A, B);
T_40_59(59, B, C, D, E, A);
/* Round 4 */
T_60_79(60, A, B, C, D, E);
T_60_79(61, E, A, B, C, D);
T_60_79(62, D, E, A, B, C);
T_60_79(63, C, D, E, A, B);
T_60_79(64, B, C, D, E, A);
T_60_79(65, A, B, C, D, E);
T_60_79(66, E, A, B, C, D);
T_60_79(67, D, E, A, B, C);
T_60_79(68, C, D, E, A, B);
T_60_79(69, B, C, D, E, A);
T_60_79(70, A, B, C, D, E);
T_60_79(71, E, A, B, C, D);
T_60_79(72, D, E, A, B, C);
T_60_79(73, C, D, E, A, B);
T_60_79(74, B, C, D, E, A);
T_60_79(75, A, B, C, D, E);
T_60_79(76, E, A, B, C, D);
T_60_79(77, D, E, A, B, C);
T_60_79(78, C, D, E, A, B);
T_60_79(79, B, C, D, E, A);
digest[0] += A;
digest[1] += B;
digest[2] += C;
digest[3] += D;
digest[4] += E;
计算完80次,生成的A,B,C,D,E,然后加上原始的,就是160bit的输出。更详细的算法参考:
IPv6详解:SHA1算法实现及详解
检查是否是保留interface id
分配的地址如下之中情况,认为分配失败。
static bool ipv6_reserved_interfaceid(struct in6_addr address)
// interface id 为0 ,认为失败
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;
return false;
DAD失败的处理
生成地址之后,会进行DAD验证是否是重复地址,如果是重复的地址,需要重新生成void addrconf_dad_failure(struct inet6_ifaddr *ifp)
struct in6_addr addr;
struct inet6_dev *idev = ifp->idev;
struct net *net = dev_net(ifp->idev->dev);
if (addrconf_dad_end(ifp))
in6_ifa_put(ifp);
return;
//进入这个函数说明DAD失败
net_info_ratelimited("%s: IPv6 duplicate address %pI6c detected!\\n",
ifp->idev->dev->name, &ifp->addr);
spin_lock_bh(&ifp->lock);
//如果地址flag有IFA_F_STABLE_PRIVACY,说明是stable address
if (ifp->flags & IFA_F_STABLE_PRIVACY)
int scope = ifp->scope;
u32 flags = ifp->flags;
struct in6_addr new_addr;
struct inet6_ifaddr *ifp2;
u32 valid_lft, preferred_lft;
int pfxlen = ifp->prefix_len;
int retries = ifp->stable_privacy_retry + 1; //retry 次数加一
//如果重传次数大于idgen_retries,就彻底失败
if (retries > net->ipv6.sysctl.idgen_retries)
net_info_ratelimited("%s: privacy stable address generation failed because of DAD conflicts!\\n",
ifp->idev->dev->name);
goto errdad;
//否则重新生成ipv6_generate_stable_address,retries 为DAD_Counter
new_addr = ifp->addr;
if (ipv6_generate_stable_address(&new_addr, retries,
idev))
goto errdad;
valid_lft = ifp->valid_lft;
preferred_lft = ifp->prefered_lft;
spin_unlock_bh(&ifp->lock);
if (idev->cnf.max_addresses &&
ipv6_count_addresses(idev) >=
idev->cnf.max_addresses)
goto lock_errdad;
net_info_ratelimited("%s: generating new stable privacy address because of DAD conflict\\n",
ifp->idev->dev->name);
//重新设置到接口中
ifp2 = ipv6_add_addr(idev, &new_addr, NULL, pfxlen,
scope, flags, valid_lft,
preferred_lft);
if (IS_ERR(ifp2))
goto lock_errdad;
spin_lock_bh(&ifp2->lock);
ifp2->stable_privacy_retry = retries;
ifp2->state = INET6_IFADDR_STATE_PREDAD;
spin_unlock_bh(&ifp2->lock);
addrconf_mod_dad_work(ifp2, net->ipv6.sysctl.idgen_delay);
in6_ifa_put(ifp2);
lock_errdad:
spin_lock_bh(&ifp->lock);
else if (idev->cnf.accept_dad > 1 && !idev->cnf.disable_ipv6)
addr.s6_addr32[0] = htonl(0xfe800000);
addr.s6_addr32[1] = 0;
if (!ipv6_generate_eui64(addr.s6_addr + 8, idev->dev) &&
ipv6_addr_equal(&ifp->addr, &addr))
/* DAD failed for link-local based on MAC address */
idev->cnf.disable_ipv6 = 1;
pr_info("%s: IPv6 being disabled!\\n",
ifp->idev->dev->name);
errdad:
/* transition from _POSTDAD to _ERRDAD */
ifp->state = INET6_IFADDR_STATE_ERRDAD;
spin_unlock_bh(&ifp->lock);
//再次进行dad检查
addrconf_mod_dad_work(ifp, 0);
从上述代码可以看出基本流程是按照RFC7217的说明:
如果DAD失败,就会在DAD_Counter满足idgen_retries,就会重新生成, DAD_Counter增加的地方有两个:
1. 刚生成地址时,检查是否是保留interface id,如果是,DAD_Counter 加一,重新生成stable address
2. DAD失败之后,重新生成stable address,也会加一
以上是关于IPV6详解:Stable Privacy Address Kernel实现分析的主要内容,如果未能解决你的问题,请参考以下文章