kk Blog —— 通用基础


date [-d @int|str] [+%s|"+%F %T"]
netstat -ltunp

crash kmem

一、kmem -s 查看slab

1
2
3
4
5
6
crash> kmem -s
CACHE            NAME                 OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE
...
ffff8808132d1ac0 request_sock_TCP         128          2        30      1     4k
ffff8808135e1400 sock_inode_cache         704        298       470     94     4k
...

二、kmem -S 查看slab中详细内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
crash> kmem -S request_sock_TCP
CACHE            NAME                 OBJSIZE  ALLOCATED     TOTAL  SLABS  SSIZE
ffff8808132d1ac0 request_sock_TCP         128          2        30      1     4k
SLAB              MEMORY            TOTAL  ALLOCATED  FREE
ffff88078b9c6000  ffff88078b9c60c0     30          2    28
FREE / [ALLOCATED]
   ffff88078b9c60c0
   ffff88078b9c6140
   ffff88078b9c61c0
   ffff88078b9c6240
   ffff88078b9c62c0
   ffff88078b9c6340
   ffff88078b9c63c0
   ffff88078b9c6440
   ffff88078b9c64c0
   ffff88078b9c6540
   ffff88078b9c65c0
   ffff88078b9c6640
   ffff88078b9c66c0
  [ffff88078b9c6740]
  [ffff88078b9c67c0]
   ffff88078b9c6840
   ffff88078b9c68c0
   ffff88078b9c6940
   ffff88078b9c69c0
...

request_sock_TCP 是 struct request_sock 类型,所以对于已分配的地址可以直接查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
crash> struct request_sock 0xffff88078b9c6740
struct request_sock {
  dl_next = 0x0, 
  mss = 1460, 
  retrans = 0 '\000', 
  cookie_ts = 0 '\000', 
  window_clamp = 8388480, 
  rcv_wnd = 14600, 
  ts_recent = 0, 
  expires = 4302901768, 
  rsk_ops = 0xffffffff81c0e840 <tcp_request_sock_ops>, 
  sk = 0xffff880771dad800, 
  secid = 3039208612, 
  peer_secid = 3672081930
}

http://blog.csdn.net/u011279649/article/details/17529315

Android系统典型bootloader分析

http://security.tencent.com/index.php/blog/msg/38

1、bootloader是什么?

简单地说,bootloader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。

Android系统基于Linux,所以bootloader部分也是与传统的嵌入式设备上运行的Linux没有什么区别。由于除Google外的大部分Android厂商都没有提供bootloader的源代码,所以分析手机设备的bootloader需要使用逆向工程的手段,当然由于有了Google官方的开源bootloader代码做参考,能让分析工作轻松不少。本文中使用的分析工具为IDA 6.5,针对的手机设备为N9006,固件版本为N9006ZCUDMK2。

2、bootloader典型结构

这部分会以高通MSM8960为例子介绍下Bootloader的典型结构。

高通MSM8960中包含多个运算单元,分别负责引导过程中的不同功能,sbl1的代码负责加载sbl2,sbl2加载tz和sbl3,sbl3加载apppsbl,appsbl加载HLOS。


图1 SecureBoot 3.0 的Code Flow


图2 MSM8960引导过程简化流程图

3、Note3的bootloader结构分析

国行版Note3(N9006)使用的CPU是MSM8974,它的bootloader结构与典型的MSM8960差不多,最大的区别就是把sbl1,sbl2,sbl3整合进了一个文件sbl1中,TrustZone和APPSBL都由sbl1进行验证和加载,以下为几个主要功能的加载代码分析。

sbl1的功能是对硬件进行初始化并加载其他模块,需要加载的模块信息按顺序保存在sbl1中,对应每个模块的数据是一段大小为0x64字节的模块信息数据内,sbl1中有一个循环负责验证和加载所有需要的其他模块(tz,rpm,wdt,appsbl),加载代码会根据模块信息内的数据调用不同的加载器加载和验证的代码,具体代码如下图。


图3 sbl1中循环加载全部模块的代码


图4 sbl1中对待加载模块进行验证


图5 TZ模块信息数据


图6 APPSBL模块信息数据

固件包里的tz.mbn是加载在TrustZone中的模块,模块格式为elf,这个模块中的代码和系统其他模块代码运行在互相隔离的区域内,权限也比其他模块更高,三星KNOX的很多底层安全特性也是在这部分中实现,关于TrustZone的更多资料可以参考arm官方的说明。

固件包里的aboot.mbn就是APPSBL模块,模块格式为bin,文件最前面的0x28字节的头部描述了bin的加载地址等信息,后面的数据就是实际加载到内存中的映像,整个bootloader中这个模块的代码量最大(很大一部分是openssl的代码),linux内核的验证和加载(正常启动和Recovery模式),ODIN模式等等代码都包含在这个模块内。


图7 aboot.mbn文件头


图8 根据按键和共享内存中的数据确定引导模式


图9 三星特有的ODIN刷机模式代码

4、Note3的bootloader中KNOX系统的底层代码初步分析

Note3提供了一个企业安全套装KNOX,这个系统包含了底层的Customizable Secure Boot和TrustZone-based Integrity Measurement Architecture(TIMA,目前为2.0版本),系统层的SecurityEnhancements for Android(SE-Android)和应用层的Samsung KNOX Container,Encrypted File System(EFS),Virtual Private Network(VPN),其中Customizable Secure Boot和TIMA的代码包含在Bootloader的aboot.mbn,tz.mbn,NON-HLOS.bin中,功能为保障加载的内核在加载时和运行期的完整性。

通过前面的分析,我们已经知道了tz.mbn和aboot.mbn在加载时已经由sbl1验证过完整性,tz.mbn加载后会在CPU的安全环境下运行,从高权限的隔离区域内对系统的完整性进行监控,而负责加载android内核的aboot.mbn中包含对内核的完整性检测,三星在bootloader每一部分的结尾都会加上自己的签名,加载前会对签名进行验证,以保障系统未被修改过。


图10 tz.mbn中初始化TIMA系统的的代码


图11 aboot.mbn中对内核是否使用SEANDROID进行验证

当任何一部分检测代码发现系统异常状况后,就会调用SMC指令通知TrustZone中运行的TIMA系统设置fuse为系统完整性被破坏,此fuse数据一旦被设置后没有办法被重置,系统也无法再次进入KNOX系统。


图12 加载内核前对内核签名和TIMA的测点进行验证


图13 系统完整性检测失败后设置fuse值

当以上所有检测都通过后,bootloader会把内核复制到指定的内存地址并跳到内核的入口继续执行,到此,就进入了系统内核代码的范畴,bootloader的使命也就完成了,跳到linux内核入口的代码见图14。


图14 内核加载和校验完成后跳到内核的入口点继续执行

另外,除了这两个模块外Modem固件相关的NON-HLOS.bin中也有大量TIMA系统相关的文件,由于TIMA系统包含大量硬件相关代码(使用三星猎户座CPU的N900中TIMA系统的实现与高通CPU的N9006差别很大),如果需要进行进一步的分析TIMA在modem中的行为,需要对TrustZone,modem工作方式等有更多了解。


图15 NON-HLOS.bin中包含的大量TIMA相关文件

IPV6 实现

http://www.cnblogs.com/super-king/p/ipv6_implement.html

code extract from 2.6.24. 在文件 net/ipv6/af_inet6.c 中包含了ipv6协议初始化的主函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
static int __init inet6_init(void)
{
	struct sk_buff *dummy_skb;
	struct list_head *r;
	int err;
	//inet6_skb_parm必须小于等于skb中的cb
	BUILD_BUG_ON(sizeof(struct inet6_skb_parm) > sizeof(dummy_skb->cb));

	//初始化tcpv6_prot结构中的一些与slab相关的字段,然后添加到 proto_list 全局连表
	err = proto_register(&tcpv6_prot, 1);
	if (err)
		goto out;
	//udp协议同上
	err = proto_register(&udpv6_prot, 1);
	if (err)
		goto out_unregister_tcp_proto;
	//udp-lite传输协议,主要用于多媒体传输,参考kernel中的 Documentation/networking/udplite.txt
	err = proto_register(&udplitev6_prot, 1);
	if (err)
		goto out_unregister_udp_proto;
	//原始套接字同上
	err = proto_register(&rawv6_prot, 1);
	if (err)
		goto out_unregister_udplite_proto;

	/* Register the socket-side information for inet6_create.  */
	for(r = &inetsw6[0]; r < &inetsw6[SOCK_MAX]; ++r) //初始化一个协议连表数组
		INIT_LIST_HEAD(r);
	/* We MUST register RAW sockets before we create the ICMP6, IGMP6, or NDISC control sockets. */
	//根据参数数据结构中标识的协议类型,把这数据结构添加到上面的协议连表数组中
	inet6_register_protosw(&rawv6_protosw);

	/* Register the family here so that the init calls below will be able to create sockets. (?? is this dangerous ??) */
	//注册ipv6协议族,主要是注册socket创建函数
	err = sock_register(&inet6_family_ops);
	if (err)
		goto out_unregister_raw_proto;

	/* Initialise ipv6 mibs */
	err = init_ipv6_mibs(); //所有ipv6相关的统计信息
	if (err)
		goto out_unregister_sock;
	/* ipngwg API draft makes clear that the correct semantics for TCP and UDP is to consider one TCP and UDP instance 
	 * in a host availiable by both INET and INET6 APIs and able to communicate via both network protocols.
	 */
#ifdef CONFIG_SYSCTL
	ipv6_sysctl_register(); // ipv6协议proc条件项初始化
#endif
	//icmp协议注册
	err = icmpv6_init(&inet6_family_ops);
	if (err)
		goto icmp_fail;
	//邻居协议(arp)初始化       
	err = ndisc_init(&inet6_family_ops);
	if (err)
		goto ndisc_fail;
	//igmp协议初始化       
	err = igmp6_init(&inet6_family_ops);
	if (err)
		goto igmp_fail;
	//ipv6协议相关的 netfilter 初始化     
	err = ipv6_netfilter_init();
	if (err)
		goto netfilter_fail;

	/* Create /proc/foo6 entries. */
#ifdef CONFIG_PROC_FS //注册/proc/中协议统计输出项
	err = -ENOMEM;
	if (raw6_proc_init())
		goto proc_raw6_fail;
	if (tcp6_proc_init())
		goto proc_tcp6_fail;
	if (udp6_proc_init())
		goto proc_udp6_fail;
	if (udplite6_proc_init())
		goto proc_udplite6_fail;
	if (ipv6_misc_proc_init())
		goto proc_misc6_fail;
	if (ac6_proc_init())
		goto proc_anycast6_fail;
	if (if6_proc_init())
		goto proc_if6_fail;
#endif
	ip6_route_init(); //ipv6 路由初始化
	ip6_flowlabel_init();//ipv6 中流标记,注册了输出流标记的 proc

	//rtnetlink相关部分和路由模板中一些字段和其他一些功能的初始化
	err = addrconf_init();
	if (err)
		goto addrconf_fail;
	/* Init v6 extension headers. */
	//ipv6 新添加的扩展头初始化,参考ipv6介绍
	ipv6_rthdr_init();
	ipv6_frag_init();
	ipv6_nodata_init();
	ipv6_destopt_init();

	/* Init v6 transport protocols. */
	//最主要的传输层协议初始化
	udpv6_init();
	udplitev6_init();
	tcpv6_init();

	//最后注册ipv6协议,注册协议处理函数
	ipv6_packet_init();
	err = 0;
out:
	return err;
	...... //下面就是错误处理的过程
}

