kk Blog —— 通用基础


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

拥塞避免处理函数 tcp_reno_cong_avoid

http://blog.csdn.net/shanshanpt/article/details/22201847

慢启动和快速重传拥塞避免算法,函数tcp_reno_cong_avoid
在“慢开始”阶段,每收到一个ACK,cwnd++一次,那么一个RTT之后,cwnd就会加倍
拥塞避免阶段,其实就是在一个RTT时间内将cwnd++一次( 注意在不丢包的情况下 )

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
/*
 * TCP Reno congestion control
 * This is special case used for fallback as well.
 */
/* This is Jacobson's slow start and congestion avoidance.
 * SIGCOMM '88, p. 328.
 */
void tcp_reno_cong_avoid(struct sock *sk, u32 ack, u32 in_flight)
{
	struct tcp_sock *tp = tcp_sk(sk);         // 获取tcp_sock
	// 函数返回1说明拥塞窗口被限制,我们需要增加拥塞窗口,否则的话,就不需要增加拥塞窗口。
	if (!tcp_is_cwnd_limited(sk, in_flight))  // 是否已经达到拥塞窗口的限制值(1)
		return;

	/* In "safe" area, increase. */
	if (tp->snd_cwnd <= tp->snd_ssthresh)     // 如果发送窗口大小还 比 慢开始门限小,那么还是慢开始处理
		tcp_slow_start(tp);                   // 下面进入慢开始处理 (2)
	/* In dangerous area, increase slowly. */
	else if (sysctl_tcp_abc) {                // 否则进入拥塞避免阶段!!每个RTT时间就加1
		/* RFC3465: Appropriate Byte Count
		 * increase once for each full cwnd acked              // 基本思想就是:经过一个RTT时间就将snd_cwnd增加一个单位!
		 */                                                    // 一个RTT时间可以认为是当前拥塞窗口发送出去的数据的所有ACK都被接收到
		if (tp->bytes_acked >= tp->snd_cwnd*tp->mss_cache) {   // 当前的拥塞窗口的所有段都被ack了,窗口才被允许增加。
			tp->bytes_acked -= tp->snd_cwnd*tp->mss_cache;     // ACK处理过的及删除去了
			if (tp->snd_cwnd < tp->snd_cwnd_clamp)             // 不允许发送窗口大小超过snd_cwnd_clamp值
				tp->snd_cwnd++;
		}
	} else {                                       // 每接收到一个ACK,窗口增大(1/snd_cwnd),使用cnt计数
		/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd */
		if (tp->snd_cwnd_cnt >= tp->snd_cwnd) {    // 线性增长计数器 >= 阈值
			if (tp->snd_cwnd < tp->snd_cwnd_clamp) // 如果窗口还没有达到阈值
				tp->snd_cwnd++;                    // 那么++增大窗口
			tp->snd_cwnd_cnt = 0;
		} else
			tp->snd_cwnd_cnt++;                    // 否则仅仅是增大线性递增计数器
	}
}

