Linux SYN报文接收及发送SYNACK报文

注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4

在分析connect()系统调用时,我们已经发送SYN报文,所以服务端就需要作出回应了。我们依然只分析TCP层的操作。SYN报文到达TCP层由tcp_v4_rcv()接管。

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);
...
    //checksum检查,其实也就是完整性校验
    if (skb_checksum_init(skb, IPPROTO_TCP, inet_compute_pseudo))
        goto csum_error;

    th = tcp_hdr(skb);//获取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);
    TCP_SKB_CB(skb)->tcp_flags = tcp_flag_byte(th);
    TCP_SKB_CB(skb)->tcp_tw_isn = 0;
    TCP_SKB_CB(skb)->ip_dsfield = ipv4_get_dsfield(iph);
    TCP_SKB_CB(skb)->sacked  = 0;

    //根据报文的源和目的地址在established哈希表以及listen哈希表中查找连接
    //对于正要建立的连接,返回的就是listen哈希表的连接
    sk = __inet_lookup_skb(&tcp_hashinfo, skb, th->source, th->dest);
    if (!sk)
        goto no_tcp_socket;

process:
    //如果此时socket状态处于time_wait,那就进入对应的处理流程中
    if (sk->sk_state == TCP_TIME_WAIT)
        goto do_time_wait;
...
    th = (const struct tcphdr *)skb->data;
    iph = ip_hdr(skb);

    sk_mark_napi_id(sk, skb);//记录napi的id
    skb->dev = NULL;

    bh_lock_sock_nested(sk);
    tcp_sk(sk)->segs_in += max_t(u16, 1, skb_shinfo(skb)->gso_segs);
    ret = 0;
    if (!sock_owned_by_user(sk)) {//如果sk没有被用户锁定,即没在使用
        //检查是否需要先进入prequeue队列
        if (!tcp_prequeue(sk, skb))
            ret = tcp_v4_do_rcv(sk, skb);//进入到主处理函数

    //如果用户正在使用,则数据包进入backlog中
    //不太理解的是为什么limit入参是sk_rcvbufsk_sndbuf之和
    } else if (unlikely(sk_add_backlog(sk, skb,
                       sk->sk_rcvbuf + sk->sk_sndbuf))) {
        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;
...

do_time_wait:
    if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
        inet_twsk_put(inet_twsk(sk));
        goto discard_it;
    }

    if (skb->len < (th->doff << 2)) {
        inet_twsk_put(inet_twsk(sk));
        goto bad_packet;
    }
    if (tcp_checksum_complete(skb)) {
        inet_twsk_put(inet_twsk(sk));
        goto csum_error;
    }
    //处理在time_wait状态收到报文的情况
    switch (tcp_timewait_state_process(inet_twsk(sk), skb, th)) {
    case TCP_TW_SYN: {
        struct sock *sk2 = inet_lookup_listener(dev_net(skb->dev),
                            &tcp_hashinfo,
                            iph->saddr, th->source,
                            iph->daddr, th->dest,
                            inet_iif(skb));
        if (sk2) {
            inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
            inet_twsk_put(inet_twsk(sk));
            sk = sk2;
            goto process;
        }
        /* Fall through to ACK */
    }
    case TCP_TW_ACK:
        tcp_v4_timewait_ack(sk, skb);
        break;
    case TCP_TW_RST:
        tcp_v4_send_reset(sk, skb);
        inet_twsk_deschedule(inet_twsk(sk), &tcp_death_row);
        inet_twsk_put(inet_twsk(sk));
        goto discard_it;
    case TCP_TW_SUCCESS:;
    }
    goto discard_it;
}

接收到SYN包后要查看下该报文是否之前已建立的连接,通过__inet_lookup_skb()查找是否有匹配的连接。