下面我们主要看ipv6协议部分流程,其他部分在各自相关文章中介绍。

ipv6扩展头,路由包头注册

1
2
3
4
5
void __init ipv6_rthdr_init(void)
{
	if (inet6_add_protocol(&rthdr_protocol, IPPROTO_ROUTING) < 0)
		printk(KERN_ERR "ipv6_rthdr_init: Could not register protocol\n");
};

ipv6扩展头,分片包头注册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void __init ipv6_frag_init(void)
{
	if (inet6_add_protocol(&frag_protocol, IPPROTO_FRAGMENT) < 0)
		printk(KERN_ERR "ipv6_frag_init: Could not register protocol\n");

	ip6_frags.ctl = &ip6_frags_ctl;
	ip6_frags.hashfn = ip6_hashfn;
	ip6_frags.constructor = ip6_frag_init;
	ip6_frags.destructor = NULL;
	ip6_frags.skb_free = NULL;
	ip6_frags.qsize = sizeof(struct frag_queue);
	ip6_frags.match = ip6_frag_match;
	ip6_frags.frag_expire = ip6_frag_expire;
	inet_frags_init(&ip6_frags);
}
void __init ipv6_nodata_init(void)
{
	if (inet6_add_protocol(&nodata_protocol, IPPROTO_NONE) < 0)
		printk(KERN_ERR "ipv6_nodata_init: Could not register protocol\n");
}

ipv6扩展头,目的选项包头注册

1
2
3
4
5
6
7
8
9
10
void __init ipv6_destopt_init(void)
{
	if (inet6_add_protocol(&destopt_protocol, IPPROTO_DSTOPTS) < 0)
		printk(KERN_ERR "ipv6_destopt_init: Could not register protocol\n");
}
	注册ipv6协议处理函数
void __init ipv6_packet_init(void)
{
	dev_add_pack(&ipv6_packet_type);
}

当netif_receive_skb函数向上层递交skb时会根据协议类型调用相关的协议处理函数,那么就会调用到 ipv6_rcv函数了。

1
2
3
4
5
6
static struct packet_type ipv6_packet_type = {
	.type = __constant_htons(ETH_P_IPV6),
	.func = ipv6_rcv,
	.gso_send_check = ipv6_gso_send_check,
	.gso_segment = ipv6_gso_segment,
};

ipv6协议处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
int ipv6_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
	struct ipv6hdr *hdr;
	u32             pkt_len;
	struct inet6_dev *idev;

	if (dev->nd_net != &init_net) {
		kfree_skb(skb);
		return 0;
	}
	//mac地址是其他主机的包
	if (skb->pkt_type == PACKET_OTHERHOST) {
		kfree_skb(skb);
		return 0;
	}
	rcu_read_lock();
	//获取ipv6相关的配置结构
	idev = __in6_dev_get(skb->dev);

	IP6_INC_STATS_BH(idev, IPSTATS_MIB_INRECEIVES);
	//是否共享,如果是,新clone一个
	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
		IP6_INC_STATS_BH(idev, IPSTATS_MIB_INDISCARDS);
		rcu_read_unlock();
		goto out;
	}
	//清空保存扩展头解析结果的数据结构
	memset(IP6CB(skb), 0, sizeof(struct inet6_skb_parm));

	//保存接收这个数据包的设备索引
	IP6CB(skb)->iif = skb->dst ? ip6_dst_idev(skb->dst)->dev->ifindex : dev->ifindex;

	//有足够的头长度,ipv6是40字节
	if (unlikely(!pskb_may_pull(skb, sizeof(*hdr))))
		goto err;

	hdr = ipv6_hdr(skb); //获取头

	if (hdr->version != 6) //验证版本
		goto err;

	//传输头(扩展头)在网络头后面
	skb->transport_header = skb->network_header + sizeof(*hdr);
	//保存下一个扩展头协议在ipv6头结构中的偏移
	IP6CB(skb)->nhoff = offsetof(struct ipv6hdr, nexthdr);
	pkt_len = ntohs(hdr->payload_len); //ipv6负载数据长度

	/* pkt_len may be zero if Jumbo payload option is present */
	if (pkt_len || hdr->nexthdr != NEXTHDR_HOP) { //没有使用扩展头逐个跳段选项
		if (pkt_len + sizeof(struct ipv6hdr) > skb->len) { //数据长度不对
			IP6_INC_STATS_BH(idev, IPSTATS_MIB_INTRUNCATEDPKTS);
			goto drop;
		}
		//如果skb->len > (pkt_len + sizeof(struct ipv6hdr))试着缩小skb->len的长度
		//相对ipv4来说简单多了,自己看吧
		if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr))) {
			IP6_INC_STATS_BH(idev, IPSTATS_MIB_INHDRERRORS);
			goto drop;
		}
		hdr = ipv6_hdr(skb); //重新获取ip头
	}
	if (hdr->nexthdr == NEXTHDR_HOP) { //使用了扩展头逐个跳段选项
		if (ipv6_parse_hopopts(skb) < 0) {//处理这个选项
			IP6_INC_STATS_BH(idev, IPSTATS_MIB_INHDRERRORS);
			rcu_read_unlock();
			return 0;
		}
	}
	rcu_read_unlock();
	//进入ipv6的netfilter然后调用ip6_rcv_finish
	return NF_HOOK(PF_INET6,NF_IP6_PRE_ROUTING, skb, dev, NULL, ip6_rcv_finish);
err:
	IP6_INC_STATS_BH(idev, IPSTATS_MIB_INHDRERRORS);
drop:
	rcu_read_unlock();
	kfree_skb(skb);
out:
	return 0;
}

解析扩展头逐个跳段中的巨量负载选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
int ipv6_parse_hopopts(struct sk_buff *skb)
{
	struct inet6_skb_parm *opt = IP6CB(skb); //获取扩展头结果结构
	/* skb_network_header(skb) is equal to skb->data, and skb_network_header_len(skb) is always equal to
	 * sizeof(struct ipv6hdr) by definition of hop-by-hop options.
	 */
	//验证数据有足够的长度
	if (!pskb_may_pull(skb, sizeof(struct ipv6hdr) + 8) || !pskb_may_pull(skb, (sizeof(struct ipv6hdr) +
					//下面的意思是取得扩展首部中的长度
					((skb_transport_header(skb)[1] + 1) << 3)))) {
		kfree_skb(skb);
		return -1;
	}
	opt->hop = sizeof(struct ipv6hdr); //40字节
	if (ip6_parse_tlv(tlvprochopopt_lst, skb)) { //实际的解析工作
		//把传输头移动到扩展首部之后
		skb->transport_header += (skb_transport_header(skb)[1] + 1) << 3;
		opt = IP6CB(skb);
		opt->nhoff = sizeof(struct ipv6hdr); //进行了ipv6扩展头解析,保存下一个扩展头协议字段的偏移
		return 1;
	}
	return -1;
}