下面看一下“慢开始”算法:

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
void tcp_slow_start(struct tcp_sock *tp)           // 每到达一个ACK,snd_cwnd就加1。这意味着每个RTT,拥塞窗口就会翻倍。
{
	int cnt; /* increase in packets */

	/* RFC3465: ABC Slow start
	 * Increase only after a full MSS of bytes is acked
	 *
	 * TCP sender SHOULD increase cwnd by the number of
	 * previously unacknowledged bytes ACKed by each incoming
	 * acknowledgment, provided the increase is not more than L
	 */
	if (sysctl_tcp_abc && tp->bytes_acked < tp->mss_cache)                     // 如果ack确认的数据少于一个MSS大小,不需要增大窗口
		return;
	// 限制cnt的值
	if (sysctl_tcp_max_ssthresh > 0 && tp->snd_cwnd > sysctl_tcp_max_ssthresh) // 发送窗口超过最大门限值
		cnt = sysctl_tcp_max_ssthresh >> 1;     /* limited slow start */       // 窗口减半~~~~~
	else
		cnt = tp->snd_cwnd;          /* exponential increase */                // 否则还是原来的窗口

	/* RFC3465: ABC
	 * We MAY increase by 2 if discovered delayed ack
	 */
	if (sysctl_tcp_abc > 1 && tp->bytes_acked >= 2*tp->mss_cache) // 如果启动了延迟确认,那么当接收到的ACK大于等于两个MSS的时候才加倍窗口大小
		cnt <<= 1;
	tp->bytes_acked = 0;  // 清空

	tp->snd_cwnd_cnt += cnt;
	while (tp->snd_cwnd_cnt >= tp->snd_cwnd) {  // 这里snd_cwnd_cnt是snd_cwnd的几倍,拥塞窗口就增加几。
		tp->snd_cwnd_cnt -= tp->snd_cwnd;       // ok
		if (tp->snd_cwnd < tp->snd_cwnd_clamp)  // 判断窗口大小
			tp->snd_cwnd++;  // + +
	}
}

最后看一下这个函数:tcp_is_cwnd_limited,基本的意思就是判断需不需要增大拥塞窗口。

关于gso:主要功能就是尽量的延迟数据包的传输,以便与在最恰当的时机传输数据包。如果支持gso,就有可能是tso 延迟了数据包,因此这里会进行几个相关的判断,来看需不需要增加拥塞窗口。

关于burst:主要用来控制网络流量的突发性增大,也就是说当left数据(还能发送的数据段数)大于burst值的时候,我们需要暂时停止增加窗口,因为此时有可能我们这边数据发送过快。其实就是一个平衡权值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int tcp_is_cwnd_limited(const struct sock *sk, u32 in_flight)  // 第二个参数是正在网络中传输,还没有收到确认的报数量
{
	const struct tcp_sock *tp = tcp_sk(sk);
	u32 left;

	if (in_flight >= tp->snd_cwnd)    // 比较发送未确认和发送拥塞窗口的大小
		return 1;                     // 如果未确认的大,那么需要增大拥塞窗口

	if (!sk_can_gso(sk))              // 如果没有gso延时处理所有包,不需要增大窗口
		return 0;

	left = tp->snd_cwnd - in_flight;  // 得到还能发送的数据包的数量
	if (sysctl_tcp_tso_win_divisor)
		return left * sysctl_tcp_tso_win_divisor < tp->snd_cwnd;
	else
		return left <= tcp_max_burst(tp); // 如果还可以发送的数量>burst,说明发送太快,不需要增大窗口。
}

清理重传队列中函数 tcp_clean_rtx_queue

http://blog.csdn.net/shanshanpt/article/details/22194029

如果重传队列中的一些数据已经被确认,那么, 需要从重传队列中清除出去,需要使用这个函数:tcp_clean_rtx_queue

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
161
162
/* Remove acknowledged frames from the retransmission queue. If our packet
 * is before the ack sequence we can discard it as it's confirmed to have
 * arrived at the other end.
 */
