linux tcp三次握手-SYN接收以及SYNACK发送

概述

对于协议栈的接收路径,syn包的处理路径如下

  • tcp_v4_rcv
    • ->__inet_lookup_skb() //listen hash中找到对应的TCP_LISTEN的sk
    • ->tcp_v4_do_rcv()
      • ->tcp_v4_cookie_check() //syncookie检查,因为没有syn包没有ack选项,因此忽略, 如果syncookie验证通过则创建新的sock
      • ->tcp_rcv_state_process()
        ->tcp_v4_conn_request()
  • 对于syncookie,服务端不保存任何状态
  • 对于fastopen,新建sock进入TCP_SYN_RCV状态, 并插入等待accpet队列,并把数据部分放倒接收队列中, 并设置重传定时器
  • 对于一般的syn包,request_sock设置为TCP_NEW_SYN_RECV,插入ehash表, 设置req_timer定时器,重传synack

tcp_v4_conn_request

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
int tcp_conn_request(struct request_sock_ops *rsk_ops, //tcp_request_sock_ops
const struct tcp_request_sock_ops *af_ops, //tcp_request_sock_ipv4_ops
struct sock *sk, struct sk_buff *skb)
{
struct tcp_fastopen_cookie foc = { .len = -1 };
__u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn; // 非0表示发送到了timewait sock
struct tcp_options_received tmp_opt;
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
struct sock *fastopen_sk = NULL;
struct dst_entry *dst = NULL;
struct request_sock *req;
bool want_cookie = false;
struct flowi fl;
if ((net->ipv4.sysctl_tcp_syncookies == 2 || //sysctl_tcp_syncookies=2无条件生成syncookie
inet_csk_reqsk_queue_is_full(sk)) && !isn) { //或者请求队列太长, 并且当前不是timewait
want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name); //sysctl_tcp_syncookies>0, 并未当前socket打印一次告警
if (!want_cookie)
goto drop; //队列满了,但不使用syncookie,则丢弃
}
if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) { //accept队列满,但是syn队列依然有可能被accept的连接,此时丢弃
NET_INC_STATS(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS);
goto drop;
}
req = inet_reqsk_alloc(rsk_ops, sk, !want_cookie); //分配request_sock, 进入TCP_NEW_SYN_RECV状态
if (!req)
goto drop;
tcp_rsk(req)->af_specific = af_ops; //tcp_request_sock_ipv4_ops
tcp_clear_options(&tmp_opt);
tmp_opt.mss_clamp = af_ops->mss_clamp; //TCP_MSS_DEFAULT=536
tmp_opt.user_mss = tp->rx_opt.user_mss; //listen sock设置的或是tw的
tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc); //开启syncookie后则不用考虑fastopen, syncookie不允许使用tcp扩展
if (want_cookie && !tmp_opt.saw_tstamp) //开启syncookie,但是不带timestamp
tcp_clear_options(&tmp_opt); //清除wscale,sack_ok等选项,因为没地方存
tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;
tcp_openreq_init(req, &tmp_opt, skb, sk); //初始化req
inet_rsk(req)->no_srccheck = inet_sk(sk)->transparent;
/* Note: tcp_v6_init_req() might override ir_iif for link locals */
inet_rsk(req)->ir_iif = inet_request_bound_dev_if(sk, skb);
af_ops->init_req(req, sk, skb); //tcp_v4_init_req
if (security_inet_conn_request(sk, skb, req))
goto drop_and_free;
if (!want_cookie && !isn) { //不需要生成syncookie,也不是从timewait recycle的新的sock
/* VJ's idea. We save last timestamp seen
* from the destination in peer table, when entering
* state TIME-WAIT, and check against it before
* accepting new connection request.
*
* If "isn" is not zero, this request hit alive
* timewait bucket, so that all the necessary checks
* are made in the function processing timewait state.
*/
if (tcp_death_row.sysctl_tw_recycle) {
bool strict;
dst = af_ops->route_req(sk, &fl, req, &strict); //tcp_v4_route_req
if (dst && strict &&
!tcp_peer_is_proven(req, dst, true, //如果tw recycle开启,但是syn不带timestamp
tmp_opt.saw_tstamp)) {
NET_INC_STATS(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
goto drop_and_release;
}
}
/* Kill the following clause, if you dislike this way. */
else if (!net->ipv4.sysctl_tcp_syncookies && //如果没开启sysctl_tw_recycle和syncookie,最后1/4的syn请求需要验证过去的连接信息
(sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
(sysctl_max_syn_backlog >> 2)) &&
!tcp_peer_is_proven(req, dst, false, //如果不存在tcp metric或者过去的连接信息则丢弃
tmp_opt.saw_tstamp)) {
/* Without syncookies last quarter of
* backlog is filled with destinations,
* proven to be alive.
* It means that we continue to communicate
* to destinations, already remembered
* to the moment of synflood.
*/
pr_drop_req(req, ntohs(tcp_hdr(skb)->source),
rsk_ops->family);
goto drop_and_release;
}
isn = af_ops->init_seq(skb); //tcp_v4_init_sequence,根据四元组,随机数,当前高精度时间来生成isn
}
if (!dst) {
dst = af_ops->route_req(sk, &fl, req, NULL); //tcp_v4_route_req
if (!dst)
goto drop_and_free;
}
tcp_ecn_create_request(req, skb, sk, dst);
if (want_cookie) {
isn = cookie_init_sequence(af_ops, sk, skb, &req->mss); //cookie_v4_init_sequence生成syncookie,并作为ack的起始序号
req->cookie_ts = tmp_opt.tstamp_ok;
if (!tmp_opt.tstamp_ok)
inet_rsk(req)->ecn_ok = 0;
}
tcp_rsk(req)->snt_isn = isn;
tcp_rsk(req)->txhash = net_tx_rndhash();
tcp_openreq_init_rwin(req, sk, dst); //设置初始化rwnd
if (!want_cookie) {
tcp_reqsk_record_syn(sk, req, skb); //如果设置保存TCP_SAVE_SYN标记,则保存
fastopen_sk = tcp_try_fastopen(sk, skb, req, &foc, dst); //验证后创建fastopen sock,并把数据部分放入接收队列中
}
if (fastopen_sk) { //验证并创建fastsocket成功, 进入TCP_SYN_RCV状态
af_ops->send_synack(fastopen_sk, dst, &fl, req, //tcp_v4_send_synack
&foc, TCP_SYNACK_FASTOPEN);
/* Add the child socket directly into the accept queue */
inet_csk_reqsk_queue_add(sk, req, fastopen_sk); //添加到等待accept的队列
sk->sk_data_ready(sk);
bh_unlock_sock(fastopen_sk);
sock_put(fastopen_sk);
} else {
tcp_rsk(req)->tfo_listener = false;
if (!want_cookie)
inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT); //插入ehash,并设置定时器
af_ops->send_synack(sk, dst, &fl, req, &foc, //tcp_v4_send_synack
!want_cookie ? TCP_SYNACK_NORMAL :
TCP_SYNACK_COOKIE);
if (want_cookie) { //启用syncookie的话,可以直接释放req
reqsk_free(req);
return 0;
}
}
reqsk_put(req);
return 0;
drop_and_release:
dst_release(dst);
drop_and_free:
reqsk_free(req);
drop:
tcp_listendrop(sk);
return 0;
}