static inline struct sock *__inet_lookup_skb(struct inet_hashinfo *hashinfo,
                         struct sk_buff *skb,
                         const __be16 sport,
                         const __be16 dport)
{
    //sk_buff结构体里有一个变量指向sock,即skb->sk
    //但是对于尚未建立连接的skb来说,其sk变量为空,因此会走进__inet_lookup()
    struct sock *sk = skb_steal_sock(skb);
    const struct iphdr *iph = ip_hdr(skb);

    if (sk)
        return sk;
    else
        return __inet_lookup(dev_net(skb_dst(skb)->dev), hashinfo,
                     iph->saddr, sport,
                     iph->daddr, dport, inet_iif(skb));
}
static inline struct sock *__inet_lookup(struct net *net,
                     struct inet_hashinfo *hashinfo,
                     const __be32 saddr, const __be16 sport,
                     const __be32 daddr, const __be16 dport,
                     const int dif)
{
    u16 hnum = ntohs(dport);
    //查找established哈希表
    struct sock *sk = __inet_lookup_established(net, hashinfo,
                saddr, sport, daddr, hnum, dif);
    //查找listen哈希表
    return sk ? : __inet_lookup_listener(net, hashinfo, saddr, sport,
                         daddr, hnum, dif);
}

最终会在listen哈希表中找到该连接,也就是服务端的监听socket。

之后如果当前这个监听socket没有被使用,就会进入prequeue队列中处理,但是由于这是SYN报文,还没有进程接收数据,所以不会进入prequeue的真正处理中。

bool tcp_prequeue(struct sock *sk, struct sk_buff *skb)
{
    struct tcp_sock *tp = tcp_sk(sk);

    //如果设置了/proc/sys/net/ipv4/tcp_low_latency(低时延)参数,默认为0
    //或者用户还没有调用接收函数接收数据,那么不使用prequeue队列
    //ucopy.task会在接收数据函数recvmsg()中设置为接收数据的当前进程
    //所以对于第一个SYN报文,会从以下分支返回
    if (sysctl_tcp_low_latency || !tp->ucopy.task)
        return false;

    if (skb->len <= tcp_hdrlen(skb) &&
        skb_queue_len(&tp->ucopy.prequeue) == 0)
        return false;

    if (likely(sk->sk_rx_dst))
        skb_dst_drop(skb);
    else
        skb_dst_force_safe(skb);

    //加入到prequeue队列尾部
    __skb_queue_tail(&tp->ucopy.prequeue, skb);
    tp->ucopy.memory += skb->truesize;
    //如果prequeue队列长度大于socket连接的接收缓冲区,
    //将prequeue中的数据报文转移到receive_queue中
    if (tp->ucopy.memory > sk->sk_rcvbuf) {
        struct sk_buff *skb1;

        BUG_ON(sock_owned_by_user(sk));
        //从prequeue中摘链
        while ((skb1 = __skb_dequeue(&tp->ucopy.prequeue)) != NULL) {
            sk_backlog_rcv(sk, skb1);//放入backlog中
            NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_TCPPREQUEUEDROPPED);
        }

        tp->ucopy.memory = 0;
    //如果prequeue中有报文了,那么唤醒睡眠的进程来收取报文
    } else if (skb_queue_len(&tp->ucopy.prequeue) == 1) {
        //唤醒sk上睡眠的进程,这里只唤醒其中一个,避免惊群现象
        //至于怎么唤醒,选择哪个唤醒,暂未研究
        wake_up_interruptible_sync_poll(sk_sleep(sk),
                       POLLIN | POLLRDNORM | POLLRDBAND);
        //没有ACK需要发送,重置延时ACK定时器
        if (!inet_csk_ack_scheduled(sk))
            inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,
                          (3 * tcp_rto_min(sk)) / 4,
                          TCP_RTO_MAX);
    }
    return true;
}

既然不会进入到prequeue队列中,那就进入tcp_v4_do_rcv()的处理,这是个主要的报文处理函数。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
    struct sock *rsk;
...
    //SYN报文走的是这里
    if (sk->sk_state == TCP_LISTEN) {
        //查找对应的半连接状态的socket
        struct sock *nsk = tcp_v4_hnd_req(sk, skb);
        if (!nsk)
            goto discard;

        //可知,对于SYN报文,返回的还是入参sk,即nsk=sk
        if (nsk != sk) {
            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);

    //这里是除ESTABLISHED and TIME_WAIT状态外报文的归宿。。。
    if (tcp_rcv_state_process(sk, skb, tcp_hdr(skb), skb->len)) {
        rsk = sk;
        goto reset;
    }
    return 0;
...
}