static int tcp_clean_rtx_queue(struct sock *sk, int prior_fackets)
{
	struct tcp_sock *tp = tcp_sk(sk);   // 获得tcp_sock
	const struct inet_connection_sock *icsk = inet_csk(sk); // 获得连接sock
	struct sk_buff *skb;
	u32 now = tcp_time_stamp;           // 当前时间,用于计算RTT
	int fully_acked = 1;                // 表示数据段是否完全被确认
	int flag = 0;
	u32 pkts_acked = 0;
	u32 reord = tp->packets_out;        // 发送出去,还在网络上跑,但是还没有被确认的数据包们
	s32 seq_rtt = -1;
	s32 ca_seq_rtt = -1;
	ktime_t last_ackt = net_invalid_timestamp();    // 把last_ackt设置位0
	// 下面就是遍历sk_write_queue队列,遇到snd_una就停止,如果没有更新过,开始就直接退出了
	while ((skb = tcp_write_queue_head(sk)) && skb != tcp_send_head(sk)) {
		struct tcp_skb_cb *scb = TCP_SKB_CB(skb);   // 获得这个重传队列的一个skb的cb字段
		u32 end_seq;
		u32 acked_pcount;
		u8 sacked = scb->sacked;

		/* Determine how many packets and what bytes were acked, tso and else */
		if (after(scb->end_seq, tp->snd_una)) {     // 注意这个scb是我们发出去的数据的skb中的一个scb哦!,不是接受到的数据!小心
			if (tcp_skb_pcount(skb) == 1 ||         // 这里的意思就是发出去的数据最后一个字节在已经确认的snd_una之后,说明还有没有确认的字节
			!after(tp->snd_una, scb->seq))          // 如果没有设置了TSO 或者 seq不在snd_una之前,即不是 seq---snd_una---end_seq这样情况
				break;                              // 那么说明没有必要把重传元素去掉,(如果是seq---snd_una---end_seq)那么前面半部分的就可以从队列中删除!!!

			acked_pcount = tcp_tso_acked(sk, skb);  // 如果只确认了TSO段中的一部分,则从skb删除已经确认的segs,并统计确认了多少段( 1 )
			if (!acked_pcount)                      // 处理出错
				break;

			fully_acked = 0;                        // 表示TSO只处理了一部分,其他还没处理完
			end_seq = tp->snd_una;
		} else {
			acked_pcount = tcp_skb_pcount(skb);     // 即 !after(scb->end_seq, tp->snd_una),说明已经完全确认OK!
			end_seq = scb->end_seq;
		}

		/* MTU probing checks */
		if (fully_acked && icsk->icsk_mtup.probe_size &&      // 探测mtu,暂时不多说
		!after(tp->mtu_probe.probe_seq_end, scb->end_seq)) {
			tcp_mtup_probe_success(sk, skb);
		}
		// 下面通过sack的信息得到这是一个被重传的过包
		if (sacked & TCPCB_RETRANS) {
			if (sacked & TCPCB_SACKED_RETRANS)      // 如果之前重传过,&& 之前还没收到回复
				tp->retrans_out -= acked_pcount;    // 现在需要更新重传的且没有收到ACK的包
			flag |= FLAG_RETRANS_DATA_ACKED;        // 重传包收到ACK
			ca_seq_rtt = -1;
			seq_rtt = -1;
			if ((flag & FLAG_DATA_ACKED) || (acked_pcount > 1))
				flag |= FLAG_NONHEAD_RETRANS_ACKED;
		} else { // 如果此数据段没有被重传过
			ca_seq_rtt = now - scb->when;           // 通过ACK确认获得RTT值
			last_ackt = skb->tstamp;                // 获得skb的发送时间
			if (seq_rtt < 0) {
				seq_rtt = ca_seq_rtt;
			}
			if (!(sacked & TCPCB_SACKED_ACKED))     // 如果SACK存在一段没有被确认,那么保存其中序号最小号的
				reord = min(pkts_acked, reord);
		}

		if (sacked & TCPCB_SACKED_ACKED)            // 如果是有sack标识
			tp->sacked_out -= acked_pcount;         // 那么更新sack的发出没有接受到确认的数量
		if (sacked & TCPCB_LOST)                    // 如果是丢包标识,那么更新数量
			tp->lost_out -= acked_pcount;

		if (unlikely(tp->urg_mode && !before(end_seq, tp->snd_up)))  // 紧急模式
			tp->urg_mode = 0;

		tp->packets_out -= acked_pcount;            // 发送的包没有确认的数量-=acked_pcount
		pkts_acked += acked_pcount;                 // 接收到确认的包数量+=acked_pcount

		/* Initial outgoing SYN's get put onto the write_queue
		 * just like anything else we transmit.  It is not
		 * true data, and if we misinform our callers that
		 * this ACK acks real data, we will erroneously exit
		 * connection startup slow start one packet too
		 * quickly.  This is severely frowned upon behavior.
		 */
		if (!(scb->flags & TCPCB_FLAG_SYN)) {       // 如果不是SYN握手包
			flag |= FLAG_DATA_ACKED;                // 标识是数据确认
		} else {
			flag |= FLAG_SYN_ACKED;                 // 标识是SYN包标识
			tp->retrans_stamp = 0;                  // 清除重传戳
		}

		if (!fully_acked)                           // 如果TSO段没被完全确认,则到此为止
			break;

		tcp_unlink_write_queue(skb, sk);            // 从发送队列上移除这个skb!!!这个函数其实很简单,其实就是从链表中移除这个skb而已
		sk_wmem_free_skb(sk, skb);                  // 删除skb内存对象
		tcp_clear_all_retrans_hints(tp);
	}                                               // while循环结束

	if (skb && (TCP_SKB_CB(skb)->sacked & TCPCB_SACKED_ACKED))  // 虚假的SACK
		flag |= FLAG_SACK_RENEGING;

	if (flag & FLAG_ACKED) {                        // 如果ACK更新了数据,是的snd_una更新了
		const struct tcp_congestion_ops *ca_ops
			= inet_csk(sk)->icsk_ca_ops;            // 拥塞信息

		tcp_ack_update_rtt(sk, flag, seq_rtt);      // 更新RTT
		tcp_rearm_rto(sk);                          // 重置超时重传计时器

		if (tcp_is_reno(tp)) {                      // 如果没有SACK处理
			tcp_remove_reno_sacks(sk, pkts_acked);  // 处理乱序的包
		} else {
			/* Non-retransmitted hole got filled? That's reordering */
			if (reord < prior_fackets)
				tcp_update_reordering(sk, tp->fackets_out - reord, 0);  // 更新乱序队列大小
		}

		tp->fackets_out -= min(pkts_acked, tp->fackets_out);    // 更新提前确认算法得出的尚未得到确认的包的数量

		if (ca_ops->pkts_acked) {   // 这是一个钩子函数
			s32 rtt_us = -1;

			/* Is the ACK triggering packet unambiguous? */
			if (!(flag & FLAG_RETRANS_DATA_ACKED)) {            // 如果是确认了非重传的包
				/* High resolution needed and available? */
				if (ca_ops->flags & TCP_CONG_RTT_STAMP &&       // 下面都是测量RTT,精读不同而已
				!ktime_equal(last_ackt,
						 net_invalid_timestamp()))
					rtt_us = ktime_us_delta(ktime_get_real(),
								last_ackt);
				else if (ca_seq_rtt > 0)
					rtt_us = jiffies_to_usecs(ca_seq_rtt);
			}

			ca_ops->pkts_acked(sk, pkts_acked, rtt_us);
		}
	}

#if FASTRETRANS_DEBUG > 0  // 下面用于调试
	BUG_TRAP((int)tp->sacked_out >= 0);
	BUG_TRAP((int)tp->lost_out >= 0);
	BUG_TRAP((int)tp->retrans_out >= 0);
	if (!tp->packets_out && tcp_is_sack(tp)) {
		icsk = inet_csk(sk);
		if (tp->lost_out) {
			printk(KERN_DEBUG "Leak l=%u %d\n",
				tp->lost_out, icsk->icsk_ca_state);
			tp->lost_out = 0;
		}
		if (tp->sacked_out) {
			printk(KERN_DEBUG "Leak s=%u %d\n",
				tp->sacked_out, icsk->icsk_ca_state);
			tp->sacked_out = 0;
		}
		if (tp->retrans_out) {
			printk(KERN_DEBUG "Leak r=%u %d\n",
				tp->retrans_out, icsk->icsk_ca_state);
			 tp->retrans_out = 0;
		}
	}
#endif
	return flag;
}

