TCP三次握手源码分析(服务端响应SYN)

发布时间 2024-01-10 19:04:53作者: 划水的猫

内核版本: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定时器。