半连接状态socket通过tcp_v4_hnd_req()查找。

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;
    /* Find possible connection requests. */
    //查找半连接队列,对于SYN报文肯定找不到
    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);

    //再一次查找established哈希表,以防在此期间重传过SYN报文且建立了连接
    //对于SYN报文这里也是返回空的
    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) {
            bh_lock_sock(nsk);
            return nsk;
        }
        inet_twsk_put(inet_twsk(nsk));
        return NULL;
    }

#ifdef CONFIG_SYN_COOKIES
    if (!th->syn)
        sk = cookie_v4_check(sk, skb, &(IPCB(skb)->opt));
#endif
    //所以最终返回的还是原来的连接,即该函数对于SYN报文啥都没做
    return sk;
}

接下来就是进入tcp_rcv_state_process()处理,这个函数处理绝大多数状态的报文处理。

int tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb,
              const struct tcphdr *th, unsigned int len)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct request_sock *req;
    int queued = 0;
    bool acceptable;
    u32 synack_stamp;

    tp->rx_opt.saw_tstamp = 0;

    switch (sk->sk_state) {
    case TCP_CLOSE:
        goto discard;

    case TCP_LISTEN:
...
        if (th->syn) {//LISTEN状态收到SYN报文
            if (th->fin)
                goto discard;
            //这里其实就是将请求放入半连接队列,必要时启动SYNACK定时器
            if (icsk->icsk_af_ops->conn_request(sk, skb) < 0)
                return 1;

            kfree_skb(skb);
            return 0;
        }
        goto discard;
    }
...
}

加入半连接队列通过icsk->icsk_af_ops->conn_request操作。我们知道icsk->icsk_af_ops指向ipv4_specific

const struct inet_connection_sock_af_ops ipv4_specific = {
    .queue_xmit    = ip_queue_xmit,
    .send_check    = tcp_v4_send_check,
    .rebuild_header    = inet_sk_rebuild_header,
    .sk_rx_dst_set     = inet_sk_rx_dst_set,
    .conn_request      = tcp_v4_conn_request,
...
};

所以加入半连接的操作就是由tcp_v4_conn_request()操刀。

int tcp_v4_conn_request(struct sock *sk, struct sk_buff *skb)
{
    /* Never answer to SYNs send to broadcast or multicast */
    if (skb_rtable(skb)->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
        goto drop;

    return tcp_conn_request(&tcp_request_sock_ops,
                &tcp_request_sock_ipv4_ops, sk, skb);
drop:
    NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_LISTENDROPS);
    return 0;
}

tcp_v4_conn_request()对tcp_conn_request()做了一个简单的封装。

int tcp_conn_request(struct request_sock_ops *rsk_ops,
             const struct tcp_request_sock_ops *af_ops,
             struct sock *sk, struct sk_buff *skb)
{
    struct tcp_options_received tmp_opt;
    struct request_sock *req;
    struct tcp_sock *tp = tcp_sk(sk);
    struct dst_entry *dst = NULL;
    __u32 isn = TCP_SKB_CB(skb)->tcp_tw_isn;
    bool want_cookie = false, fastopen;
    struct flowi fl;
    struct tcp_fastopen_cookie foc = { .len = -1 };
    int err;

    //如果开启了syncookies选项,/proc/sys/net/ipv4/
    if ((sysctl_tcp_syncookies == 2 ||
         //或者此时半连接队列已经满了
         //同时isn不是由tcp_timewait_state_process()函数选择
         //那么判断是否需要发送syncookie
         inet_csk_reqsk_queue_is_full(sk)) && !isn) {
        want_cookie = tcp_syn_flood_action(sk, skb, rsk_ops->slab_name);
        //不需要发送syncookies就直接丢弃报文
        if (!want_cookie)
            goto drop;
    }

