《监视和调整Linux网络协议栈:接收数据》

《监控和调整Linux网络协议栈的图解指南:接收数据》

《Linux网络 - 数据包的接收过程》

《Linux网络协议栈:网络包接收过程》

《Linux内核网络协议栈:udp数据包发送(源码解读)》

《TCP/IP网络协议栈面试经典题目》

《TCP/IP网络协议栈:以太网数据包结构、802.3》

《TCP/IP网络协议栈:ARP协议详解》

《TCP / IP攻击:ARP缓存中毒的基本原理、TCP序列号预测和TCP重置攻击》

《TCP/IP网络协议栈:IP协议》

《TCP/IP网络协议栈:以太网数据包结构、802.3》

《TCP/IP网络协议栈:ARP协议详解》

《TCP / IP攻击:ARP缓存中毒的基本原理、TCP序列号预测和TCP重置攻击》

目录

背景

关闭 TCP 连接过程

sys_close()

sock_release()

inet_release()

tcp_close()

tcp_send_fin()


背景


有一次,光神 在群问了个问题:

当 close 一个 TCP 连接时,如果还有没发送完的数据在缓冲区中,内核会怎么处理?

当时我认为,因为关闭 TCP 连接会触发四次挥手过程,而为了让四次挥手能够快速完成,应该会把发送缓冲区的数据清空,然后发送四次挥手的数据包。

带着疑问,我去查阅 Linux 源码的实现,下面就是关闭一个 TCP 连接的过程。

关闭 TCP 连接过程


关闭一个 TCP 连接可以使用 close() 系统调用,我们来分析一下当调用 close() 关闭一个 TCP 连接时会发生什么事情。

当调用 close() 系统调用时,会触发调用 sys_close() 内核函数,其实现如下:

 sys_close() 


asmlinkage long sys_close(unsigned int fd)
{struct file * filp;struct files_struct *files = current->files;...return filp_close(filp, files);...
}

sys_close() 函数最终会调用 filp_close() 函数来关闭文件(由于在 Linux 中 socket 是一种特殊的文件),我们接着分析 filp_close() 函数的实现:

int filp_close(struct file *filp, fl_owner_t id)
{...fput(filp);return retval;
}void fput(struct file * file)
{...if (atomic_dec_and_test(&file->f_count)) {...if (file->f_op && file->f_op->release)file->f_op->release(inode, file);...}
}

可以看到,最终会调用文件系统对应的 release() 方法来处理关闭操作。对于 socket 文件系统,release() 方法对应的是 sock_close() 函数,而 sock_close() 函数最终会调用 sock_release() 函数,所以我们来看看 sock_release() 函数的实现:

sock_release() 


void sock_release(struct socket *sock)
{if (sock->ops)sock->ops->release(sock);...
}

sock_release() 函数也很简单,就是调用对应 协议族 的 release() 方法,因为 Linux 的 socket 文件系统可以支持多种协议族,比如 INETUnix Domain SocketNetlink 等。而对应 INET协议族(网络) 来说,这个 release() 方法对应的是 inet_release() 函数,inet_release() 函数实现如下:

inet_release() 


int inet_release(struct socket *sock)
{struct sock *sk = sock->sk;if (sk) {long timeout;...timeout = 0;if (sk->linger && !(current->flags & PF_EXITING))timeout = sk->lingertime;sock->sk = NULL;sk->prot->close(sk, timeout);}return(0);
}

inet_release() 函数最终会调用对应 传输层(TCP或者UDP) 的 close() 方法,对于 TCP协议 来说,close() 方法对应的是 tcp_close() 函数,tcp_close() 就是关闭 TCP 连接的最后站点。

tcp_close()


由于 tcp_close() 函数比较复杂,我们这里只分析当发生缓冲区还有数据的情况下,内核会怎么处理缓冲区的数据。


void tcp_close(struct sock *sk, long timeout)
{struct sk_buff *skb;int data_was_unread = 0;...// 如果接收缓冲区有数据, 那么先清空接收缓冲区的数据while((skb= __skb_dequeue(&sk->receive_queue)) != NULL) {u32 len = TCP_SKB_CB(skb)->end_seq - TCP_SKB_CB(skb)->seq - skb->h.th->fin;data_was_unread += len;__kfree_skb(skb);}...if (data_was_unread != 0) {                // 如果接收缓冲区有数据没有处理tcp_set_state(sk, TCP_CLOSE);          // 把socket状态设置为TCP_CLOSEtcp_send_active_reset(sk, GFP_KERNEL); // 发送一个reset包给对端连接} else if (sk->linger && sk->lingertime==0) {...} else if (tcp_close_state(sk)) {tcp_send_fin(sk); // 开始发生四次挥手包}...
}