解析tlv编码的扩展选项头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
static int ip6_parse_tlv(struct tlvtype_proc *procs, struct sk_buff *skb)
{
	struct tlvtype_proc *curr;
	const unsigned char *nh = skb_network_header(skb); //获取网络头
	int off = skb_network_header_len(skb); //获取网络头长度
	int len = (skb_transport_header(skb)[1] + 1) << 3; //首部扩展头长度

	if (skb_transport_offset(skb) + len > skb_headlen(skb)) //长度错误
		goto bad;
	off += 2; //跳过下一个首部和首部扩展长度这两个字节
	len -= 2;

	while (len > 0) {
		int optlen = nh[off + 1] + 2; //获取选项数据长度 + 2 (2是选项类型和选项数据长度两字节)
		switch (nh[off]) { //选项类型
			case IPV6_TLV_PAD0: //Pad1选项
				optlen = 1;
				break;
			case IPV6_TLV_PADN://PadN选项
				break;
			default: //其他选项
				if (optlen > len)
					goto bad;

				for (curr = procs; curr->type >= 0; curr++) {
					if (curr->type == nh[off]) { //类型匹配,调用参数函数处理,参考下面ipv6选项处理
						/* type specific length/alignment checks will be performed in the func(). */
						if (curr->func(skb, off) == 0)
							return 0;
						break;
					}
				}
				if (curr->type < 0) {
					if (ip6_tlvopt_unknown(skb, off) == 0) //处理未知选项
						return 0;
				}
				break;
		}
		off += optlen; //偏移增加,这样到下一个选项
		len -= optlen; //长度递减
	}
	if (len == 0)
		return 1; //正确解析完毕
bad:
	kfree_skb(skb);
	return 0;
}

处理未知的选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
static int ip6_tlvopt_unknown(struct sk_buff *skb, int optoff)
{
	//根据选项类型标识符的要求进行处理
	switch ((skb_network_header(skb)[optoff] & 0xC0) >> 6) {
		case 0: /* ignore */
			return 1;
		case 1: /* drop packet */
			break;
		case 3: /* Send ICMP if not a multicast address and drop packet */
			/* Actually, it is redundant check. icmp_send will recheck in any case. */
			if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr)) //目的是多播地址
				break;
		case 2: /* send ICMP PARM PROB regardless and drop packet */
			//给包的源地址发送一个 ICMP "参数存在问题", 编码 2 的报文, 指针指向无法识别的选项类型
			icmpv6_param_prob(skb, ICMPV6_UNK_OPTION, optoff);
			return 0;
	}
	kfree_skb(skb);
	return 0;
}

到这需要解释一下,上面解析ipv6选项只是解析了第一层的扩展头,在后面可能还有其他扩展头会在后面解析。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
inline int ip6_rcv_finish( struct sk_buff *skb)
{
	if (skb->dst == NULL) //没有路由,进行路由查找
		ip6_route_input(skb); //路由部分将在路由实现文章中介绍

	return dst_input(skb);
}
static inline int dst_input(struct sk_buff *skb)
{
	int err;
	for (;;) {
		err = skb->dst->input(skb); //调用路由的输入函数
		if (likely(err == 0))
			return err;

		/* Oh, Jamal... Seems, I will not forgive you this mess. :-) */
		if (unlikely(err != NET_XMIT_BYPASS))
			return err;
	}
}

现在我们假设包是到本地的,那么上面的input函数就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
int ip6_input(struct sk_buff *skb)
{
	//进入ipv6 netfilter NF_IP6_LOCAL_IN hook 然后调用 ip6_input_finish
	return NF_HOOK(PF_INET6, NF_IP6_LOCAL_IN, skb, skb->dev, NULL, ip6_input_finish);
}
static int ip6_input_finish(struct sk_buff *skb)
{
	struct inet6_protocol *ipprot;
	struct sock *raw_sk;
	unsigned int nhoff;
	int nexthdr;
	u8 hash;
	struct inet6_dev *idev;

	/* Parse extension headers */
	rcu_read_lock();
resubmit:
	idev = ip6_dst_idev(skb->dst);
	//将skb->data指针移动到传输层头
	if (!pskb_pull(skb, skb_transport_offset(skb)))
		goto discard;

	nhoff = IP6CB(skb)->nhoff;
	nexthdr = skb_network_header(skb)[nhoff];//下一个扩展头协议

	//处理原始sock
	raw_sk = sk_head(&raw_v6_htable[nexthdr & (MAX_INET_PROTOS - 1)]);
	if (raw_sk && !ipv6_raw_deliver(skb, nexthdr))
		raw_sk = NULL;

	//向上层协议栈递交数据,看初始化时注册的一些协议,主要是tcp,udp等,还包括一些ip扩展头的处理
	hash = nexthdr & (MAX_INET_PROTOS - 1);
	if ((ipprot = rcu_dereference(inet6_protos[hash])) != NULL) {
		int ret;
		if (ipprot->flags & INET6_PROTO_FINAL) {
			struct ipv6hdr *hdr;
			/* Free reference early: we don't need it any more,                        
			   and it may hold ip_conntrack module loaded indefinitely. */
			nf_reset(skb);

			skb_postpull_rcsum(skb, skb_network_header(skb), skb_network_header_len(skb));
			hdr = ipv6_hdr(skb);
			if (ipv6_addr_is_multicast(&hdr->daddr) && !ipv6_chk_mcast_addr(skb->dev, &hdr->daddr, &hdr->saddr)
					&& !ipv6_is_mld(skb, nexthdr))
				goto discard;
		}
		//处理 IPSEC v6 的相关部分
		if (!(ipprot->flags & INET6_PROTO_NOPOLICY) && !xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb))
			goto discard;

		ret = ipprot->handler(skb); //上层协议处理,看下面ipv6扩展头处理
		if (ret > 0)
			goto resubmit; //重新处理
		else if (ret == 0)
			IP6_INC_STATS_BH(idev, IPSTATS_MIB_INDELIVERS);
	} else { //没有找到上层处理函数
		if (!raw_sk) {
			if (xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
				IP6_INC_STATS_BH(idev, IPSTATS_MIB_INUNKNOWNPROTOS);
				icmpv6_send(skb, ICMPV6_PARAMPROB, ICMPV6_UNK_NEXTHDR, nhoff, skb->dev);
			}
		} else
			IP6_INC_STATS_BH(idev, IPSTATS_MIB_INDELIVERS);
		kfree_skb(skb);
	}
	rcu_read_unlock();
	return 0;
discard:
	IP6_INC_STATS_BH(idev, IPSTATS_MIB_INDISCARDS);
	rcu_read_unlock();
	kfree_skb(skb);
	return 0;
}

ipv6选项处理

1
2
3
4
5
6
7
8
9
10
11
static struct tlvtype_proc tlvprochopopt_lst[] = {
	{
		.type   = IPV6_TLV_ROUTERALERT,
		.func   = ipv6_hop_ra,
	},
	{
		.type   = IPV6_TLV_JUMBO,
		.func   = ipv6_hop_jumbo,
	},
	{ -1, }
};

解析路由警告选项

1
2
3
4
5
6
7
8
9
10
11
12
static int ipv6_hop_ra(struct sk_buff *skb, int optoff)
{
	const unsigned char *nh = skb_network_header(skb); //获取网络头

	if (nh[optoff + 1] == 2) { //路由警告选项长度必须是2 ? rfc 要求是 4
		IP6CB(skb)->ra = optoff; //记录警告类型
		return 1;
	}
	LIMIT_NETDEBUG(KERN_DEBUG "ipv6_hop_ra: wrong RA length %d\n", nh[optoff + 1]);
	kfree_skb(skb);
	return 0;
}

解析jumbo frame选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
static int ipv6_hop_jumbo(struct sk_buff *skb, int optoff)
{
	const unsigned char *nh = skb_network_header(skb);
	u32 pkt_len;
	//选项数据长度必须是4,选项类型必须是 0xc2, &3 后必须是2
	if (nh[optoff + 1] != 4 || (optoff & 3) != 2) {
		LIMIT_NETDEBUG(KERN_DEBUG "ipv6_hop_jumbo: wrong jumbo opt length/alignment %d\n", nh[optoff+1]);
		IP6_INC_STATS_BH(ipv6_skb_idev(skb), IPSTATS_MIB_INHDRERRORS);
		goto drop;
	}
	pkt_len = ntohl(*(__be32 *)(nh + optoff + 2)); //获取整个负载长度
	if (pkt_len <= IPV6_MAXPLEN) { //小于65535 是不对地
		IP6_INC_STATS_BH(ipv6_skb_idev(skb), IPSTATS_MIB_INHDRERRORS);
		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff+2);
		return 0;
	}
	if (ipv6_hdr(skb)->payload_len) { //原ipv6头中就不应该有负载长度了
		IP6_INC_STATS_BH(ipv6_skb_idev(skb), IPSTATS_MIB_INHDRERRORS);
		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, optoff);
		return 0;
	}
	if (pkt_len > skb->len - sizeof(struct ipv6hdr)) { //长度超出了 skb 的实际长度
		IP6_INC_STATS_BH(ipv6_skb_idev(skb), IPSTATS_MIB_INTRUNCATEDPKTS);
		goto drop;
	}
	//如果必要试图缩减 skb 的长度
	if (pskb_trim_rcsum(skb, pkt_len + sizeof(struct ipv6hdr)))
		goto drop;

	return 1;
drop:
	kfree_skb(skb);
	return 0;
}

目的选项处理

1
2
3
4
5
6
7
8
9
static struct tlvtype_proc tlvprocdestopt_lst[] = {
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
	{
		.type   = IPV6_TLV_HAO,
		.func   = ipv6_dest_hao,
	},
#endif
	{-1,    NULL}
};

