从 level-ip 源码来看 TCP 如何接收消息

来自个人博客:http://www.coolsite.top/archives/368

陈硕的书中有说到过网络编程有三个层次:

● 读过教程和文档,做过练习
● 熟悉本系统 TCP/IP 协议栈的脾气
● 自己写过一个简单的 TCP/IP stack

个人觉得自己第一层次已经没有什么问题,自己编写过一些经典的的网络编程代码(chat、echo、proxy),能够不错地运行在开发环境;第二层次通过自己开发的 serverlite 网络库也对网络编程和 TCP/IP 的一些坑有所了解,欠缺一些线上调试和解决问题的经验,这个需要多年的工作积累目前尚未达到,所以第二层次算是入门。但经验积累不是一蹴而就的,交给时间。但好奇心驱使下想在这里一窥第三层次的感觉,但内核代码又过于庞大冗长,因此这里想分享一个应用层的 TCP/IP 协议栈源码,代码简洁且严格遵从 TCP/IP 协议规范,便于阅读理解。

level-ip 源码地址:https://github.com/saminiir/level-ip


本文主要分享 TCP 接收消息的部分。

相关函数如下,tcp_input_state 是消息到达网卡后,经过层层传递和解包(链路层和网络层),最终到达的地方,其接收的参数有三个,分别是 struct sock* sk,表示一个传输层相关数据;struct tcphdr* th,表示 tcp 头信息,struct sk_buff *skb表示完整的一个包数据。

所以该函数的工作就是根据当前 TCP 连接的状态和数据包的头信息,来对接收的数据包做不同的处理,是 TCP 接收数据的核心函数。

/*
 * Follows RFC793 "Segment Arrives" section closely
 */ 