下面看一下tcp_tso_acked函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/* If we get here, the whole TSO packet has not been acked. */
static u32 tcp_tso_acked(struct sock *sk, struct sk_buff *skb)       // TSO 包并没有全部被确认,现在需要统计已经被确认的数量
{
	struct tcp_sock *tp = tcp_sk(sk);                                // 获得tcp_sock
	u32 packets_acked;

	BUG_ON(!after(TCP_SKB_CB(skb)->end_seq, tp->snd_una));           // seq---end_seq---snd_una  这种情况不可能进来

	packets_acked = tcp_skb_pcount(skb);                             // TSO段总共包括几个
	if (tcp_trim_head(sk, skb, tp->snd_una - TCP_SKB_CB(skb)->seq))  // 对于已经确认的部分,更新skb中的信息。例如len之类信息都变了
		return 0;                                                    // 然后重新计算出新的剩余的segs
	packets_acked -= tcp_skb_pcount(skb);                            // 之前总的segs - 现在剩余的segs == 被确认的segs

	if (packets_acked) {
		BUG_ON(tcp_skb_pcount(skb) == 0);
		BUG_ON(!before(TCP_SKB_CB(skb)->seq, TCP_SKB_CB(skb)->end_seq));
	}

	return packets_acked;                                            // 返回被确认的数量
}