解析目的选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
static int ipv6_dest_hao(struct sk_buff *skb, int optoff)
{
	struct ipv6_destopt_hao *hao;
	struct inet6_skb_parm *opt = IP6CB(skb);
	struct ipv6hdr *ipv6h = ipv6_hdr(skb);
	struct in6_addr tmp_addr;
	int ret;

	if (opt->dsthao) { //已经处理
		LIMIT_NETDEBUG(KERN_DEBUG "hao duplicated\n");
		goto discard;
	}
	opt->dsthao = opt->dst1;
	opt->dst1 = 0;

	//获取网络头后面的选项部分
	hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + optoff);

	if (hao->length != 16) { //长度要求
		LIMIT_NETDEBUG(KERN_DEBUG "hao invalid option length = %d\n", hao->length);
		goto discard;
	}
	if (!(ipv6_addr_type(&hao->addr) & IPV6_ADDR_UNICAST)) { //地址不是单播
		LIMIT_NETDEBUG(KERN_DEBUG "hao is not an unicast addr: " NIP6_FMT "\n", NIP6(hao->addr));
		goto discard;
	}
	//IPSEC相关
	ret = xfrm6_input_addr(skb, (xfrm_address_t *)&ipv6h->daddr, (xfrm_address_t *)&hao->addr, IPPROTO_DSTOPTS);
	if (unlikely(ret < 0))
		goto discard;

	if (skb_cloned(skb)) { //如果包是cloned
		//分配新的内存数据
		if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC))
			goto discard;

		//重新指向各头
		hao = (struct ipv6_destopt_hao *)(skb_network_header(skb) + optoff);
		ipv6h = ipv6_hdr(skb);
	}
	if (skb->ip_summed == CHECKSUM_COMPLETE)
		skb->ip_summed = CHECKSUM_NONE;

	//把ip头中的源地址与选项中的地址交换
	ipv6_addr_copy(&tmp_addr, &ipv6h->saddr);
	ipv6_addr_copy(&ipv6h->saddr, &hao->addr);
	ipv6_addr_copy(&hao->addr, &tmp_addr);

	if (skb->tstamp.tv64 == 0)
		__net_timestamp(skb); //记录时间截

	return 1;
discard:
	kfree_skb(skb);
	return 0;
}

ipv6扩展头处理

我们只介绍根ipv6扩展头相关的实现,像其他的扩展头(tcp, udp)等虽然也是叫扩展头但实际是传输层的内容,将在其他文章中介绍。

路由扩展首部

1
2
3
4
5
6
7
8
struct ipv6_rt_hdr {
	__u8            nexthdr;
	__u8            hdrlen;
	__u8            type;
	__u8            segments_left;

	/* type specific data variable length field */
};

路由扩展首部处理结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
static struct inet6_protocol rthdr_protocol = {
	.handler        =       ipv6_rthdr_rcv,
	.flags          =       INET6_PROTO_NOPOLICY | INET6_PROTO_GSO_EXTHDR,
};
static int ipv6_rthdr_rcv(struct sk_buff *skb)
{
	struct inet6_skb_parm *opt = IP6CB(skb);
	struct in6_addr *addr = NULL;
	struct in6_addr daddr;
	struct inet6_dev *idev;
	int n, i;
	struct ipv6_rt_hdr *hdr;
	struct rt0_hdr *rthdr;
	int accept_source_route = ipv6_devconf.accept_source_route;

	idev = in6_dev_get(skb->dev); //包进入设备
	if (idev) {
		if (accept_source_route > idev->cnf.accept_source_route) //默认数量大于了手动调节(proc中)的数量
			accept_source_route = idev->cnf.accept_source_route;
		in6_dev_put(idev);
	}
	//skb长度和内存空间正确
	if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || !pskb_may_pull(skb, (skb_transport_offset(skb) +
					((skb_transport_header(skb)[1] + 1) << 3)))) {
		IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
		kfree_skb(skb);
		return -1;
	}
	hdr = (struct ipv6_rt_hdr *)skb_transport_header(skb); //路由扩展头
	//是到多播地址或硬件地址不是到本机的地址
	if (ipv6_addr_is_multicast(&ipv6_hdr(skb)->daddr) || skb->pkt_type != PACKET_HOST) {
		IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INADDRERRORS);
		kfree_skb(skb);
		return -1;
	}
looped_back:
	if (hdr->segments_left == 0) { //根据rfc要求 分段剩余为0
		switch (hdr->type) {
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
			case IPV6_SRCRT_TYPE_2:
				/* Silently discard type 2 header unless it was processed by own */
				if (!addr) {
					IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INADDRERRORS);
					kfree_skb(skb);
					return -1;
				}
				break;
#endif
			default:
				break;

		}
		opt->lastopt = opt->srcrt = skb_network_header_len(skb);
		skb->transport_header += (hdr->hdrlen + 1) << 3; //下一个传输头的位置
		opt->dst0 = opt->dst1;
		opt->dst1 = 0;
		opt->nhoff = (&hdr->nexthdr) - skb_network_header(skb); //记录下一个头数据相对网络头的偏移量
		return 1;
	}
	switch (hdr->type) {
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
		case IPV6_SRCRT_TYPE_2:
			if (accept_source_route < 0)
				goto unknown_rh;
			/* Silently discard invalid RTH type 2 */
			if (hdr->hdrlen != 2 || hdr->segments_left != 1) {
				IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
				kfree_skb(skb);
				return -1;
			}
			break;
#endif
		default:
			goto unknown_rh;
	}
	/* This is the routing header forwarding algorithm from RFC 2460, page 16. */

	n = hdr->hdrlen >> 1; //计算路由首部中的地址数量
	if (hdr->segments_left > n) {
		IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, ((&hdr->segments_left) - skb_network_header(skb)));
		return -1;
	}
	/* We are about to mangle packet header. Be careful!                                       
	   Do not damage packets queued somewhere.  */
	if (skb_cloned(skb)) {
		/* the copy is a forwarded packet */
		if (pskb_expand_head(skb, 0, 0, GFP_ATOMIC)) {
			IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_OUTDISCARDS);
			kfree_skb(skb);
			return -1;
		}
		hdr = (struct ipv6_rt_hdr *)skb_transport_header(skb);
	}
	if (skb->ip_summed == CHECKSUM_COMPLETE)
		skb->ip_summed = CHECKSUM_NONE;

	i = n - --hdr->segments_left; //计算地址向量(地址列表)中要"访问"的下一个地址

	rthdr = (struct rt0_hdr *) hdr;
	addr = rthdr->addr; //指向地址列表首部
	addr += i - 1; //移动到下一个地址

	switch (hdr->type) {
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
		case IPV6_SRCRT_TYPE_2:
			if (xfrm6_input_addr(skb, (xfrm_address_t *)addr, (xfrm_address_t *)&ipv6_hdr(skb)->saddr, IPPROTO_ROUTING) < 0) {
				IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INADDRERRORS);
				kfree_skb(skb);
				return -1;
			}
			if (!ipv6_chk_home_addr(addr)) {
				IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INADDRERRORS);
				kfree_skb(skb);
				return -1;
			}
			break;
#endif
		default:
			break;
	}
	if (ipv6_addr_is_multicast(addr)) { //这个地址是多播地址
		IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INADDRERRORS);
		kfree_skb(skb);
		return -1;
	}
	//交换 IPv6 目的地址和这个地址
	ipv6_addr_copy(&daddr, addr);
	ipv6_addr_copy(addr, &ipv6_hdr(skb)->daddr);
	ipv6_addr_copy(&ipv6_hdr(skb)->daddr, &daddr);
	dst_release(xchg(&skb->dst, NULL));

	ip6_route_input(skb); //路由查找处理,将在其他文章中介绍

	if (skb->dst->error) {
		skb_push(skb, skb->data - skb_network_header(skb));
		dst_input(skb);
		return -1;
	}

	if (skb->dst->dev->flags & IFF_LOOPBACK) { //路由查找后要发送到的目的设备是回环
		if (ipv6_hdr(skb)->hop_limit <= 1) { //跳数限制小于1
			IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
			//给源地址发送一个 ICMP "超时 – 传输超过跳数限制" 的报文, 并且抛弃此包
			icmpv6_send(skb, ICMPV6_TIME_EXCEED, ICMPV6_EXC_HOPLIMIT, 0, skb->dev);
			kfree_skb(skb);
			return -1;
		}
		ipv6_hdr(skb)->hop_limit--;
		goto looped_back;
	}
	//将data之中移动到网络头
	skb_push(skb, skb->data - skb_network_header(skb));
	dst_input(skb); //这时包应该被转发了
	return -1;
unknown_rh:
	IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
	icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, (&hdr->type) - skb_network_header(skb));
	return -1;
}