    //如果全连接队列满了,同时半连接队列里尚未重传过的SYN报文个数大于1
    //那么就直接丢弃报文
    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;
    }

    //都没问题的话,那就分配一个request_sock,表示一个请求
    //这个内存分配是从tcp的slab中分配的
    req = inet_reqsk_alloc(rsk_ops);
    if (!req)
        goto drop;

    inet_rsk(req)->ireq_family = sk->sk_family;

    //af_ops即为tcp_request_sock_ipv4_ops,这个结构体比较重要,请留意
    tcp_rsk(req)->af_specific = af_ops;

    tcp_clear_options(&tmp_opt);
    tmp_opt.mss_clamp = af_ops->mss_clamp;
    tmp_opt.user_mss  = tp->rx_opt.user_mss;
    //分析该请求的tcp各个选项,比如时间戳、窗口大小、快速开启等选项
    tcp_parse_options(skb, &tmp_opt, 0, want_cookie ? NULL : &foc);

    if (want_cookie && !tmp_opt.saw_tstamp)
        tcp_clear_options(&tmp_opt);

    tmp_opt.tstamp_ok = tmp_opt.saw_tstamp;//记录时间戳选项开启情况
    //将刚才分析的请求的TCP选项记录到刚刚分配的request_sock中,即req中
    tcp_openreq_init(req, &tmp_opt, skb);

    af_ops->init_req(req, sk, skb);

    if (security_inet_conn_request(sk, skb, req))
        goto drop_and_free;

    //如果不需要发送syncookies
    //同时isn不是由tcp_timewait_state_process()函数选择
    if (!want_cookie && !isn) {
        //如果开启了time_wait状态连接快速回收
        //即设置/proc/sys/net/ipv4/tcp_tw_recycle
        if (tcp_death_row.sysctl_tw_recycle) {
            bool strict;
            //查找路由
            dst = af_ops->route_req(sk, &fl, req, &strict);

            if (dst && strict &&
                //主要用于判断是否会和该IP的旧连接冲突
                //这里就涉及到nat环境下丢包的问题
                !tcp_peer_is_proven(req, dst, true, tmp_opt.saw_tstamp)) {
                NET_INC_STATS_BH(sock_net(sk), LINUX_MIB_PAWSPASSIVEREJECTED);
                goto drop_and_release;
            }
        }
        /* Kill the following clause, if you dislike this way. */
        //如果没有开启syncookies选项
        else if (!sysctl_tcp_syncookies &&
             //同时,半连接队列长度已经大于syn backlog队列的3/4
             (sysctl_max_syn_backlog - inet_csk_reqsk_queue_len(sk) <
              (sysctl_max_syn_backlog >> 2)) &&
             //并且当前连接和旧连接有冲突
             !tcp_peer_is_proven(req, dst, false, tmp_opt.saw_tstamp)) {
            //很有可能遭受synflood 攻击
            pr_drop_req(req, ntohs(tcp_hdr(skb)->source), rsk_ops->family);
            goto drop_and_release;
        }
        //生成随机报文序列号
        isn = af_ops->init_seq(skb);
    }
    if (!dst) {
        dst = af_ops->route_req(sk, &fl, req, NULL);
        if (!dst)
            goto drop_and_free;
    }

    tcp_ecn_create_request(req, skb, sk, dst);

    //如果要发送syncookies,那就发送
    if (want_cookie) {
        isn = cookie_init_sequence(af_ops, sk, skb, &req->mss);
        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_openreq_init_rwin(req, sk, dst);
    fastopen = !want_cookie && tcp_try_fastopen(sk, skb, req, &foc, dst);
    //这里便是调用tcp_v4_send_synack()发送SYNACK报文了
    err = af_ops->send_synack(sk, dst, &fl, req,
                  skb_get_queue_mapping(skb), &foc);
    if (!fastopen) {
        if (err || want_cookie)
            goto drop_and_free;

        tcp_rsk(req)->listener = NULL;
        //发送报文后将该请求加入半连接队列,同时启动SYNACK定时器
        //调用inet_csk_reqsk_queue_hash_add()完成上述操作
        af_ops->queue_hash_add(sk, req, TCP_TIMEOUT_INIT);
    }

    return 0;
...
}