linux TCP/IP协议栈-IP层

linux TCP/IP协议栈 —ip_rcv()
linux TCP/IP协议栈 —ip_rcv_finish()
linux TCP/IP协议栈 —ip_local_deliver()
linux TCP/IP协议栈 —ip_local_deliver_finish()
linux TCP/IP协议栈 —ip_defrag()
linux TCP/IP协议栈 —ip_find()
linux TCP/IP协议栈 —inet_frag_find()

ip_rcv()

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
/* 主要功能:对IP头部合法性进行严格检查,然后把具体功能交给ip_rcv_finish。*/
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
	struct iphdr *iph;
	u32 len;
	/* 网络名字空间,忽略 */
	if (dev->nd_net != &init_net)
		goto drop;
	/*
	 *当网卡处于混杂模式时,收到不是发往该主机的数据包,由net_rx_action()设置。
	 *在调用ip_rcv之前,内核会将该数据包交给嗅探器,所以该函数仅丢弃该包。
	 */
	if (skb->pkt_type == PACKET_OTHERHOST)
		goto drop;
	/* SNMP所需要的统计数据,忽略 */
	IP_INC_STATS_BH(IPSTATS_MIB_INRECEIVES);

	/*
	 *ip_rcv是由netif_receive_skb函数调用,如果嗅探器或者其他的用户对数据包需要进
	 *进行处理,则在调用ip_rcv之前,netif_receive_skb会增加skb的引用计数,既该引
	 *用计数会大于1。若如此次,则skb_share_check会创建sk_buff的一份拷贝。
	 */
	if ((skb = skb_share_check(skb, GFP_ATOMIC)) == NULL) {
		IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
		goto out;
	}
	/*
	 *pskb_may_pull确保skb->data指向的内存包含的数据至少为IP头部大小,由于每个
	 *IP数据包包括IP分片必须包含一个完整的IP头部。如果小于IP头部大小,则缺失
	 *的部分将从数据分片中拷贝。这些分片保存在skb_shinfo(skb)->frags[]中。
	 */
	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
		goto inhdr_error;
	/* pskb_may_pull可能会调整skb中的指针,所以需要重新定义IP头部*/
	iph = ip_hdr(skb);

	/*
	 *    RFC1122: 3.1.2.2 MUST silently discard any IP frame that fails the checksum.
	 *
	 *    Is the datagram acceptable?
	 *
	 *    1.    Length at least the size of an ip header
	 *    2.    Version of 4
	 *    3.    Checksums correctly. [Speed optimisation for later, skip loopback checksums]
	 *    4.    Doesn't have a bogus length
	 */
	/* 上面说的很清楚了 */
	if (iph->ihl < 5 || iph->version != 4)
		goto inhdr_error;
	/* 确保IP完整的头部包括选项在内存中 */
	if (!pskb_may_pull(skb, iph->ihl*4))
		goto inhdr_error;
	
	iph = ip_hdr(skb);
	/* 验证IP头部的校验和 */
	if (unlikely(ip_fast_csum((u8 *)iph, iph->ihl)))
		goto inhdr_error;
	/* IP头部中指示的IP数据包总长度 */
	len = ntohs(iph->tot_len);
	/*
	 *确保skb的数据长度大于等于IP头部中指示的IP数据包总长度及数据包总长度必须
	 *大于等于IP头部长度。
	 */
	if (skb->len < len) {
		IP_INC_STATS_BH(IPSTATS_MIB_INTRUNCATEDPKTS);
		goto drop;
	} else if (len < (iph->ihl*4))
		goto inhdr_error;

	/* Our transport medium may have padded the buffer out. Now we know it
	 * is IP we can trim to the true length of the frame.
	 * Note this now means skb->len holds ntohs(iph->tot_len).
	 */
	/* 注释说明的很清楚,该函数成功执行完之后,skb->len = ntohs(iph->tot_len). */
	if (pskb_trim_rcsum(skb, len)) {
		IP_INC_STATS_BH(IPSTATS_MIB_INDISCARDS);
		goto drop;
	}

	/* Remove any debris in the socket control block */
	memset(IPCB(skb), 0, sizeof(struct inet_skb_parm));
	/* 忽略与netfilter子系统的交互,调用为ip_rcv_finish(skb) */
	return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
		 ip_rcv_finish);