ipv6分配包扩展首部处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
static struct inet6_protocol frag_protocol =
{
	.handler        =       ipv6_frag_rcv,
	.flags          =       INET6_PROTO_NOPOLICY,
};
static int ipv6_frag_rcv(struct sk_buff *skb)
{
	struct frag_hdr *fhdr;
	struct frag_queue *fq;
	struct ipv6hdr *hdr = ipv6_hdr(skb);

	IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_REASMREQDS);

	/* Jumbo payload inhibits frag. header */
	if (hdr->payload_len == 0) { //是Jumbo payload,不是分片包
		IP6_INC_STATS(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb_network_header_len(skb));
		return -1;
	}
	//有碎片头空间
	if (!pskb_may_pull(skb, (skb_transport_offset(skb) + sizeof(struct frag_hdr)))) {
		IP6_INC_STATS(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, skb_network_header_len(skb));
		return -1;
	}
	hdr = ipv6_hdr(skb);
	fhdr = (struct frag_hdr *)skb_transport_header(skb); //分片头

	if (!(fhdr->frag_off & htons(0xFFF9))) { //没有分片偏移,不是分片包
		/* It is not a fragmented frame */
		skb->transport_header += sizeof(struct frag_hdr); //传输头向后移动到下一个头
		IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_REASMOKS);
		IP6CB(skb)->nhoff = (u8 *)fhdr - skb_network_header(skb);
		return 1;
	}
	if (atomic_read(&ip6_frags.mem) > ip6_frags_ctl.high_thresh) //内存使用超过限制
		ip6_evictor(ip6_dst_idev(skb->dst));

	//查找或创建分片队列头
	if ((fq = fq_find(fhdr->identification, &hdr->saddr, &hdr->daddr, ip6_dst_idev(skb->dst))) != NULL) {
		int ret;
		spin_lock(&fq->q.lock);
		ret = ip6_frag_queue(fq, skb, fhdr, IP6CB(skb)->nhoff); //入队重组
		spin_unlock(&fq->q.lock);
		fq_put(fq);
		return ret;
	}
	IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_REASMFAILS);
	kfree_skb(skb);
	return -1;
}
static __inline__ struct frag_queue * fq_find(__be32 id, struct in6_addr *src, struct in6_addr *dst, struct inet6_dev *idev)
{
	struct inet_frag_queue *q;
	struct ip6_create_arg arg;
	unsigned int hash;

	arg.id = id;
	arg.src = src;
	arg.dst = dst;
	hash = ip6qhashfn(id, src, dst); //id,源,目的进行 hash

	q = inet_frag_find(&ip6_frags, &arg, hash); //查找或创建
	if (q == NULL)
		goto oom;

	return container_of(q, struct frag_queue, q); //成功返回
oom: //没内存了
	IP6_INC_STATS_BH(idev, IPSTATS_MIB_REASMFAILS);
	return NULL;
}
struct inet_frag_queue *inet_frag_find(struct inet_frags *f, void *key, unsigned int hash)
{
	struct inet_frag_queue *q;
	struct hlist_node *n;

	read_lock(&f->lock);
	hlist_for_each_entry(q, n, &f->hash[hash], list) { //在hash桶中查找

		if (f->match(q, key)) { //调用匹配函数进行匹配,具体函数很简单参考初始化时的ipv6_frag_init函数
			atomic_inc(&q->refcnt);
			read_unlock(&f->lock);
			return q;
		}
	}
	//没有找到就创建一个
	return inet_frag_create(f, key, hash);
}

创建分片队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
static struct inet_frag_queue *inet_frag_create(struct inet_frags *f, void *arg, unsigned int hash)
{
	struct inet_frag_queue *q;

	q = inet_frag_alloc(f, arg); //分配一个
	if (q == NULL)
		return NULL;
	//添加到 hash 表
	return inet_frag_intern(q, f, hash, arg);
}
static struct inet_frag_queue *inet_frag_alloc(struct inet_frags *f, void *arg)
{
	struct inet_frag_queue *q;

	q = kzalloc(f->qsize, GFP_ATOMIC); //分配一个队列头,大小是 sizeof(struct frag_queue)
	if (q == NULL)
		return NULL;

	f->constructor(q, arg); //拷贝地址和 id 到队列头结构中
	atomic_add(f->qsize, &f->mem);
	setup_timer(&q->timer, f->frag_expire, (unsigned long)q);
	spin_lock_init(&q->lock);
	atomic_set(&q->refcnt, 1);
	return q;
}
static struct inet_frag_queue *inet_frag_intern(struct inet_frag_queue *qp_in, struct inet_frags *f, unsigned int hash, void *arg)
{
	struct inet_frag_queue *qp;
#ifdef CONFIG_SMP
	struct hlist_node *n;
#endif

	write_lock(&f->lock);
#ifdef CONFIG_SMP
	//其他cpu可能已经创建了一个,所以要再次检查
	hlist_for_each_entry(qp, n, &f->hash[hash], list) {
		if (f->match(qp, arg)) { //已经创建
			atomic_inc(&qp->refcnt);
			write_unlock(&f->lock);
			qp_in->last_in |= COMPLETE;
			inet_frag_put(qp_in, f); //释放新分配的
			return qp;

		}
	}
#endif
	qp = qp_in;
	if (!mod_timer(&qp->timer, jiffies + f->ctl->timeout)) //启动定时器
		atomic_inc(&qp->refcnt);

	//增加引用计数,然后添加到hash表
	atomic_inc(&qp->refcnt);
	hlist_add_head(&qp->list, &f->hash[hash]);
	list_add_tail(&qp->lru_list, &f->lru_list);
	f->nqueues++;
	write_unlock(&f->lock);
	return qp;
}

入队重组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
static int ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb, struct frag_hdr *fhdr, int nhoff)
{
	struct sk_buff *prev, *next;
	struct net_device *dev;
	int offset, end;

	if (fq->q.last_in & COMPLETE) //重组已经完成
		goto err;

	//分片开始位置
	offset = ntohs(fhdr->frag_off) & ~0x7;//偏移必须8字节对齐
	//分片在整个包中的结束位置 包负载长度 - 分片头长度
	end = offset + (ntohs(ipv6_hdr(skb)->payload_len) -  ((u8 *)(fhdr + 1) - (u8 *)(ipv6_hdr(skb) + 1)));

	//结束位置 > 65535
	if ((unsigned int)end > IPV6_MAXPLEN) {
		IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
		icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, ((u8 *)&fhdr->frag_off - skb_network_header(skb)));
		return -1;
	}
	//校验和已经完成
	if (skb->ip_summed == CHECKSUM_COMPLETE) {
		const unsigned char *nh = skb_network_header(skb);
		//减去分片包头的校验和
		skb->csum = csum_sub(skb->csum, csum_partial(nh, (u8 *)(fhdr + 1) - nh, 0));
	}
	//最后一个碎片包
	if (!(fhdr->frag_off & htons(IP6_MF))) {
		/* If we already have some bits beyond end or have different end, the segment is corrupted. */
		if (end < fq->q.len || ((fq->q.last_in & LAST_IN) && end != fq->q.len)) //分片出现错误
			goto err;

		fq->q.last_in |= LAST_IN; //标识最后一个分片
		fq->q.len = end; //记录包总长度
	} else {
		/* Check if the fragment is rounded to 8 bytes. Required by the RFC. */
		if (end & 0x7) { //碎片结尾也需要8字节对齐
			/* RFC2460 says always send parameter problem in this case. -DaveM */
			IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), PSTATS_MIB_INHDRERRORS);
			icmpv6_param_prob(skb, ICMPV6_HDR_FIELD, offsetof(struct ipv6hdr, payload_len));
			return -1;
		}
		if (end > fq->q.len) {
			/* Some bits beyond end -> corruption. */
			if (fq->q.last_in & LAST_IN)
				goto err;
			fq->q.len = end; //记录已经得到的碎片的最大长度
		}
	}
	if (end == offset) //开始 = 结束
		goto err;

	//skb->data 指向碎片首部头后数据部分
	if (!pskb_pull(skb, (u8 *) (fhdr + 1) - skb->data))
		goto err;
	//如果需要缩短skb的内存长度
	if (pskb_trim_rcsum(skb, end - offset))
		goto err;

	//找出碎片所在位置
	prev = NULL;
	for(next = fq->q.fragments; next != NULL; next = next->next) {
		if (FRAG6_CB(next)->offset >= offset)
			break;  /* bingo! */
		prev = next;
	}
	if (prev) { //有前一个碎片
		//前一个碎片 (开始 + 长度) - 这个碎片的开始. 计算出重叠部分
		int i = (FRAG6_CB(prev)->offset + prev->len) - offset;
		if (i > 0) { //有重叠
			offset += i; //调整这个碎片的开始位置
			if (end <= offset) //调整后出错
				goto err;
			if (!pskb_pull(skb, i))//skb->data += i;
				goto err;
			if (skb->ip_summed != CHECKSUM_UNNECESSARY)
				skb->ip_summed = CHECKSUM_NONE;
		}
	}
	//有下一个碎片,且开始位置 < 这个碎片的结束位置
	while (next && FRAG6_CB(next)->offset < end) {
		//这个碎片的结束位置  - 下一个碎片的开始位置,计算重叠
		int i = end - FRAG6_CB(next)->offset; /* overlap is 'i' bytes */
		if (i < next->len) { //重叠长度 < 下一个碎片的长度
			if (!pskb_pull(next, i)) //next->data += i;
				goto err;

			FRAG6_CB(next)->offset += i;    //下一个碎片开始位置调整
			fq->q.meat -= i; //总长度减少
			if (next->ip_summed != CHECKSUM_UNNECESSARY)
				next->ip_summed = CHECKSUM_NONE;
			break;

		} else { //这个碎片完全复盖了下一个碎片
			struct sk_buff *free_it = next; //释放这个碎片
			next = next->next;//调整下一个碎片指针
			//调整队列指针
			if (prev)
				prev->next = next;
			else
				fq->q.fragments = next;

			fq->q.meat -= free_it->len;
			frag_kfree_skb(free_it, NULL); //释放被复盖的包
		}
	}
	FRAG6_CB(skb)->offset = offset; //这个碎片包记录自己的开始位置

	//插入这个碎片到队列
	skb->next = next;
	if (prev)
		prev->next = skb;
	else
		fq->q.fragments = skb;

	dev = skb->dev;
	if (dev) {
		fq->iif = dev->ifindex;
		skb->dev = NULL;
	}
	fq->q.stamp = skb->tstamp;
	fq->q.meat += skb->len; //累加总长度
	atomic_add(skb->truesize, &ip6_frags.mem);

	if (offset == 0) { //偏移为0
		fq->nhoffset = nhoff;
		fq->q.last_in |= FIRST_IN; //标识开始碎片
	}
	//碎片已经聚齐,记录长度 = 包中标识的长度
	if (fq->q.last_in == (FIRST_IN | LAST_IN) && fq->q.meat == fq->q.len)
		return ip6_frag_reasm(fq, prev, dev); //重组
	//没有聚齐,移动队列连表到lru连表尾部
	write_lock(&ip6_frags.lock);
	list_move_tail(&fq->q.lru_list, &ip6_frags.lru_list);
	write_unlock(&ip6_frags.lock);
	return -1;