要加入半连接队列首先要创建一个request_sock,用于表示客户端发起的请求,然后是做一些初始化,其中req->ts_recent后续会用到多次,这个变量表示的就是对端发送报文的时间(前提是对端开启了时间戳选项)。

static inline void tcp_openreq_init(struct request_sock *req,
                    struct tcp_options_received *rx_opt,
                    struct sk_buff *skb)
{
    struct inet_request_sock *ireq = inet_rsk(req);

    req->rcv_wnd = 0;       /* So that tcp_send_synack() knows! */
    req->cookie_ts = 0;
    tcp_rsk(req)->rcv_isn = TCP_SKB_CB(skb)->seq;
    tcp_rsk(req)->rcv_nxt = TCP_SKB_CB(skb)->seq + 1;
    tcp_rsk(req)->snt_synack = tcp_time_stamp;
    tcp_rsk(req)->last_oow_ack_time = 0;
    req->mss = rx_opt->mss_clamp;
    //如果对端开启时间戳,那么记录下这个时间,也就是对方发送SYN报文的时间
    req->ts_recent = rx_opt->saw_tstamp ? rx_opt->rcv_tsval : 0;
    ireq->tstamp_ok = rx_opt->tstamp_ok;//时间戳开启标志
    ireq->sack_ok = rx_opt->sack_ok;
    ireq->snd_wscale = rx_opt->snd_wscale;
    ireq->wscale_ok = rx_opt->wscale_ok;
    ireq->acked = 0;
    ireq->ecn_ok = 0;
    ireq->ir_rmt_port = tcp_hdr(skb)->source;
    //目的端口,也就是当前服务端的监听端口
    ireq->ir_num = ntohs(tcp_hdr(skb)->dest);
}

初始化完这个请求后就要看下这个请求是否有问题。主要检查的就是看是否会和当前ip的上次通讯有冲突。该操作通过tcp_peer_is_proven()检查。

#define TCP_PAWS_MSL    60      /* Per-host timestamps are invalidated
                     * after this time. It should be equal
                     * (or greater than) TCP_TIMEWAIT_LEN
                     * to provide reliability equal to one
                     * provided by timewait state.
                     */
#define TCP_PAWS_WINDOW 1       /* Replay window for per-host
                     * timestamps. It must be less than
                     * minimal timewait lifetime.
                     */
bool tcp_peer_is_proven(struct request_sock *req, struct dst_entry *dst,
            bool paws_check, bool timestamps)
{
    struct tcp_metrics_block *tm;
    bool ret;

    if (!dst)
        return false;

    rcu_read_lock();
    tm = __tcp_get_metrics_req(req, dst);
    if (paws_check) {
        //如果当前ip的上次tcp通讯发生在60s内
        if (tm && (u32)get_seconds() - tm->tcpm_ts_stamp < TCP_PAWS_MSL &&
            //同时当前ip上次tcp通信的时间戳大于本次tcp,或者没有开启时间戳开关
            //从这里看,快速回收打开选项就很容易导致nat环境丢包
            ((s32)(tm->tcpm_ts - req->ts_recent) > TCP_PAWS_WINDOW ||!timestamps))
            ret = false;
        else
            ret = true;
    } else {
        if (tm && tcp_metric_get(tm, TCP_METRIC_RTT) && tm->tcpm_ts_stamp)
            ret = true;
        else
            ret = false;
    }
    rcu_read_unlock();

    return ret;
}

从这个冲突判断上看有这么几个条件:

  1. 同个ip的此次通信和上次通信间隔时间在60s(刚好等于time_wait状态默认持续时间)内
  2. 上次通信时间大于此次通信时间,或者没有开时间戳选项

所以,这样看来在nat环境下就很容易有问题。nat环境下的机器时间可能不统一,也就有可能出现某个机器先发的报文时间比较靠前,后面其他机器发的报文时间比较靠后,那个这个报文就会被丢弃,也就经常出现nat环境下有些机器无法连接网络的问题。