inhdr_error:
	IP_INC_STATS_BH(IPSTATS_MIB_INHDRERRORS);
drop:
	kfree_skb(skb);
out:
	return NET_RX_DROP;
}

ip_rcv_finish()

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 ip_rcv_finish(struct sk_buff *skb)
{
	const struct iphdr *iph = ip_hdr(skb);
	struct rtable *rt;

	/*
	 *    Initialise the virtual path cache for the packet. It describes
	 *    how the packet travels inside Linux networking.
	 */
	/*
	 * 通常从外界接收的数据包,skb->dst不会包含路由信息,暂时还不知道在何处会设置
	 * 这个字段。ip_route_input函数会根据路由表设置路由信息,暂时不考虑路由系统。
	 */
	if (skb->dst == NULL) {
		int err = ip_route_input(skb, iph->daddr, iph->saddr, iph->tos,
					 skb->dev);
		if (unlikely(err)) {
			if (err == -EHOSTUNREACH)
				IP_INC_STATS_BH(IPSTATS_MIB_INADDRERRORS);
			else if (err == -ENETUNREACH)
				IP_INC_STATS_BH(IPSTATS_MIB_INNOROUTES);
			goto drop;
		}
	}
/* 更新流量控制所需要的统计数据,忽略 */
#ifdef CONFIG_NET_CLS_ROUTE
	if (unlikely(skb->dst->tclassid)) {
		struct ip_rt_acct *st = ip_rt_acct + 256*smp_processor_id();
		u32 idx = skb->dst->tclassid;
		st[idx&0xFF].o_packets++;
		st[idx&0xFF].o_bytes+=skb->len;
		st[(idx>>16)&0xFF].i_packets++;
		st[(idx>>16)&0xFF].i_bytes+=skb->len;
	}
#endif
	/* 如果IP头部大于20字节,则表示IP头部包含IP选项,需要进行选项处理.暂时忽略,毕竟很少用 */
	if (iph->ihl > 5 && ip_rcv_options(skb))
		goto drop;

	/* skb->dst包含路由信息。根据路由类型更新SNMP统计数据 */
	rt = (struct rtable*)skb->dst;
	if (rt->rt_type == RTN_MULTICAST)
		IP_INC_STATS_BH(IPSTATS_MIB_INMCASTPKTS);
	else if (rt->rt_type == RTN_BROADCAST)
		IP_INC_STATS_BH(IPSTATS_MIB_INBCASTPKTS);
	/*
	 * dst_input实际上会调用skb->dst->input(skb).input函数会根据路由信息设置为合适的
	 * 函数指针,如果是递交到本地的则为ip_local_deliver,若是转发则为ip_forward.
	 * 暂时仅先考虑ip_local_deliver。
	 */
	return dst_input(skb);

drop:
	kfree_skb(skb);
	return NET_RX_DROP;
}