err:
	IP6_INC_STATS(ip6_dst_idev(skb->dst), IPSTATS_MIB_REASMFAILS);
	kfree_skb(skb);
	return -1;
}

重组ip头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
static int ip6_frag_reasm(struct frag_queue *fq, struct sk_buff *prev, struct net_device *dev)
{
	struct sk_buff *fp, *head = fq->q.fragments;
	int    payload_len;
	unsigned int nhoff;

	fq_kill(fq); //把这个重组队列出队

	/* Make the one we just received the head. */
	if (prev) {
		//下面是把head指向的skb复制到fp,然后把fp插入到head指向的位置
		head = prev->next;
		fp = skb_clone(head, GFP_ATOMIC);

		if (!fp)
			goto out_oom;


		fp->next = head->next;
		prev->next = fp;
		//把真正的头skb复制到head指针的skb
		skb_morph(head, fq->q.fragments);
		head->next = fq->q.fragments->next;

		kfree_skb(fq->q.fragments);//释放原来的头
		fq->q.fragments = head;
	}
	/* Unfragmented part is taken from the first segment. */
	//计算负载总长度
	payload_len = ((head->data - skb_network_header(head)) - sizeof(struct ipv6hdr) + fq->q.len -  sizeof(struct frag_hdr));
	if (payload_len > IPV6_MAXPLEN) //超过65535
		goto out_oversize;

	/* Head of list must not be cloned. */
	//如果skb被克隆,从新分配他的data
	if (skb_cloned(head) && pskb_expand_head(head, 0, 0, GFP_ATOMIC))
		goto out_oom;

	/* If the first fragment is fragmented itself, we split it to two chunks: the first with data and paged part
	 * and the second, holding only fragments.
	 */
	if (skb_shinfo(head)->frag_list) {//如果头自己已经被分片
		struct sk_buff *clone;
		int i, plen = 0;

		if ((clone = alloc_skb(0, GFP_ATOMIC)) == NULL)
			goto out_oom;

		//把这个clone插入到头后               
		clone->next = head->next;
		head->next = clone;
		//把头的分片给这个clone
		skb_shinfo(clone)->frag_list = skb_shinfo(head)->frag_list;
		skb_shinfo(head)->frag_list = NULL;
		//头使用了页面,计算总长度
		for (i = 0; i < skb_shinfo(head)->nr_frags; i++)
			plen += skb_shinfo(head)->frags[i].size;

		clone->len = clone->data_len = head->data_len - plen;
		head->data_len -= clone->len;
		head->len -= clone->len;
		clone->csum = 0;
		clone->ip_summed = head->ip_summed;
		atomic_add(clone->truesize, &ip6_frags.mem);
	}
	/* We have to remove fragment header from datagram and to relocate                         
	 * header in order to calculate ICV correctly. */
	nhoff = fq->nhoffset;
	//把传输头(分片头)中的下一个头字段值赋给网络头中的下一个头字段
	skb_network_header(head)[nhoff] = skb_transport_header(head)[0];
	//把分片首部复盖掉
	memmove(head->head + sizeof(struct frag_hdr), head->head, (head->data - head->head) - sizeof(struct frag_hdr));
	//调整相应的各个层的头位置
	head->mac_header += sizeof(struct frag_hdr);
	head->network_header += sizeof(struct frag_hdr);

	skb_shinfo(head)->frag_list = head->next; //保存碎片连表
	skb_reset_transport_header(head);//重新调整网络头,现在指向分片头后的头
	skb_push(head, head->data - skb_network_header(head));//使head->data指向网络头
	atomic_sub(head->truesize, &ip6_frags.mem);

	for (fp = head->next; fp; fp = fp->next) { //统计分片总长度
		head->data_len += fp->len;
		head->len += fp->len;
		if (head->ip_summed != fp->ip_summed)
			head->ip_summed = CHECKSUM_NONE;
		else if (head->ip_summed == CHECKSUM_COMPLETE)
			head->csum = csum_add(head->csum, fp->csum); //添加各分片的累加和

		head->truesize += fp->truesize;
		atomic_sub(fp->truesize, &ip6_frags.mem);
	}
	head->next = NULL;
	head->dev = dev;
	head->tstamp = fq->q.stamp;
	ipv6_hdr(head)->payload_len = htons(payload_len); //总长度
	IP6CB(head)->nhoff = nhoff;

	/* Yes, and fold redundant checksum back. 8) */
	if (head->ip_summed == CHECKSUM_COMPLETE) //添加网络头累加和
		head->csum = csum_partial(skb_network_header(head), skb_network_header_len(head), head->csum);

	rcu_read_lock();
	IP6_INC_STATS_BH(__in6_dev_get(dev), IPSTATS_MIB_REASMOKS);
	rcu_read_unlock();
	fq->q.fragments = NULL;
	return 1;
	...... //下面是错误处理
}

无数据扩展头

1
2
3
4
5
6
7
8
9
static struct inet6_protocol nodata_protocol = {
	.handler        =       ipv6_nodata_rcv,
	.flags          =       INET6_PROTO_NOPOLICY,
};
static int ipv6_nodata_rcv(struct sk_buff *skb)
{
	kfree_skb(skb);
	return 0;
}

目的选项首部处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static struct inet6_protocol destopt_protocol = {
	.handler        =       ipv6_destopt_rcv,
	.flags          =       INET6_PROTO_NOPOLICY | INET6_PROTO_GSO_EXTHDR,
};
static int ipv6_destopt_rcv(struct sk_buff *skb)
{
	struct inet6_skb_parm *opt = IP6CB(skb);
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
	__u16 dstbuf;
#endif
	struct dst_entry *dst;
	//长度验证
	if (!pskb_may_pull(skb, skb_transport_offset(skb) + 8) || !pskb_may_pull(skb, (skb_transport_offset(skb) +
					((skb_transport_header(skb)[1] + 1) << 3)))) {
		IP6_INC_STATS_BH(ip6_dst_idev(skb->dst), IPSTATS_MIB_INHDRERRORS);
		kfree_skb(skb);
		return -1;
	}
	opt->lastopt = opt->dst1 = skb_network_header_len(skb); //网络头长度
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
	dstbuf = opt->dst1;
#endif
	dst = dst_clone(skb->dst); //增加dst的引用计数
	//解析tlv,上面已经看到过了
	if (ip6_parse_tlv(tlvprocdestopt_lst, skb)) {
		dst_release(dst);
		skb->transport_header += (skb_transport_header(skb)[1] + 1) << 3; //调整网络头位置
		opt = IP6CB(skb);
#if defined(CONFIG_IPV6_MIP6) || defined(CONFIG_IPV6_MIP6_MODULE)
		opt->nhoff = dstbuf;
#else
		opt->nhoff = opt->dst1;
#endif
		return 1;
	}
	IP6_INC_STATS_BH(ip6_dst_idev(dst), IPSTATS_MIB_INHDRERRORS);
	dst_release(dst);
	return -1;
}

linux内核中tcp连接的断开处理

http://simohayha.iteye.com/blog/503856

我们这次主要来分析相关的两个断开函数close和shotdown以及相关的套接口选项SO_LINGER。这里要注意SO_LINGER对shutdown无任何影响。它只对close起作用。

先来坎SO_LINGER所对应的数据结构:

1
2
3
4
5
6
struct linger {
	//linger的开关
	int     l_onoff;    /* Linger active        */
	//所等待的时间。
	int     l_linger;   /* How long to linger for   */
};

这里对这个套接口选项就不详细介绍了,在unix网络编程中有详细的介绍,我们这里只会分析内核的处理代码。

首先来看close函数,我们知道缺醒情况下,close是立即返回,但是如果套接口的发送缓冲区还有未发送的数据,系统将会试着把这些数据发送给对端。而这个缺醒情况我们是可以通过SO_LINGER来改变的。还有一个要注意就是close调用并不一定会引发tcp的断开连接。因为close只是将这个socket的引用计数减一(主要是针对多个进程),而真正要直接引发断开,则需要用shutdown函数。

内核中socket的close的系统调用是sock_close,而在sock_close中,直接调用sock_release来实现功能,因此这里我们直接看sock_release的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void sock_release(struct socket *sock)
{
	if (sock->ops) {
		struct module *owner = sock->ops->owner;

		//调用inet_stream_ops的inet_release函数
		sock->ops->release(sock);
		//将ops致空。
		sock->ops = NULL;
		module_put(owner);
	}

	//这个域貌似是26.31新加的,具体做什么的还不知道。
	if (sock->fasync_list)
		printk(KERN_ERR "sock_release: fasync list not empty!\n");

	//更新全局的socket数目
	percpu_sub(sockets_in_use, 1);
	if (!sock->file) {
		//更新inode的引用计数
		iput(SOCK_INODE(sock));
		return;
	}
	sock->file = NULL;
}