从 tcp_close() 函数的实现可以看出,关闭过程主要有两种情况:

  • 如果接收缓冲区还有数据没有被用户处理,那么就先把接收缓冲区的数据清空,并且发送一个 reset 包给对端连接。

  • 如果接收缓冲区没有数据,那么就调用 tcp_send_fin() 函数开始进行四次挥手过程。

四次挥手过程如下图:

tcp_send_fin()


接下来,我们分析 tcp_send_fin() 函数的实现:

void tcp_send_fin(struct sock *sk)
{struct tcp_opt *tp = &(sk->tp_pinfo.af_tcp);struct sk_buff *skb = skb_peek_tail(&sk->write_queue); // 发送缓冲区列表最后一个缓冲块unsigned int mss_now;...if (tp->send_head != NULL) {                         // 如果发送缓冲区不为空TCP_SKB_CB(skb)->flags |= TCPCB_FLAG_FIN;        // 把最后一个发送缓冲块设置FIN标志TCP_SKB_CB(skb)->end_seq++;tp->write_seq++;} else {                                             // 如果发送缓冲区为空for (;;) {skb = alloc_skb(MAX_TCP_HEADER, GFP_KERNEL); // 申请一个新的缓冲块if (skb)break;current->policy |= SCHED_YIELD;schedule();}skb_reserve(skb, MAX_TCP_HEADER);skb->csum = 0;TCP_SKB_CB(skb)->flags = (TCPCB_FLAG_ACK | TCPCB_FLAG_FIN); // 设置FIN标志TCP_SKB_CB(skb)->sacked = 0;TCP_SKB_CB(skb)->seq = tp->write_seq;TCP_SKB_CB(skb)->end_seq = TCP_SKB_CB(skb)->seq + 1;tcp_send_skb(sk, skb, 1, mss_now); // 发送给对端连接}...
}

在 tcp_send_fin() 函数我们终于找到了当发送缓冲区不为空的处理,当发送缓冲区不为空时,首先会获取发送缓冲区的最后一个缓冲块,然后把这个缓冲区的 FIN标志位 设置上。

所以我前面的想法是错的,当关闭一个 TCP 连接时,如果发送缓冲区还有数据没发送完,那么内核只会把发送缓冲区最后一个缓冲块设置上 FIN标志,而不是把发送缓冲区清空。

#define TCPCB_FLAG_FIN       0x01
#define TCPCB_FLAG_SYN      0x02
#define TCPCB_FLAG_RST      0x04
#define TCPCB_FLAG_PSH      0x08
#define TCPCB_FLAG_ACK      0x10
#define TCPCB_FLAG_URG      0x20
#define TCPCB_FLAG_ECE      0x40
#define TCPCB_FLAG_CWR      0x80

https://mp.weixin.qq.com/s/zCwSGGt__HB8wUri_xwd0g

http://vger.kernel.org/~davem/tcp_skbcb.html

The TCP SKB control block is defined in as follows:

TCP SKB控制块在 如下:


struct tcp_skb_cb {union {struct inet_skb_parmh4;struct inet6_skb_parmh6;} header;   /* For incoming frames */...

Before input TCP packets are processed by TCP, the upper layer (ipv4 or ipv6) examine the packet first. They also use the SKB control block area to record various bits of per-packet information. For example, ipv4 records the IP header options parsed from the protocol header. In order to not corrupt the protocol level data stored here by ipv4/ipv6, we define this union at the front of the TCP control block.

在TCP处理输入的TCP数据包之前,上层(ipv4或ipv6)首先检查该数据包。他们还使用SKB控制块区域来记录每个数据包信息的各个位。例如,ipv4记录从协议标头解析的IP标头选项。为了不破坏ipv4 / ipv6此处存储的协议级别数据,我们在TCP控制块的前面定义了这个并集。


 ...__u32        seq;        /* Starting sequence number */__u32     end_seq;    /* SEQ + FIN + SYN + datalen */...

These define the TCP sequence numbers covered by the packet. The seq value is simply the sequence number in the TCP packet header on input. As suggested by the comment, the end_seq member is calculated as seq plus the number of data bytes in the TCP packet, plus 1 if the FIN bit is set, and plus 1 if the SYN bit is set.

这些定义了数据包覆盖的TCP序列号。的SEQ值是简单地在输入的TCP数据包报头中的序列号。正如评论所建议的那样, end_seq成员的计算方式为seq加上TCP数据包中的数据字节数,如果FIN位置1,则加1;如果SYN位置1,则加1。


