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实现分析的主要内容,如果未能解决你的问题,请参考以下文章

清默网络——IPV6的地址详解

详解IPv6邻居发现协议

IPv6详解:SHA1算法实现及详解

LINUX ifconfig 命令详解

Stable Diffusion原理详解

IPv6ISATAP隧道技术详解