int tcp_input_state(struct sock *sk, struct tcphdr *th, struct sk_buff *skb)
{
    struct tcp_sock *tsk = tcp_sk(sk);
    struct tcb *tcb = &tsk->tcb;

    tcpsock_dbg("input state", sk);

    switch (sk->state) {
    // 如果当前连接已经关闭,则丢掉数据包,并返回 RST 包
    case TCP_CLOSE:
        return tcp_closed(tsk, skb, th);
    // 如果当前连接是监听状态,不处理任何数据包,丢弃之
    case TCP_LISTEN:
        return tcp_listen(tsk, skb, th);
    // 如果当前连接是三次握手过程中,则根据情况返回 ack 或者 syn&ack
    case TCP_SYN_SENT:
        return tcp_synsent(tsk, skb, th);
    }

    /* "Otherwise" section in RFC793 */

    // 首先检查序列号是否合法
    /* first check sequence number */
    if (!tcp_verify_segment(tsk, th, skb)) {
        /* RFC793: If an incoming segment is not acceptable, an acknowledgment
         * should be sent in reply (unless the RST bit is set, if so drop
         *  the segment and return): */
        // 如果数据包序列号不正常,则发送 ack 包
        if (!th->rst) {
            tcp_send_ack(sk);
        }
        // 丢弃该数据包
        return_tcp_drop(sk, skb);
    }

    // 如果是RST包,则丢弃数据包,直接进入TIME_WAIT状态,唤醒recv系统调用
    /* second check the RST bit */
    if (th->rst) {
        free_skb(skb);
        tcp_enter_time_wait(sk);
        tsk->sk.ops->recv_notify(&tsk->sk);
        return 0;
    }
    
    /* third check security and precedence */
    // Not implemented

    // 如果非SYN_SEND状态收到了SYN包,则发送challenge包,也就是发送syn ack包
    /* fourth check the SYN bit */
    if (th->syn) {
        /* RFC 5961 Section 4.2 */
        tcp_send_challenge_ack(sk, skb);
        return_tcp_drop(sk, skb);
    }
    
    // 如果没有ACK标志,则直接丢弃
    /* fifth check the ACK field */
    if (!th->ack) {
        return_tcp_drop(sk, skb);
    }

    // ACK bit is on
    switch (sk->state) {
    case TCP_SYN_RECEIVED:
        // 三次握手成功,状态置为ESTABLISHED
        if (tcb->snd_una <= th->ack_seq && th->ack_seq < tcb->snd_nxt) {
            tcp_set_state(sk, TCP_ESTABLISHED);
        // 三次握手SYN的ACK包确认号有误,直接丢弃
        } else {
            return_tcp_drop(sk, skb);
        }
    case TCP_ESTABLISHED:
    case TCP_FIN_WAIT_1:
    case TCP_FIN_WAIT_2:
    case TCP_CLOSE_WAIT:
    case TCP_CLOSING:
    case TCP_LAST_ACK:
        // 收到数据包,更新TCP最小未确认号,重新计算RTO,
        // 删除发送队列中已确认的消息,并清除重传定时器
        if (tcb->snd_una < th->ack_seq && th->ack_seq <= tcb->snd_nxt) {
            tcb->snd_una = th->ack_seq;
            /* Any segments on the retransmission queue which are thereby
               entirely acknowledged are removed. */
            tcp_rtt(tsk);
            tcp_clean_rto_queue(sk, tcb->snd_una);
        }
        // 丢弃重复的ACK,也就是已被确认过的ACK
        if (th->ack_seq < tcb->snd_una) {
            // If the ACK is a duplicate, it can be ignored
            return_tcp_drop(sk, skb);
        }
        // 收到了未发送数据的ACK,丢弃之
        if (th->ack_seq > tcb->snd_nxt) {
            // If the ACK acks something not yet sent, then send an ACK, drop segment
            // and return
            // TODO: Dropping the seg here, why would I respond with an ACK? Linux
            // does not respond either
            //tcp_send_ack(&tsk->sk);
            return_tcp_drop(sk, skb);
        }
        // 已确认部分数据,窗口可以右移
        if (tcb->snd_una < th->ack_seq && th->ack_seq <= tcb->snd_nxt) {
            // TODO: Send window should be updated
        }

        break;
    }

    // 如果收到了ACK包,且发送队列为空,说明是FIN的ACK包
    /* If the write queue is empty, it means our FIN was acked */
    if (skb_queue_empty(&sk->write_queue)) {
        switch (sk->state) {
        // 正常流程,进入FIN_WAIT_2
        case TCP_FIN_WAIT_1:
            tcp_set_state(sk, TCP_FIN_WAIT_2);
        case TCP_FIN_WAIT_2:
            break;
        // 半关闭状态收到FIN ACK直接进入TIME_WAIT
        case TCP_CLOSING:
            /* In addition to the processing for the ESTABLISHED state, if
             * the ACK acknowledges our FIN then enter the TIME-WAIT state,
               otherwise ignore the segment. */
            tcp_set_state(sk, TCP_TIME_WAIT);
            break;
        // LASK_ACK收到FIN ACK表示被动端四次挥手完成,连接结束
        case TCP_LAST_ACK:
            /* The only thing that can arrive in this state is an acknowledgment of our FIN.  
             * If our FIN is now acknowledged, delete the TCB, enter the CLOSED state, and return. */
            free_skb(skb);
            return tcp_done(sk);
        // TIME_WAIT下收到FIN ACK,说明发给被动端的FIN ACK丢失,重传FIN ACK,并重启TIME_WAIT定时器
        case TCP_TIME_WAIT:
            /* TODO: The only thing that can arrive in this state is a
               retransmission of the remote FIN.  Acknowledge it, and restart
               the 2 MSL timeout. */
            if (tcb->rcv_nxt == th->seq) {
                tcpsock_dbg("Remote FIN retransmitted?", sk);
//                tcb->rcv_nxt += 1;
                tsk->flags |= TCP_FIN;
                tcp_send_ack(sk);
            }
            break;
        }
    }
    
    /* sixth, check the URG bit */
    if (th->urg) {

    }

    int expected = skb->seq == tcb->rcv_nxt;

    /* seventh, process the segment txt */
    switch (sk->state) {
    // 处理收到的数据包,放入接收队列
    case TCP_ESTABLISHED:
    case TCP_FIN_WAIT_1:
    case TCP_FIN_WAIT_2:
        if (th->psh || skb->dlen > 0) {
            tcp_data_queue(tsk, th, skb);
        }
                
        break;
    // 忽略数据包
    case TCP_CLOSE_WAIT:
    case TCP_CLOSING:
    case TCP_LAST_ACK:
    case TCP_TIME_WAIT:
        /* This should not occur, since a FIN has been received from the
           remote side.  Ignore the segment text. */
        break;
    }

    /* eighth, check the FIN bit */
    if (th->fin && expected) {
        tcpsock_dbg("Received in-sequence FIN", sk);

        // CLOSE LISTEN SYN_SENT 下直接丢弃FIN ACK包
        switch (sk->state) {
        case TCP_CLOSE:
        case TCP_LISTEN:
        case TCP_SYN_SENT:
            // Do not process, since SEG.SEQ cannot be validated
            goto drop_and_unlock;
        }

        // 回复FIN包的ACK,并唤醒recv系统调用
        tcb->rcv_nxt += 1;
        tsk->flags |= TCP_FIN;
        sk->poll_events |= (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND);
        
        tcp_send_ack(sk);
        tsk->sk.ops->recv_notify(&tsk->sk);

        switch (sk->state) {
        // 被动端收到FIN,进入CLOSE_WAIT
        case TCP_SYN_RECEIVED:
        case TCP_ESTABLISHED:
            tcp_set_state(sk, TCP_CLOSE_WAIT);
            break;
        // 主动端收到FIN和ACK
        case TCP_FIN_WAIT_1:
            // 如果发送队列为空收到FIN和ACK,则可直接进入TIME_WAIT状态
            /* If our FIN has been ACKed (perhaps in this segment), then
               enter TIME-WAIT, start the time-wait timer, turn off the other
               timers; otherwise enter the CLOSING state. */
            if (skb_queue_empty(&sk->write_queue)) {
                tcp_enter_time_wait(sk);
            } else {
                tcp_set_state(sk, TCP_CLOSING);
            }

            break;
        // 正常流程,FIN_WAIT_2收到FIN进入TIME_WAIT,启动TIME_WAIT定时器,关闭其他定时器
        case TCP_FIN_WAIT_2:
            /* Enter the TIME-WAIT state.  Start the time-wait timer, turn
               off the other timers. */
            tcp_enter_time_wait(sk);
            break;
        case TCP_CLOSE_WAIT:
        case TCP_CLOSING:
        case TCP_LAST_ACK:
            /* Remain in the state */
            break;
        // TIME_WAIT下收到FIN和ACK,重启TIME_WAIT定时器
        case TCP_TIME_WAIT:
            /* TODO: Remain in the TIME-WAIT state.  Restart the 2 MSL time-wait
               timeout. */
            break;
        }
    }

    /* Congestion control and delacks */
    switch (sk->state) {
    case TCP_ESTABLISHED:
    case TCP_FIN_WAIT_1:
    case TCP_FIN_WAIT_2:
        if (expected) {
            tcp_stop_delack_timer(tsk);
            int pending = min(skb_queue_len(&sk->write_queue), 3);
            // 如果没有正在发送的消息,则发送不大于3个的发送队列中的数据包,拥塞窗口这里固定3
            /* RFC1122:  A TCP SHOULD implement a delayed ACK, but an ACK should not
             * be excessively delayed; in particular, the delay MUST be less than
             * 0.5 seconds, and in a stream of full-sized segments there SHOULD 
             * be an ACK for at least every second segment. */
            if (tsk->inflight == 0 && pending > 0) {
                tcp_send_next(sk, pending);
                tsk->inflight += pending;
                tcp_rearm_rto_timer(tsk);
            // 延迟次数大于1,且数据量大于1000,直接发送ACK,无需延迟确认
            } else if (th->psh || (skb->dlen > 1000 && ++tsk->delacks > 1)) {
                tsk->delacks = 0;
                tcp_send_ack(sk);
            // 如果有数据,启动ACK延迟确认定时器,200ms后才发ACK包
            } else if (skb->dlen > 0) {
                tsk->delack = timer_add(200, &tcp_send_delack, &tsk->sk);
            }
        }
    }

    free_skb(skb);

unlock:
    return 0;
drop_and_unlock:
    tcp_drop(sk, skb);
    goto unlock;
}