ip_local_deliver()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
 *     Deliver IP Packets to the higher protocol layers.
 */
主要功能:收集IP分片,然后调用ip_local_deliver_finish将一个完整的数据包传送给上层协议。
int ip_local_deliver(struct sk_buff *skb)
{
	/*
	 *    Reassemble IP fragments.
	 */
	/*
	 * 判断该IP数据包是否是一个分片,如果IP_MF置位,则表示该包是分片之一,其
	 * 后还有更多分片,最后一个IP分片未置位IP_MF但是其offset是非0。
	 * 如果是一个IP分片,则调用ip_defrag重新组织IP数据包。
	 */
	if (ip_hdr(skb)->frag_off & htons(IP_MF | IP_OFFSET)) {
		if (ip_defrag(skb, IP_DEFRAG_LOCAL_DELIVER))
			return 0;
	}
	/* 调用ip_local_deliver_finish(skb) */
	return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
		 ip_local_deliver_finish);
}

ip_local_deliver_finish()

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
/* 如果忽略掉原始套接字和IPSec,则该函数仅仅是根据IP头部中的协议字段选择上层L4协议,并交给它来处理 */
static int ip_local_deliver_finish(struct sk_buff *skb)
{
	/* 跳过IP头部 */
	__skb_pull(skb, ip_hdrlen(skb));

	/* Point into the IP datagram, just past the header. */
	/* 设置传输层头部位置 */
	skb_reset_transport_header(skb);

	rcu_read_lock();
	{
		/* Note: See raw.c and net/raw.h, RAWV4_HTABLE_SIZE==MAX_INET_PROTOS */
		int protocol = ip_hdr(skb)->protocol;
		int hash;
		struct sock *raw_sk;
		struct net_protocol *ipprot;

	resubmit:
	/* 这个hash根本不是哈希值,仅仅只是inet_protos数组中的下表而已 */
		hash = protocol & (MAX_INET_PROTOS - 1);
		raw_sk = sk_head(&raw_v4_htable[hash]);

		/* If there maybe a raw socket we must check - if not we
		 * don't care less
		 */
	/* 原始套接字?? 忽略... */
		if (raw_sk && !raw_v4_input(skb, ip_hdr(skb), hash))
			raw_sk = NULL;
	/* 查找注册的L4层协议处理结构。 */
		if ((ipprot = rcu_dereference(inet_protos[hash])) != NULL) {
			int ret;
	/* 启用了安全策略,则交给IPSec */
			if (!ipprot->no_policy) {
				if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					kfree_skb(skb);
					goto out;
				}
				nf_reset(skb);
			}
	/* 调用L4层协议处理函数 */
	/* 通常会是tcp_v4_rcv, udp_rcv, icmp_rcv和igmp_rcv */
	/* 如果注册了其他的L4层协议处理,则会进行相应的调用。 */
			ret = ipprot->handler(skb);
			if (ret < 0) {
				protocol = -ret;
				goto resubmit;
			}
			IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
		} else {
			if (!raw_sk) {    /* 无原始套接字,提交给IPSec */
				if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
					IP_INC_STATS_BH(IPSTATS_MIB_INUNKNOWNPROTOS);
					icmp_send(skb, ICMP_DEST_UNREACH,
						 ICMP_PROT_UNREACH, 0);
				}
			} else
				IP_INC_STATS_BH(IPSTATS_MIB_INDELIVERS);
			kfree_skb(skb);
		}
	}
 out:
	rcu_read_unlock();

	return 0;
}