 ...__u32        when;       /* used to compute rtt's   */...

When a TCP packet is sent, we record the current jiffies value here. It is used to later calculate the round trip time estimates, if necessary.

发送TCP数据包时,我们在 此处记录当前的吉菲斯值。如有必要,它可用于稍后计算往返时间估计。


 ...__u8     flags;      /* TCP header flags.        *//* NOTE: These must match up to the flags byte in a*   real TCP header.*/
#define TCPCB_FLAG_FIN      0x01
#define TCPCB_FLAG_SYN      0x02
#define TCPCB_FLAG_RST      0x04
#define TCPCB_FLAG_PSH      0x08
#define TCPCB_FLAG_ACK      0x10
#define TCPCB_FLAG_URG      0x20
#define TCPCB_FLAG_ECE      0x40
#define TCPCB_FLAG_CWR      0x80...

This member records the raw TCP header flags field we will use in the packet we send out. It is used by tcp_transmit_skb() to fill in the TCP header properly.

该成员记录了我们将在发送的数据包中使用的原始TCP标头标志字段。tcp_transmit_skb()使用它 正确填充TCP头。


 ...__u8     sacked;     /* State flags for SACK/FACK.   */
#define TCPCB_SACKED_ACKED  0x01    /* SKB ACK'd by a SACK block   */
#define TCPCB_SACKED_RETRANS    0x02    /* SKB retransmitted        */
#define TCPCB_LOST      0x04    /* SKB is lost          */
#define TCPCB_TAGBITS       0x07    /* All tag bits         */#define TCPCB_EVER_RETRANS    0x80    /* Ever retransmitted frame */
#define TCPCB_RETRANS       (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS)#define TCPCB_URG      0x20    /* Urgent pointer advenced here */#define TCPCB_AT_TAIL     (TCPCB_URG)...

The sacked field holds the retransmission state for a packet. It also indicates whether the packet contains urgent data or not. As SACK packets arrive from the receiver, they are inspected and the TCPCB_TAGBITS fields are updated as needed. Then, the retransmission engine decides whether the retransmit packets or not.

If a retransmit timeout occurs, all of the SACK state tage bits are cleared, and we forget that state.

If an SKB is retransmitted in any way (either via timeout, or via fast retransmit), the TCPCB_EVER_RETRANS bit is set. Both TCPCB_SACK_RETRANS and TCPCB_EVER_RETRANS are set by tcp_retransmit_skb(). The TCPCB_SACK_RETRANS bit is selectively cleared by routines such as tcp_enter_loss().

该落马字段保存重发状态的数据包。它还指示数据包是否包含紧急数据。当SACK数据包从接收器到达时,将对其进行检查,并 根据需要更新TCPCB_TAGBITS字段。然后,重发引擎决定是否重发数据包。

如果发生重传超时,则会清除所有SACK状态有效位,而我们会忘记该状态。

如果以任何方式(通过超时或通过快速重传)重传SKB,则TCPCB_EVER_RETRANS位置1。无论TCPCB_SACK_RETRANS和TCPCB_EVER_RETRANS 被设置tcp_retransmit_skb() 。所述 TCPCB_SACK_RETRANS位被选择性地通过例程,如清除tcp_enter_loss() 。


 ...__u16        urg_ptr;    /* Valid w/URG flags is set.    */__u32     ack_seq;    /* Sequence number ACK'd   */
};#define TCP_SKB_CB(__skb)((struct tcp_skb_cb *)&((__skb)->cb[0]))

The urg_ptr states the URG pointer TCP header value to use if TCPCB_FLAG_URG is set in the flags. The ack_seq is the ACK sequence from the TCP header on input packets.

所述urg_ptr陈述了URG指针TCP报头值,如果使用TCPCB_FLAG_URG在标志中设置。的 ACK_SEQ是从上输入数据包的TCP报头中的ACK序列。