然后来看inet_release的实现,这个函数主要用来通过SO_LINGER套接字来得到超时时间,然后调用tcp_close来关闭sock。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int inet_release(struct socket *sock)
{
	struct sock *sk = sock->sk;

	if (sk) {
		long timeout;

		/* Applications forget to leave groups before exiting */
		ip_mc_drop_socket(sk);

		timeout = 0;
		//判断是否设置SO_LINGER并且不是处于正在shutdowning,则设置timeout为l_linger(也就是我们设置的值).
		if (sock_flag(sk, SOCK_LINGER) &&
			!(current->flags & PF_EXITING))
			timeout = sk->sk_lingertime;
		sock->sk = NULL;
		//调用tcp_close.
		sk->sk_prot->close(sk, timeout);
	}
	return 0;
}

tcp_close函数比较长我们这里分段来分析它,首先来看第一部分。这里要注意几点:

1 当close掉一个服务端的父socket的时候,内核会先处理半连接队列然后是已经accept了的队列,最后才会处理父sock。

2 处理接收缓冲区的数据的时候,直接遍历receive_queue(前面blog有介绍),然后统计未发送的socket。我们知道close是不管接收buf的,也就是他会把接收buf释放掉,然后发送rst给对端的。

3 当so_linger有设置并且超时时间为0,则发送rst给对端,并且清空发送和接收buf。这个也不会引起最终的四分组终止序列。

4 当接收缓冲区有未读数据,则直接发送rst给对端。这个也不会引起最终的四分组终止序列。

5 当so_linger有设置,并且超时不为0,或者so_linger没有设置,此时都会引起最终的四分组终止序列来终止连接。(通过send_fin来发送fin,并引发四分组终止序列).而在send_fin中会发送掉发送缓冲区中的数据。

来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
void tcp_close(struct sock *sk, long timeout)
{
	struct sk_buff *skb;
	int data_was_unread = 0;
	int state;

	lock_sock(sk);
	sk->sk_shutdown = SHUTDOWN_MASK;

	//如果处于tcp_listen说明将要关闭的这个socket是一个服务端的主socket。
	if (sk->sk_state == TCP_LISTEN) {
		//设置sock状态.
		tcp_set_state(sk, TCP_CLOSE);

		//这个函数主要用来清理半连接队列(下面会简要分析这个函数)
		/* Special case. */
		inet_csk_listen_stop(sk);
		//处理要关闭的sock
		goto adjudge_to_death;
	}

	//遍历sk_receive_queue也就是输入buf队列。然后统计还没有读取的数据。
	while ((skb = __skb_dequeue(&sk->sk_receive_queue)) != NULL) {
		u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq -
			  tcp_hdr(skb)->fin;
		data_was_unread += len;
		//free这个skb
		__kfree_skb(skb);
	}

	sk_mem_reclaim(sk);


	//第一个if主要是实现了rfc2525的2.17,也就是关闭的时候,如果接收buf中有未读数据,则发送一个rst给对端。(下面有摘抄相关内容)
	if (data_was_unread) {
		/* Unread data was tossed, zap the connection. */
		NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONCLOSE);
		//设置状态
		tcp_set_state(sk, TCP_CLOSE);
		//发送rst
		tcp_send_active_reset(sk, GFP_KERNEL);
	}
	//第二个if主要是判断so_linger套接字,并且超时时间为0。此时我们就直接丢掉所有的发送缓冲区中的数据
	else if (sock_flag(sk, SOCK_LINGER) && !sk->sk_lingertime) {
		/* Check zero linger _after_ checking for unread data. */
		//调用tcp_disconnect,这个函数主要用来断开和对端的连接,这个函数下面会介绍。
		sk->sk_prot->disconnect(sk, 0);
		NET_INC_STATS_USER(sock_net(sk), LINUX_MIB_TCPABORTONDATA);
	}
	//这个函数主要用来判断是否需要发送fin,也就是判断状态。下面我会详细介绍这个函数。
	else if (tcp_close_state(sk)) {

		//发送fin.
		tcp_send_fin(sk);
	}

	//等待一段时间。这里的timeout,如果有设置so_linger的话就是l_linger.这里主要是等待发送缓冲区的buf发送(如果超时时间不为0).
	sk_stream_wait_close(sk, timeout);
	........................

}
rfc2525的2.17的介绍:
1
2
3
When an application closes a connection in such a way that it can no longer read any received data, 
the TCP SHOULD, per section 4.2.2.13 of RFC 1122, send a RST if there is any unread received data, 
or if any new data is received. A TCP that fails to do so exhibits "Failure to RST on close with data pending".

ok,现在来看上面遇到的3个函数,一个是inet_csk_listen_stop,一个是tcp_close_state,一个是tcp_disconnect.我们一个个来看他们。

首先是inet_csk_listen_stop函数。我们知道这个函数主要用来清理所有的半连接队列。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
void inet_csk_listen_stop(struct sock *sk)
{
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct request_sock *acc_req;
	struct request_sock *req;

	//首先删除keepalive定时器。
	inet_csk_delete_keepalive_timer(sk);

	/* make all the listen_opt local to us */
	//得到accept 队列。
	acc_req = reqsk_queue_yank_acceptq(&icsk->icsk_accept_queue);

	//然后销毁掉所有的半连接队列,也就是listen_sock队列
	reqsk_queue_destroy(&icsk->icsk_accept_queue);


	//遍历accept队列断开与对端的连接。
	while ((req = acc_req) != NULL) {
	...............................................

		//调用tcp_disconnect来断开与对端的连接。这里注意是非阻塞的。
		sk->sk_prot->disconnect(child, O_NONBLOCK);

		sock_orphan(child);

		percpu_counter_inc(sk->sk_prot->orphan_count);

		//销毁这个sock。
		inet_csk_destroy_sock(child);

		........................................
	}
	WARN_ON(sk->sk_ack_backlog);
}

接下来来看tcp_disconnect函数。这个函数主要用来断开和对端的连接.它会释放读写队列,发送rst,清除定时器等等一系列操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
int tcp_disconnect(struct sock *sk, int flags)
{
	struct inet_sock *inet = inet_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);
	struct tcp_sock *tp = tcp_sk(sk);
	int err = 0;
	int old_state = sk->sk_state;

	if (old_state != TCP_CLOSE)
		tcp_set_state(sk, TCP_CLOSE);
	...................

	//清除定时器,重传,delack等。
	tcp_clear_xmit_timers(sk);
	//直接free掉接收buf。
	__skb_queue_purge(&sk->sk_receive_queue);
	//free掉写buf。
	tcp_write_queue_purge(sk);
	__skb_queue_purge(&tp->out_of_order_queue);
#ifdef CONFIG_NET_DMA
	__skb_queue_purge(&sk->sk_async_wait_queue);
#endif

	inet->dport = 0;

	if (!(sk->sk_userlocks & SOCK_BINDADDR_LOCK))
		inet_reset_saddr(sk);
		..........................................
	//设置状态。
	tcp_set_ca_state(sk, TCP_CA_Open);
	//清理掉重传的一些标记
	tcp_clear_retrans(tp);
	inet_csk_delack_init(sk);
	tcp_init_send_head(sk);
	memset(&tp->rx_opt, 0, sizeof(tp->rx_opt));
	__sk_dst_reset(sk);

	WARN_ON(inet->num && !icsk->icsk_bind_hash);

	sk->sk_error_report(sk);
	return err;
}

紧接着是tcp_close_state函数这个函数就是用来判断是否应该发送fin:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//这个数组表示了当close后,tcp的状态变化,可以看到注释很清楚,包含了3部分。这里也就是通过current也就是tcp的状态取得new state也就是close的状态,然后再和TCP_ACTION_FIN按位于,得到action
static const unsigned char new_state[16] = {
  /* current state:        new state:      action:  */
  /* (Invalid)      */ TCP_CLOSE,
  /* TCP_ESTABLISHED    */ TCP_FIN_WAIT1 | TCP_ACTION_FIN,
  /* TCP_SYN_SENT   */ TCP_CLOSE,
  /* TCP_SYN_RECV   */ TCP_FIN_WAIT1 | TCP_ACTION_FIN,
  /* TCP_FIN_WAIT1  */ TCP_FIN_WAIT1,
  /* TCP_FIN_WAIT2  */ TCP_FIN_WAIT2,
  /* TCP_TIME_WAIT  */ TCP_CLOSE,
  /* TCP_CLOSE      */ TCP_CLOSE,
  /* TCP_CLOSE_WAIT */ TCP_LAST_ACK  | TCP_ACTION_FIN,
  /* TCP_LAST_ACK   */ TCP_LAST_ACK,
  /* TCP_LISTEN     */ TCP_CLOSE,
  /* TCP_CLOSING    */ TCP_CLOSING,
};

static int tcp_close_state(struct sock *sk)
{
	//取得new state
	int next = (int)new_state[sk->sk_state];
	int ns = next & TCP_STATE_MASK;

	tcp_set_state(sk, ns);

	//得到action
	return next & TCP_ACTION_FIN;
}

