来自个人博客: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,无需再延迟发送。