收到 TCP 数据的时候,首先处理三种特殊情况:
● TCP 连接已关闭,直接返回 RST 包
● TCP 为监听状态,不做任何处理,直接丢弃
● 三次握手状态,根据握手先后顺序发出 ACK 或者 SYN ACK

处理完特殊情况,则开始正式处理 TCP 数据,首先是校验 TCP 序列号是否合法,判断的方式其实就是判断 TCP 头的序列号是否接收窗口内,或者接收窗口是否为0。

static int tcp_verify_segment(struct tcp_sock *tsk, struct tcphdr *th, struct sk_buff *skb)
{
    struct tcb *tcb = &tsk->tcb;
	// 接收窗口为 0
    if (skb->dlen > 0 && tcb->rcv_wnd == 0) return 0;
	// 序列号不在接收窗口内
    if (th->seq < tcb->rcv_nxt ||
        th->seq > (tcb->rcv_nxt + tcb->rcv_wnd)) {
        tcpsock_dbg("Received invalid segment", (&tsk->sk));
        return 0;
    }

    return 1;
}

然后是判断是否为 RST 包,如果收到了 RST 包则该 TCP 连接直接进入 TIME_WAIT 状态,丢弃数据包,并唤醒阻塞在 read/recv 上的系统调用(当我们阻塞的方式读 socket 套接字的时候,这里就会直接返回)。

第三是检查安全和优先级,这里未实现就先略过。

第四是检查是否有 SYN 标志位,如果有的话说明该连接处于非监听、非三次握手、非关闭的状态下收到了 SYN 包,这里根据 RFC 5961 Section 4.2,会发送一个 challenge 包,也就是一个带有正确序列号的 ACK 包。

