TCP三次握手源码分析(服务端接收SYN以及发送SYN+ACK)
内核版本:Linux 3.10
内核源码地址:https://elixir.bootlin.com/linux/v3.10/source (包含各个版本内核源码,且网页可全局搜索函数)
接上一篇,TCP三次握手源码分析(客户端发送SYN)
一、服务端响应SYN
在服务器端,所有的TCP包(包括客户端发来的SYN握手请求)都经过网卡、软中断,进入到tcp_v4_rcv。
具体细节可以参考《Linux收包之数据从网卡到协议栈是如何流转的》、《Linux收包之数据L3层是如何流转的》
1.tcp_v4_rcv()函数,主要工作:
- 校验数据包;
- 根据网络包TCP头信息中的目的IP信息查到当前在listen的socket;
- 将数据包链入预处理队列,如果没有预处理队列,则直接调用tcp_v4_do_rcv处理数据包;
// file: net/ipv4/tcp_ipv4.c int tcp_v4_rcv(struct sk_buff *skb) { const struct iphdr *iph; const struct tcphdr *th; struct sock *sk; int ret; struct net *net = dev_net(skb->dev); if (skb->pkt_type != PACKET_HOST) //检查数据包类型,如果不是发给本机的就丢弃 goto discard_it; /* Count it even if it's bad */ TCP_INC_STATS_BH(net, TCP_MIB_INSEGS); //递增计数 if (!pskb_may_pull(skb, sizeof(struct tcphdr))) //检查、调整数据包的TCP头部 goto discard_it; th = tcp_hdr(skb); //指向数据包的TCP头部 if (th->doff < sizeof(struct tcphdr) / 4) //检查TCP头部的长度 goto bad_packet; if (!pskb_may_pull(skb, th->doff * 4)) //检查、调整数据包的TCP头部(这次包括TCP选项) goto discard_it; if (!skb_csum_unnecessary(skb) && tcp_v4_checksum_init(skb)) //如果没有设置校验和,就计算并初始化校验和 goto csum_error; th = tcp_hdr(skb); //重新获取TCP头部(这次包括TCP选项) iph = ip_hdr(skb); //获取IP头部指针 TCP_SKB_CB(skb)->seq = ntohl(th->seq); //记录序号 TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin + skb->len - th->doff * 4); //计算结束序号 TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq); //记录ACK序号 TCP_SKB_CB(skb)->when = 0; //重发参考值 TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph); TCP_SKB_CB(skb)->sacked = 0; //SACK标志 // 先在已经连接的sock队列中查找(ehash) // 没有找到的话,就在监听的sock队列中查找(listen_hash listen的时候挂入的),此处找到的是处于listen状态的sock sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest); //查找用于接收的sock结构 if (!sk) //没有找到接收的sock结构就跳转到no_tcp_socket goto no_tcp_socket; process: if (sk->sk_state == TCP_TIME_WAIT) //如果找到的sock是TIME_WAIT状态 goto do_time_wait; if (unlikely(iph->ttl < inet_sk(sk)->min_ttl)) { NET_INC_STATS_BH(net, LINUX_MIB_TCPMINTTLDROP); goto discard_and_relse; } if (!xfrm4_policy_check(sk, XFRM_POLICY_IN, skb)) //XFRM检测,不关心 goto discard_and_relse; nf_reset(skb); if (sk_filter(sk, skb)) goto discard_and_relse; skb->dev = NULL; bh_lock_sock_nested(sk); ret = 0; if (!sock_owned_by_user(sk)) { //检测sock结构是否还可用 ...... { if (!tcp_prequeue(sk, skb)) //链入预处理队列 ret = tcp_v4_do_rcv(sk, skb); //处理数据包 } } else if (unlikely(sk_add_backlog(sk, skb, sk->sk_rcvbuf + sk->sk_sndbuf))) { //如果sock结构目前不可用,就将数据包链入后备队列 bh_unlock_sock(sk); NET_INC_STATS_BH(net, LINUX_MIB_TCPBACKLOGDROP); goto discard_and_relse; } bh_unlock_sock(sk); //解锁 sock_put(sk); //递减使用计数 return ret; no_tcp_socket: //没有找到接收的sock结构 if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) goto discard_it; if (skb->len < (th->doff << 2) || tcp_checksum_complete(skb)) { csum_error: TCP_INC_STATS_BH(net, TCP_MIB_CSUMERRORS); bad_packet: TCP_INC_STATS_BH(net, TCP_MIB_INERRS); } else { tcp_v4_send_reset(NULL, skb); //向客户端发送RST包 } discard_it: //丢弃数据包 /* Discard frame. */ kfree_skb(skb); return 0; ...... }
2.数据包通过tcp_prequeue()链入预处理队列,最终也是调用tcp_v4_do_rcv()函数处理。
// file: net/ipv4/tcp_ipv4.c int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb) { struct sock *rsk; ...... if (sk->sk_state == TCP_ESTABLISHED) { //如果sock已经处于ESTABLISHED状态 ...... //此时,服务器收到第一步握手SYN,sock还处于LISTEN状态 } if (skb->len < tcp_hdrlen(skb) || tcp_checksum_complete(skb)) //检查数据块长度,检查校验和 goto csum_err; if (sk->sk_state == TCP_LISTEN) { //如果sock处理LISTEN状态 struct sock *nsk = tcp_v4_hnd_req(sk, skb); //查找和创建代表客户端的sock结构 if (!nsk) //没有找到,丢弃数据包 goto discard; if (nsk != sk) { //如果找到的不是服务器当前的sock结构,它就是代表客户端的sock结构 sock_rps_save_rxhash(nsk, skb); if (tcp_child_process(sk, nsk, skb)) { //唤醒服务器进程接收客户端连接请求 rsk = nsk; goto reset; } return 0; } } else sock_rps_save_rxhash(sk, skb); if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) { //根据sock状态处理数据包 rsk = sk; goto reset; } return 0; reset: tcp_v4_send_reset(rsk, skb); //向客户端发送|RST包 ...... }
tcp_v4_hnd_req()任然是取得了服务器的sock结构(半连接队列和ehash中都没有找到,返回了传入的sk)。
// file: net/ipv4/tcp_ipv4.c static struct sock *tcp_v4_hnd_req(struct sock *sk, struct sk_buff *skb) { struct tcphdr *th = tcp_hdr(skb); const struct iphdr *iph = ip_hdr(skb); struct sock *nsk; struct request_sock **prev; // 查找连接请求结构(半连接队列查找) struct request_sock *req = inet_csk_search_req(sk, &prev, th->source, iph->saddr, iph->daddr); if (req) return tcp_check_req(sk, skb, req, prev, false); //创建客户端的sock结构,将连接请求链入这个sock结构的接收队列中 // 在ehash的队列中查找 nsk = inet_lookup_established(sock_net(sk), &tcp_hashinfo, iph->saddr, th->source, iph->daddr, th->dest, inet_iif(skb)); if (nsk) { if (nsk->sk_state != TCP_TIME_WAIT) { //如果不是TIME_WAIT状态 bh_lock_sock(nsk); //加锁后返回sock结构 return nsk; } inet_twsk_put(inet_twsk(nsk)); //释放sock结构 return NULL; } ...... return sk; }
3.tcp_rcv_state_process()函数,根据sock的状态处理数据包
// file: net/ipv4/tcp_input.c int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th, unsigned int len) { ...... switch (sk->sk_state) { //判断sock状态,此时sock状态为LISTEN case TCP_CLOSE: goto discard; case TCP_LISTEN: if (th->ack) //检查ACK标识 return 1; if (th->rst) //检查RST标识 goto discard; if (th->syn) { //检查是否是SYN包 if (th->fin) goto discard; if (icsk->icsk_af_ops->conn_request(sk, skb) < 0) //调用连接函数表的处理函数 return 1; kfree_skb(skb); //释放数据包 return 0; } goto discard; } ...... discard: __kfree_skb(skb); } return 0; }
icsk->icsk_af_ops在创建socket的时候,挂载的是&ipv4_specific结构;
// file: net/ipv4/tcp_ipv4.c const struct inet_connection_sock_af_ops ipv4_specific = { ...... .conn_request = tcp_v4_conn_request, ...... };
所以,icsk->icsk_af_ops->conn_request最终调用的是tcp_v4_conn_request()。
// file: net/ipv4/tcp_ipv4.c int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb) { struct tcp_options_received tmp_opt; struct request_sock *req; struct inet_request_sock *ireq; struct tcp_sock *tp = tcp_sk(sk); struct dst_entry *dst = NULL; __be32 saddr = ip_hdr(skb)->saddr; __be32 daddr = ip_hdr(skb)->daddr; __u32 isn = TCP_SKB_CB(skb)->when; //是否重发 bool want_cookie = false; struct flowi4 fl4; struct tcp_fastopen_cookie foc = { .len = -1 }; struct tcp_fastopen_cookie valid_foc = { .len = -1 }; struct sk_buff *skb_synack; int do_fastopen; /* Never answer to SYNs send to broadcast or multicast */ if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) goto drop; // 查看半连接队列是否已经满了 if (inet_csk_reqsk_queue_is_full(sk) && !isn) { want_cookie = tcp_syn_flood_action(sk, skb, "TCP"); //判断是否开启了tcp_syncookies内核参数(/proc/sys/net/ipv4/tcp_syncookies) if (!want_cookie) //半连接队列满,且未开启tcp_syncookies goto drop; } // sk_acceptq_is_full判断全连接队列是否已满了 // inet_csk_reqsk_queue_young(sk)记录的是半连接队列里面存在,没有被SYN_ACK重传定时器重传过SYN_ACK,同时也没有完成过三次握手的sock数量 if (sk_acceptq_is_full(sk) && inet_csk_reqsk_queue_young(sk) > 1) { NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENOVERFLOWS); goto drop; //半连接队列满了,同时半连接队列里尚未重传过的SYN_ACK报文个数大于1,丢弃报文 } req = inet_reqsk_alloc(&tcp_request_sock_ops); //分配一个request_sock if (!req) goto drop; #ifdef CONFIG_TCP_MD5SIG tcp_rsk(req)->af_specific = &tcp_request_sock_ipv4_ops; #endif tcp_clear_options(&tmp_opt); //清除TCP接收选项结构(tmp_opt是局部结构变量) tmp_opt.mss_clamp = TCP_MSS_DEFAULT; //最大MSS值 tmp_opt.user_mss = tp->rx_opt.user_mss; //用户指定的MSS值 tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc); //分析该请求的tcp各个选项,比如时间戳、窗口大小、快速开启等选项 if (want_cookie && !tmp_opt.saw_tstamp) tcp_clear_options(&tmp_opt); tmp_opt.tstamp_ok = tmp_opt.saw_tstamp; tcp_openreq_init(req, &tmp_opt, skb); //将刚才分析的请求的TCP选项记录到刚刚分配的request_sock中 ireq = inet_rsk(req); //获取INET的连接请求结构 ireq->loc_addr = daddr; ireq->rmt_addr = saddr; ireq->no_srccheck = inet_sk(sk)->transparent; ireq->opt = tcp_v4_save_options(skb); //保存完整的IPV4选项 ...... // 构造 syn+ack 包 skb_synack = tcp_make_synack(sk, dst, req, fastopen_cookie_present(&valid_foc) ? &valid_foc : NULL); if (skb_synack) { __tcp_v4_send_check(skb_synack, ireq->loc_addr, ireq->rmt_addr); skb_set_queue_mapping(skb_synack, skb_get_queue_mapping(skb)); } else goto drop_and_free; if (likely(!do_fastopen)) { int err; // 发送 syn + ack 响应 err = ip_build_and_send_pkt(skb_synack, sk, ireq->loc_addr, ireq->rmt_addr, ireq->opt); err = net_xmit_eval(err); if (err || want_cookie) goto drop_and_free; tcp_rsk(req)->snt_synack = tcp_time_stamp; tcp_rsk(req)->listener = NULL; // request sock添加到半连接队列,并开启重传计时器 inet_csk_reqsk_queue_hash_add(sk, req, TCP_TIMEOUT_INIT); if (fastopen_cookie_present(&foc) && foc.len != 0) NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPFASTOPENPASSIVEFAIL); } else if (tcp_v4_conn_req_fastopen(sk, skb, skb_synack, req)) goto drop_and_free; return 0; drop_and_release: dst_release(dst); drop_and_free: reqsk_free(req); drop: NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS); return 0; }
// file: net/ipv4/ip_output.c int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk, __be32 saddr, __be32 daddr, struct ip_options_rcu *opt) { struct inet_sock *inet = inet_sk(sk); struct rtable *rt = skb_rtable(skb); struct iphdr *iph; /* Build the IP header. */ skb_push(skb, sizeof(struct iphdr) + (opt ? opt->opt.optlen : 0)); skb_reset_network_header(skb); //记录IP头部的位置 iph = ip_hdr(skb); //获取IP头部 iph->version = 4; //设置版本号 iph->ihl = 5; //设置IP头部长度 iph->tos = inet->tos; //设置TOS值 if (ip_dont_fragment(sk, &rt->dst)) //决定IP封包是否可以分段 iph->frag_off = htons(IP_DF); else iph->frag_off = 0; iph->ttl = ip_select_ttl(inet, &rt->dst); iph->daddr = (opt && opt->opt.srr ? opt->opt.faddr : daddr); iph->saddr = saddr; iph->protocol = sk->sk_protocol; ip_select_ident(iph, &rt->dst, sk); if (opt && opt->opt.optlen) { iph->ihl += opt->opt.optlen>>2; ip_options_build(skb, &opt->opt, daddr, rt, 0); //处理IP选项 } skb->priority = sk->sk_priority; skb->mark = sk->sk_mark; /* Send it out. */ return ip_local_out(skb); //发送数据包 }
数据经过IP层处理之后,最终从网卡发送到客户端。至此,第一次握手完成,发起第二次握手。
二、总结
1.根据SYN报文查找到服务端的监听socket(listening_hash表中查找到的);
2.查找是否有对应的半连接socket(第一个SYN报文肯定是没有的);
3.接着检查半连接队列和全连接队列是否满了,满了就丢弃报文;
4.然后就是创建和初始化一个request_sock,表示这个请求;
5.构造 SYN+ACK 包,发送 SYN+ACK 报文给客户端;
6.请求链入半连接队列,开启SYNACK定时器。