Linux网络协议栈:关闭一个还有没发送数据完的TCP连接相关推荐

  1. Linux网络协议栈:一个TCP链接的耗时

    <一次系统调用开销到底有多大?strace.time.perf命令> 目录 一 正常TCP连接建立过程 二 TCP连接建立时的异常情况 1)客户端connect系统调用耗时失控 2)半/全 ...

  2. 监控和调整Linux网络协议栈的图解指南:接收数据

    Table of Contents 入门 最初设定 数据到达 网络数据处理开始 网络数据处理继续 协议栈和用户态套接字 结论 监视和调整Linux网络协议栈:接收数据(图解):https://rtoa ...

  3. 监视和调整Linux网络协议栈:发送数据

    目录 有关监视和调整Linux网络堆栈的一般建议 总览 详细外观 协议族注册 通过套接字发送网络数据 sock_sendmsg,__sock_sendmsg和__sock_sendmsg_nosec ...

  4. Linux网络协议栈:中断下半部处理

    <Linux中断处理:上半部和下半部> <Linux网络协议栈:中断下半部处理> 目录 数据包上送 网络中断下半部处理 总结 推荐阅读 在<Linux网络协议栈:网络包接 ...

  5. Linux网络协议栈:网络包接收过程

    目录 一 Linux网络收包总览 二 Linux启动 2.1 创建ksoftirqd内核线程 2.2 网络子系统初始化 2.3 协议栈注册 2.4 网卡驱动初始化 2.5 启动网卡 三 迎接数据的到来 ...

  6. 监视和调整Linux网络协议栈:接收数据

    Table of Contents 有关监视和调整Linux网络协议栈的建议 总览 详细外观 网络设备驱动程序 初始化 网络设备初始化 启动网络设备 监控网络设备 调整网络设备 SoftIRQ 什么是 ...

  7. linux 虚拟机大量udp请求失败_理解 Linux 网络栈:Linux 网络协议栈简单总结分析...

    1. Linux 网络路径 1.1 发送端 1.1.1 应用层 (1) Socket 应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的.Linu ...

  8. Linux网络协议栈:NAPI机制与处理流程分析(图解)

    Table of Contents NAPI机制 NAPI缺陷 使用 NAPI 先决条件 非NAPI帧的接收 netif_rx - 将网卡中收到的数据包放到系统中的接收队列中 enqueue_to_b ...

  9. 理解 Linux 网络栈:Linux 网络协议栈简单总结

    1. Linux 网络路径 1.1 发送端 1.1.1 应用层 (1) Socket 应用层的各种网络应用程序基本上都是通过 Linux Socket 编程接口来和内核空间的网络协议栈通信的.Linu ...

最新文章

  1. Java项目:设计管理系统(java+SSM+JSP+MYSQL+layui+Maven)
  2. MySQL断开SpringBoot_数据库连接中断-spring-springBoot
  3. adb logcat 保存_保存的logcat在Android设备上的文本文件
  4. 【从零开始搭建自己的.NET Core Api框架】(二)搭建项目的整体架构
  5. video4linux(v4l)使用摄像头的实例基础教程与体会
  6. 一道数学题引发的世界动荡
  7. 【题解】保安站岗[P2458]皇宫看守[LOJ10157][SDOI2006]
  8. 微信小程序地图和百度地图定位位置不一样
  9. 3个动作精准引流方法,放大操作,每天吸粉200+
  10. 学计算机的一直对画画感兴趣,[电脑绘画兴趣小组教学总结]sai电脑绘画入门教学...
  11. 阅兵方阵-蓝桥杯国赛
  12. React源码解毒 - 检测开发者是否错误的使用了props属性
  13. 语法和语义之间的差异_语法和语义之间的区别
  14. Java中的BigDecimal,你真的会用吗?
  15. 读书笔记(三)--世界上最伟大的推销员
  16. 标准差计算-python(有偏无偏)
  17. 第一次参加kaggle比赛的一些收获与心得,记录一下
  18. 中国铷铯及其化合物行业研究与投资前景报告(2022版)
  19. 数据库四大特性ACID
  20. 源代码如何保密的个人见解

热门文章

  1. Struts2和hibernate框架整合实现简单的注册登陆功能
  2. Solrj 存储一个point类型的字段
  3. leetcode191-打家劫舍
  4. .netframewor划时代的系统:纪念Vista发布5周年
  5. 压测工具下载地址说明及汇总
  6. Python 第十一篇:开发堡垒机
  7. fatal error: openssl/evp.h: 没有那个文件或目录
  8. [裴礼文数学分析中的典型问题与方法习题参考解答]5.1.16
  9. Javascript综合应用小案例
  10. X命名空间-标记扩展