接下来来看tcp_close的剩余部分的代码,剩下的部分就是处理一些状态以及通知这里只有一个要注意的就是TCP_LINGER2这个套接字,这个套接字能够设置等待fin的超时时间,也就是tcp_sock的域linger2.我们知道系统还有一个sysctl_tcp_fin_timeout,也就是提供了一个sys文件系统的接口来修改这个值,不过我们如果设置linger2为一个大于0的值的话,内核就会取linger2这个值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
adjudge_to_death:

	//得到sock的状态。
	state = sk->sk_state;
	sock_hold(sk);
	sock_orphan(sk);

	//唤醒阻塞在这个sock的队列(前面有详细介绍这个函数)
	release_sock(sk);

	local_bh_disable();
	bh_lock_sock(sk);
	WARN_ON(sock_owned_by_user(sk));

	//全局的cpu变量引用计数减一。
	percpu_counter_inc(sk->sk_prot->orphan_count);

	/* Have we already been destroyed by a softirq or backlog? */
	if (state != TCP_CLOSE && sk->sk_state == TCP_CLOSE)
		goto out;

	//如果状态为TCP_FIN_WAIT2,说明接收了ack,在等待对端的fin。
	if (sk->sk_state == TCP_FIN_WAIT2) {
		struct tcp_sock *tp = tcp_sk(sk);
		//超时时间小于0,则说明马上超时,设置状态为tcp_close,然后发送rst给对端。
		if (tp->linger2 < 0) {
			tcp_set_state(sk, TCP_CLOSE);
			tcp_send_active_reset(sk, GFP_ATOMIC);
			NET_INC_STATS_BH(sock_net(sk),
					LINUX_MIB_TCPABORTONLINGER);
		} else {
			//得到等待fin的超时时间。这里主要也就是在linger2和sysctl_tcp_fin_timeout中来取得。
			const int tmo = tcp_fin_time(sk);
			//如果超时时间太长,则启动keepalive定时器发送探测报。
			if (tmo > TCP_TIMEWAIT_LEN) {
				inet_csk_reset_keepalive_timer(sk,
						tmo - TCP_TIMEWAIT_LEN);
			} else {
				//否则进入time_wait状态。
				tcp_time_wait(sk, TCP_FIN_WAIT2, tmo);
				goto out;
			}
		}
	}
	......................................

	//如果sk的状态为tcp_close则destroy掉这个sk
	if (sk->sk_state == TCP_CLOSE)
		inet_csk_destroy_sock(sk);
	/* Otherwise, socket is reprieved until protocol close. */

out:
	bh_unlock_sock(sk);
	local_bh_enable();
	sock_put(sk);
}

然后来看send_fin的实现,这个函数用来发送一个fin,并且尽量发送完发送缓冲区中的数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void tcp_send_fin(struct sock *sk)
{
	struct tcp_sock *tp = tcp_sk(sk);
	//取得写bufer的尾部。
	struct sk_buff *skb = tcp_write_queue_tail(sk);
	int mss_now;

	/* Optimization, tack on the FIN if we have a queue of
	 * unsent frames.  But be careful about outgoing SACKS
	 * and IP options.
	 */
	mss_now = tcp_current_mss(sk);
	//如果发送队列不为空,此时我们只需要设置sk buffer的标记位(也就是tcp报文的控制位为fin),可以看到我们是加到写buffer的尾部,这里是为了能尽量将写buffer中的数据全部传出)
	if (tcp_send_head(sk) != NULL) {
		TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;
		TCP_SKB_CB(skb)->end_seq++;
		tp->write_seq++;
	} else {
	..................................
		//到这里标明发送缓冲区位空,因此我们需要新建一个sk buffer,然后设置标记位,并加入到写buffer。
		skb_reserve(skb, MAX_TCP_HEADER);
		/* FIN eats a sequence byte, write_seq advanced by tcp_queue_skb(). */
		tcp_init_nondata_skb(skb, tp->write_seq,
					 TCPCB_FLAG_ACK | TCPCB_FLAG_FIN);
		tcp_queue_skb(sk, skb);
	}
	//发送写缓冲区中的数据。
	__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_OFF);
}
void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss,
				   int nonagle)
{
	struct sk_buff *skb = tcp_send_head(sk);

	if (!skb)
		return;

	/* If we are closed, the bytes will have to remain here.
	 * In time closedown will finish, we empty the write queue and
	 * all will be happy.
	 */
	if (unlikely(sk->sk_state == TCP_CLOSE))
		return;
	//发送数据,这里关闭了nagle。也就是立即将数据全部发送出去(我前面的blog有详细解释这个函数).
	if (tcp_write_xmit(sk, cur_mss, nonagle, 0, GFP_ATOMIC))
		tcp_check_probe_timer(sk);
}

接下来来看shutdown的实现。在2.26.31中,系统调用的实现有些变化。

这里我们要知道shutdown会将写缓冲区的数据发出,然后唤醒阻塞的进程,来读取读缓冲区中的数据。

这个系统调用所对应的内核函数就是os_shutdown_socket。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#define SHUT_RD 0
#define SHUT_WR 1
#define SHUT_RDWR 2

int os_shutdown_socket(int fd, int r, int w)
{
	int what, err;

	if (r && w)
		what = SHUT_RDWR;
	else if (r)
		what = SHUT_RD;
	else if (w)
		what = SHUT_WR;
	else
		return -EINVAL;

	//调用socket的shutdown也就是kernel_sock_shutdown
	err = shutdown(fd, what);
	if (err < 0)
		return -errno;
	return 0;
}


int kernel_sock_shutdown(struct socket *sock, enum sock_shutdown_cmd how)
{
	//他最终会调用inet_shutdown
	return sock->ops->shutdown(sock, how);
}

来看inet_shutdown的实现.这个函数的主要工作就是通过判断sock的状态不同来调用相关的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
int inet_shutdown(struct socket *sock, int how)
{
	struct sock *sk = sock->sk;
	int err = 0;

	/* This should really check to make sure
	 * the socket is a TCP socket. (WHY AC...)
	 */
	//这里要注意每个how都是加1的,这说明在内核里读写是为1,2,3
	how++; /* maps 0->1 has the advantage of making bit 1 rcvs and
			   1->2 bit 2 snds.
			   2->3 */
	//判断how的合法性。
	if ((how & ~SHUTDOWN_MASK) || !how) /* MAXINT->0 */
		return -EINVAL;
	//锁住sock
	lock_sock(sk);

	//SS_CONNECTING说明这个sock的连接正在处理中。state域表示socket当前的内部状态
	if (sock->state == SS_CONNECTING) {
		//如果状态为这几个状态,说明是处于半连接处理阶段,此时设置状态为SS_DISCONNECTING
		if ((1 << sk->sk_state) &
			(TCPF_SYN_SENT | TCPF_SYN_RECV | TCPF_CLOSE))
			sock->state = SS_DISCONNECTING;
		else
			//否则设置为连接完毕
			sock->state = SS_CONNECTED;
	}

	//除过TCP_LISTEN以及TCP_SYN_SENT状态外的其他状态最终都会进入sk->sk_prot->shutdown也就是tcp_shutdown函数。

	switch (sk->sk_state) {
	//如果状态为tco_close则设置错误号,然后进入default处理
	case TCP_CLOSE:
		err = -ENOTCONN;
		/* Hack to wake up other listeners, who can poll for
		   POLLHUP, even on eg. unconnected UDP sockets -- RR */
	default:
		sk->sk_shutdown |= how;
		if (sk->sk_prot->shutdown)
			sk->sk_prot->shutdown(sk, how);
		break;

	/* Remaining two branches are temporary solution for missing
	 * close() in multithreaded environment. It is _not_ a good idea,
	 * but we have no choice until close() is repaired at VFS level.
	 */
	case TCP_LISTEN:
		//如果不为SHUT_RD则跳出switch,否则进入tcp_syn_sent的处理。
		if (!(how & RCV_SHUTDOWN))
			break;
		/* Fall through */
	case TCP_SYN_SENT:
		//断开连接,然后设置state
		err = sk->sk_prot->disconnect(sk, O_NONBLOCK);
		sock->state = err ? SS_DISCONNECTING : SS_UNCONNECTED;
		break;
	}

	/* Wake up anyone sleeping in poll. */
	//唤醒阻塞在这个socket上的进程,这里是为了将读缓冲区的数据尽量读完。
	sk->sk_state_change(sk);
	release_sock(sk);
	return err;
}

来看tcp_shutdown函数。

这里要注意,当只关闭读的话,并不会引起发送fin,也就是只会设置个标记,然后在读取数据的时候返回错误。而关闭写端,则就会引起发送fin。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
void tcp_shutdown(struct sock *sk, int how)
{
	/*  We need to grab some memory, and put together a FIN,
	 *  and then put it into the queue to be sent.
	 *      Tim MacKenzie(tym@dibbler.cs.monash.edu.au) 4 Dec '92.
	 */
	//如果为SHUT_RD则直接返回。
	if (!(how & SEND_SHUTDOWN))
		return;

	/* If we've already sent a FIN, or it's a closed state, skip this. */
	//这里英文注释很详细我就不多解释了。
	if ((1 << sk->sk_state) &
		(TCPF_ESTABLISHED | TCPF_SYN_SENT |
		 TCPF_SYN_RECV | TCPF_CLOSE_WAIT)) {
		/* Clear out any half completed packets.  FIN if needed. */
		//和tcp_close那边处理一样
		if (tcp_close_state(sk))
			tcp_send_fin(sk);
	}
}

最后来看sock_def_readable它就是sk->sk_state_change。也就是用来唤醒阻塞的进程。

1
2
3
4
5
6
7
8
9
10
11
12
13
static void sock_def_readable(struct sock *sk, int len)
{
	read_lock(&sk->sk_callback_lock);
	//判断是否有进程在等待这个sk
	if (sk_has_sleeper(sk))
	//有的话,唤醒进程,这里可以看到递交给上层的是POLLIN,也就是读事件。
	wake_up_interruptible_sync_poll(sk->sk_sleep, POLLIN |
						POLLRDNORM | POLLRDBAND);

	//这里异步唤醒,可以看到这里也是POLL_IN.
	sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
	read_unlock(&sk->sk_callback_lock);
}

可以看到shutdown函数只会处理SEND_SHUTDOWN。并且当调用shutdown之后,读缓冲区,还可以继续读取。