ip_defrag()

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
/* Process an incoming IP datagram fragment. */
int ip_defrag(struct sk_buff *skb, u32 user)
{
	struct ipq *qp;

	IP_INC_STATS_BH(IPSTATS_MIB_REASMREQDS);

	/* Start by cleaning up the memory. */
	/*
	 * 首先检查所有IP分片所消耗的内存是否大于系统允许的最高阀值,如果是,则调用
	 * ip_evictor()丢弃未完全到达的IP分片,从最旧的分片开始释放。此举一来是为了节
	 * 约内存,二来是未了防止黑客的恶意攻击。使分片在系统中累计,降低系统性能。
	 */
	if (atomic_read(&ip4_frags.mem) > ip4_frags_ctl.high_thresh)
		ip_evictor();

	/* Lookup (or create) queue header */
	/* 如果该分片是数据报的第一个分片,则ip_find返回一个新的队列来搜集分片,否则
	 * 返回其所属于的分片队列。 */
	if ((qp = ip_find(ip_hdr(skb), user)) != NULL) {
		int ret;

		spin_lock(&qp->q.lock);
	/* 将该分片加入到队列中,重组分片队列,如果所有的包都收到了,则该函数
	 * 负责重组IP包 */
		ret = ip_frag_queue(qp, skb);

		spin_unlock(&qp->q.lock);
		ipq_put(qp);    /* 引用计数减1 */
		return ret;
	}

	IP_INC_STATS_BH(IPSTATS_MIB_REASMFAILS);
	kfree_skb(skb);
	return -ENOMEM;
}

ip_find()

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
/* Find the correct entry in the "incomplete datagrams" queue for
 * this IP datagram, and create new one, if nothing is found.
 */
/* u32 user这个参数有点迷惑,其表示以何种理由需要对数据包进行重组,在ip_local_deliver的调用序列当中,这个值是IP_DEFRAG_LOCAL_DELIVER。*/
static inline struct ipq *ip_find(struct iphdr *iph, u32 user)
{
	struct inet_frag_queue *q;
	struct ip4_create_arg arg;
	unsigned int hash;

	arg.iph = iph;
	arg.user = user;
	/*
	 * hash算法,该算法除了使用所给的这四个参数之外,还使用了一个随机值
	 * ip4_frags.rnd,,其初始化为
	 * (u32) ((num_physpages ^ (num_physpages>>7)) ^ (jiffies ^ (jiffies >> 6)));
	 * 这是为了防止黑客根据固定的hash算法,通过设置ip头部的这些字段,生成同样
	 * HASH值,从而使某一HASH队列长度急剧增大而影响性能。
	 */
	hash = ipqhashfn(iph->id, iph->saddr, iph->daddr, iph->protocol);
	/* 若存在该分片所属的分片队列则返回这个队列,否则创建一个新的队列 */
	q = inet_frag_find(&ip4_frags, &arg, hash);
	if (q == NULL)
		goto out_nomem;

	return container_of(q, struct ipq, q);

out_nomem:
	LIMIT_NETDEBUG(KERN_ERR "ip_frag_create: no memory left !\n");
	return NULL;
}

inet_frag_find()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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;

	/* f->lock是读写锁,先搜索是否存在该IP分段所属的队列 */
	read_lock(&f->lock);
	hlist_for_each_entry(q, n, &f->hash[hash], list) { /* 扫描该HASH槽中所有节点 */
	/* f->match中match字段在ipfrag_init中初始化为ip4_frag_match函数。*/
	/* 对比分片队列中的散列字段和user是否和key相等,key指向的是struct ip4_create_arg
	 * 结构,包含IP头部和user字段。 */
		if (f->match(q, key)) {
			atomic_inc(&q->refcnt);     /* 若找到,则增加该队列引用计数。 */
			read_unlock(&f->lock);
			return q;                /* 返回该队列 */
		}
	}
	read_unlock(&f->lock);
	/* 该分片是第一个IP分片,创建一个新的分片队列并添加到合适的HASH队列 */
	return inet_frag_create(f, key, hash);
}