第五是检查是否为 ACK 包,如果不是直接丢弃。(TCP 非监听、非三次握手、非关闭下收到的包必须是 ACK 包)。
接下来就需要根据当前连接的状态来进行不同的处理:
● SYN_RECEIVED 状态下收到 ACK 包,表示三次握手成功,当然需要校验序列号是否合法。
● 其他非监听、非三次握手、非关闭、非 TIME_WAIT 下需要判断序列号是否合法(是否在发送滑动窗口内),如果合法则窗口往右滑动,重新计算 rto,清除发送队列中已确认的包,并清除旧的 rto 定时器。不合法则直接丢弃数据。

如果当前发送队列为空,且收到了 ACK 包,那么可以断定该 ACK 包是 FIN 的 ACK 包(因为 SYN 的 ACK 前面已经处理,如果是数据包的 ACK 发送队列一定不为空),因此接下来是处理四次握手过程。
既然是 FIN 的 ACK 包,那么有这么几种情况:
● FIN_WAIT_1 下直接转到 FIN_WAIT_2,正常四次握手流程。
● FIN_WAIT_2 下不做任何处理。
● CLOSING 收到 ACK 直接转入 TIME_WAIT 状态,该状态下就是这样的逻辑。
● LAST_ACK 收到了 ACK 说明四次握手结束(被动关闭端),释放相关数据,连接关闭。
● TIME_WAIT 下收到 ACK 说明对端未收到 ACK 包重发了 ACK(前提是该ACK序列号是正确的),重传ACK,且重置 TIME_WAIT 定时器。

第六是处理紧急指针,这里并未实现。

第七才是真正开始处理数据包,仅在 ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2 状态下才收数据包,把数据包放入接收队列中,recv 系统调用才会去接收队列中取数据。

int tcp_data_queue(struct tcp_sock *tsk, struct tcphdr *th, struct sk_buff *skb)
{
    struct sock *sk = &tsk->sk;
    struct tcb *tcb = &tsk->tcb;
    int rc = 0;

    if (!tcb->rcv_wnd) {
        free_skb(skb);
        return -1;
    }
	// 是否是期望收到的包,也就是按理论顺序到来的包
    int expected = skb->seq == tcb->rcv_nxt;
    if (expected) {
        // 后移滑动窗口
        tcb->rcv_nxt += skb->dlen;
    	// 放入接收队列
        skb->refcnt++;
        skb_queue_tail(&sk->receive_queue, skb);
    	// 重排乱序包
        tcp_consume_ofo_queue(tsk);
    	// 唤醒 recv,设置 poll 状态
        // There is new data for user to read
        sk->poll_events |= (POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND);
        tsk->sk.ops->recv_notify(&tsk->sk);
    } else {
        // 如果收到的是乱序包,那么插入乱序队列
        /* Segment passed validation, hence it is in-window
           but not the left-most sequence. Put into out-of-order queue
           for later processing */
        tcp_data_insert_ordered(&tsk->ofo_queue, skb);
    	// sack 算法
        if (tsk->sackok) {
            tcp_calculate_sacks(tsk); 
        }
         // 如果收到了乱序包,那么需要立即重发一个 ACK      
        /* RFC5581: A TCP receiver SHOULD send an immediate duplicate ACK when an out-
         * of-order segment arrives.  The purpose of this ACK is to inform the
         * sender that a segment was received out-of-order and which sequence
         * number is expected. */
        tcp_send_ack(sk);
    }
    
    return rc;
}

第八是检查是 FIN 标志,如果存在 FIN 标志那么证明该包是一个 FIN + ACK 包,也就是说明该包是属于四次挥手的第二+第三次。如果是 CLOSE、LISTEN、SYN_SENT 下直接丢弃 FIN + ACK 包,否则回一个 ACK 包。
然后分以下几种情况:
● SYN_RECEIVED、ESTABLISHED 状态下,直接置为 CLOSE_WAIT 状态, 也就是被动关闭的一方。
● FIN_WAIT1 情况下,需要判断发送队列是否为空,如果为空直接转为 TIME_WAIT 状态,说明该 ACK 是 FIN 包的 ACK,说明第二+第三次握手完成。如果不为空,则进入 CLOSING 状态,说明自己即发送了 FIN 包也收到了 FIN 包,但 ACK 是数据包的 ACK 而不是 FIN 的 ACK。
● FIN_WAIT2 下收到 FIN,则进入 TIME_WAIT 状态,这个是正常四次握手流程。
● TIME_WAIT 情况下收到 FIN,则重置 TIME_WAIT 定时器。

最后是进行拥塞控制和 ACK 延迟确认,这里没有实现拥塞控制的算法,拥塞窗口固定为3,每次从发送队列取最多三个包依次发出。

如果数据包设置了 psh 选项,或者数据的大小超过 1000 且已经被延迟过,则直接发 ACK,无需再延迟发送。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值