inet_csk_reqsk_queue_hash_add

把req放入ehash中,并设置rsk_timer定时器

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
static void reqsk_queue_hash_req(struct request_sock *req,
unsigned long timeout)
{
req->num_retrans = 0;
req->num_timeout = 0;
req->sk = NULL;
setup_pinned_timer(&req->rsk_timer, reqsk_timer_handler,
(unsigned long)req);
mod_timer(&req->rsk_timer, jiffies + timeout);
inet_ehash_insert(req_to_sk(req), NULL);
/* before letting lookups find us, make sure all req fields
* are committed to memory and refcnt initialized.
*/
smp_wmb();
atomic_set(&req->rsk_refcnt, 2 + 1);
}
static inline void reqsk_queue_added(struct request_sock_queue *queue)
{
atomic_inc(&queue->young);
atomic_inc(&queue->qlen);
}
static inline void inet_csk_reqsk_queue_added(struct sock *sk)
{
reqsk_queue_added(&inet_csk(sk)->icsk_accept_queue);
}
void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
unsigned long timeout)
{
reqsk_queue_hash_req(req, timeout);
inet_csk_reqsk_queue_added(sk); //只增加计数器,来统计没重传过synack的请求
}

synack发送

tcp_make_synack主要是根据需要设置synack的tcp选项, 使用syncookie的时候服务端不保存状态,会把tcp扩展项编码到timestamp中,把syncookie作为seq回传;
对于fastopen请求,则会设置好fastopen cookie tcp选项回传,并对接收到的数据部分进行ack
因为synack不用分片并且必须有路由缓存,直接调用ip_build_and_send_pkt()来发送

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
static int tcp_v4_send_synack(const struct sock *sk, struct dst_entry *dst,
struct flowi *fl,
struct request_sock *req,
struct tcp_fastopen_cookie *foc,
enum tcp_synack_type synack_type)
{
const struct inet_request_sock *ireq = inet_rsk(req);
struct flowi4 fl4;
int err = -1;
struct sk_buff *skb;
/* First, grab a route. */
if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL)
return -1;
skb = tcp_make_synack(sk, dst, req, foc, synack_type);
if (skb) {
__tcp_v4_send_check(skb, ireq->ir_loc_addr, ireq->ir_rmt_addr); //计算校验和
err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr, //发送synack
ireq->ir_rmt_addr,
ireq->opt);
err = net_xmit_eval(err);
}
return err;
}