对于这种情况呢,我们注意到这是在快速回收选项开启的前提下才会检查,所以只要把快速回收选项tcp_tw_recycle关闭即可。因此nat环境最好不要打开这个选项。

一切OK的情况下,接着就该发送SYNACK报文了,通过af_ops->send_synack()。上面我们说过af_ops即为tcp_request_sock_ipv4_ops

static const struct tcp_request_sock_ops tcp_request_sock_ipv4_ops = {
    ...
    .route_req  =   tcp_v4_route_req,
    .init_seq   =   tcp_v4_init_sequence,
    .send_synack    =   tcp_v4_send_synack,
    .queue_hash_add =   inet_csk_reqsk_queue_hash_add,
};

因此,SYNACK报文就是通过tcp_v4_send_synack()发送的。

static int tcp_v4_send_synack(struct sock *sk, struct dst_entry *dst,
                  struct flowi *fl,
                  struct request_sock *req,
                  u16 queue_mapping,
                  struct tcp_fastopen_cookie *foc)
{
    const struct inet_request_sock *ireq = inet_rsk(req);
    struct flowi4 fl4;
    int err = -1;
    struct sk_buff * skb;

    //获取路由
    if (!dst && (dst = inet_csk_route_req(sk, &fl4, req)) == NULL)
        return -1;

    //准备synack报文,该报文使用的是用户的send buffer内存
    skb = tcp_make_synack(sk, dst, req, foc);

    if (skb) {
        __tcp_v4_send_check(skb, ireq->ir_loc_addr, ireq->ir_rmt_addr);

        skb_set_queue_mapping(skb, queue_mapping);
        //传到IP层继续处理,组建ip头,然后发送报文
        err = ip_build_and_send_pkt(skb, sk, ireq->ir_loc_addr,
                        ireq->ir_rmt_addr,
                        ireq->opt);
        err = net_xmit_eval(err);
    }

    return err;
}

发送完SYNACK报文,接着就是将该连接放入半连接队列了,同时启动我们的SYNACK定时器。这一动作通过af_ops->queue_hash_add实现,由上面结构体可知,也就是调用inet_csk_reqsk_queue_hash_add()

void inet_csk_reqsk_queue_hash_add(struct sock *sk, struct request_sock *req,
                   unsigned long timeout)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct listen_sock *lopt = icsk->icsk_accept_queue.listen_opt;
    const u32 h = inet_synq_hash(inet_rsk(req)->ir_rmt_addr,
                     inet_rsk(req)->ir_rmt_port,
                     lopt->hash_rnd, lopt->nr_table_entries);
    //添加到半连接队列
    reqsk_queue_hash_req(&icsk->icsk_accept_queue, h, req, timeout);
    //更新半连接队列统计信息,同时开启SYNACK定时器
    inet_csk_reqsk_queue_added(sk, timeout);
}

有关SYNACK定时器的介绍,可以参看TCP SYNACK定时器梳理

至此,TCP层的处理就算是结束了,后面进入IP层处理,然后通过网卡发送出去。

我们大概总结一下总体流程:

  1. 根据SYN报文查找到服务端的监听socket;
  2. 查找是否有对应的半连接socket(第一个SYN报文肯定是没有的);
  3. 接着检查半连接队列和全连接队列是否满了,满了就丢弃报文;
  4. 然后就是创建和初始化一个request_sock,表示这个请求;
  5. 检查该请求是否和同IP的上个连接冲突;
  6. 一起OK的情况下,发送SYNACK报文;
  7. 报文发送完,请求入半连接队列,开启SYNACK定时器。

不过,有个问题不知大家有没有发现,为什么在接收到SYN报文后socket的状态没有改变,变成SYN_RECV呢?理论上收到SYN报文后就应该进入SYN_RECV的,至少从netstat命令查看是这样,书上也都是这么说的,但是代码里确实没有这么做,不知道是怎么回事?虽然我后面知道其实是在服务端接收到第三次握手报文后才会进入SYN_RECV转态,然后转为ESTABLISHED。不解不解。。。

猜你喜欢

转载自blog.csdn.net/u010039418/article/details/80546921
今日推荐