言成言成啊 | Kit Chen's Blog

TCP状态以及CLOSE_WAIT问题排查<未完成>

发布于2024-07-27 00:54:44,更新于2025-04-04 16:02:29,标签:java tcp  文章会持续修订,转载请注明来源地址:https://meethigher.top/blog

生产环境中有个网关,也就是HTTP反向代理,运行一段时间,在Linux服务器中就会出现大量CLOSE_WAIT连接一直不释放,直到把整个服务连接占满,导致程序假死——进程存在,但不能正常工作。

由于代码不是我写的,并且我也只负责生产环境运维。因此,从运维角度、不影响实际业务的情况下,最快解决该问题——实现了个程序保活脚本。

事后总结,我有点TCP常识,能独立发现问题根源,但是对于TCP的状态扭转,掌握的却很稀松,因此特地学习了下。

一、抓包工具

常见抓包工具

工具平台适用场景开源/免费
WiresharkWindows/MacOS全协议是/是
FiddlerEverywhereWindows/MacOS/LinuxHTTP/HTTPS否/否
CharlesWindows/MacOS/LinuxHTTP/HTTPS否/否
tcpdumpWindows/MacOS/Linux全协议是/是

从黑客角度,当我们需要逆向分析一些HTTPS流量时,使用Fiddler或者Charles即可,这两款软件,虽然是付费的,但功能特别强大,适合逆向。

话说,我当时上学时,安装根证书,逆向今日校园APP时,使用的就是IOS上的Charles,并且还买了正版。

当时一开始使用的是Android的HTTPCanary;还有Windows的Fiddler,可以监听Android的流量,进而实现Android抓包。

但是随着APP的升级,HTTPS的流量不再被捕获到了,这其实是证书问题,可以解决,但需要花费更多的精力。

但是Charles不同,开箱即用,主打一个无脑。

这是年轻时一件趣事,有兴趣可以查阅今日校园实现自动监测并提交最新表单 - 言成言成啊

从开发者角度,当我们需要正向分析一些TCP/UPD流量时,使用Wireshark或者tcpdump即可,比较通用,而且免费。

1.1 Wireshark

1.1.1 安装与使用

1.) 下载Wireshark,建议下载portable版本。

2.) Wireshark默认两点配置,个人感觉不太友好。需要进行以下配置

  1. 抓包TCP时,序列号seq是相对于捕获点开始的,是相对序列号,通过配置将其改为绝对序列号。
    • 步骤:编辑-首选项-Protocols-TCP-Relative sequence numbers
  2. 抓包时间点,也是相对于捕获点开始的,是相对时间点。通过配置可以改为绝对时间点。
    • 步骤:视图-时间显示格式-日期和时间

3.) 打开Wireshark,会发现有多个网络适配器,也就是网卡。根据你的需求,监听对应的网卡即可。

4.) 新增过滤器,用于过滤网络请求。

比如我要监听与某个IP的SSL请求,那么过滤器表达式为ssl && ip.addr == 49.233.46.128

比如我要监听DNS域名解析请求,那么过滤器表达式为tcp.port == 53 || udp.port == 53

比如我要监听时钟服务同步请求,那么过滤器表达式为tcp.port == 123 || udp.port == 123

5.) 借助浏览器提供的会话密钥,抓HTTPS明文包。我们会发现如果是Chrome进行抓包的话,即便是HTTPS,也不会乱码。那么Wireshark是否可以像Chrome一样展示明文呢?当然也是可以的。

首先,管理员模式关闭掉所有chrome进程

1
taskkill /f /im "chrome.exe"

其次,设置Chrome启动时,生成sslkey日志

1
"C:\Program Files\Google\Chrome\Application\chrome.exe" --ssl-key-log-file=D:\Desktop\sslkey

之后,Wireshark打开编辑-首选项-Protocols-TLS,指定Master-Secret log filename为Chrome生成的日志文件。

再之后,重启Chrome和Wireshark,浏览器发起请求,Wireshark进行抓包,可以发现已经是明文了。

这个做法,只能解析通过Chrome发出的HTTPS请求。因为他依赖于Chrome生成的TLS会话密钥。

如果像Postman或者curl直接调用,是不行的。无法解析出明文。

1.1.2 参考致谢

我云了,原来wireshark可以抓HTTPS明文包 | MonkeyWie’s Blog

Wireshark抓包分析HTTPS协议_哔哩哔哩_bilibili

Chrome not Firefox are not dumping to SSLKEYLOGFILE variable - Stack Overflow

1.2 tcpdump

1.2.1 安装与使用

命令一把梭

1
2
3
4
5
6
7
8
# 安装tcpdump工具
yum -y install tcpdump
# 查看已有网卡
ip addr
# 监听ens33网卡的流量
tcpdump -i ens33
# 监听ens33网卡的8080端口的tcp流量
tcpdump -i ens33 tcp port 8080

1.2.3 参考致谢

tcpdump(1) man page | TCPDUMP & LIBPCAP

全网最详细的 tcpdump 使用指南 - 王一白 - 博客园

1.3 抓包日志说明

以tcpdump抓包日志为例。格式如下

更详细的内容,也可自行查阅tcpdump的官方文档

1
src > dst: Flags [tcpflags], seq data-seqno, ack ackno, win window, urg urgent, options [opts], length len

其中,具体的含义如下

  • src > dst: src表示源地址,dst表示目的地址。
  • Flags [tcpflags]: tcpflags为简写后的TCP标志位,通过该标志位可知该报文的作用。
  • seq data-seqno: data-seqno为序列号
  • ack ackno: ackno为确认号
  • win window: window表示窗口大小,指示接收方可以缓冲的剩余字节数。
  • urg urgent: urgent为1表示该报文包含紧急数据;为0表示报文不含紧急数据。
  • length len: len表示数据负载的长度,不包括头部。

需要明确ACK与ack不是一个东西,这个会在TCP状态的内容中提到。

TCP标志位有如下类型

标志位简写说明
SYNSSynchronize: 初始化连接
FINFFinish: 终止连接
PSHPPush: 立即将数据推送给接收方
RSTRRest: 重置连接
URGUUrgent: 包中有紧急数据,优先处理
CWRWCongestion Window Reduced: 发送方已响应接收方的ECN标志,并较小拥塞窗口
ECEEECN Echo: 发送方告知接收方,我拥塞了
AEeAdvertisement Echo: 通常出现在使用 TCP-AO(TCP Authentication Option)协议的连接中
ACK.Acknowledgment: 确认收到数据包

二、TCP状态

2.1 TCP Socket API

TCP Socket API 的本质来自操作系统,但不同编程语言对它的封装不同。所有的 TCP 网络通信,其实本质上都基于操作系统内核提供的 系统调用(syscall)。

常用 TCP Socket API 如下

类别相关 API作用
套接字创建socket()创建一个 socket 句柄
地址绑定bind()绑定 socket 到本地 IP 和端口(仅服务器需要)
监听连接listen()让 socket 进入监听模式(仅服务器需要)
接受连接accept()从 accept queue 全连接队列获取一个新的连接
建立连接connect()让客户端连接服务器
数据收发send() / recv()发送和接收数据
write() / read()另一种发送和接收数据的方法
sendto() / recvfrom()用于 UDP(无连接 socket)
连接管理shutdown()关闭 socket 的读/写方向
close()彻底关闭 socket 释放资源
选项设置setsockopt() / getsockopt()设置 socket 选项,如超时、缓冲区大小等
地址解析getaddrinfo() / inet_pton()将主机名/IP 转换成 sockaddr 结构

2.2 状态扭转

2.2.1 三次握手

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sequenceDiagram
participant c as Client
participant s as Server
participant syncq as SyncQueue 半连接队列
participant acceptq as AcceptQueue 全连接队列

s->>s: bind()
s->>s: listen()
Note right of s: LISTEN
c->>c: connect()
c->>s: SYN seq=x
Note left of c: SYNC_SENT
s->>syncq: put
Note right of s: SYN_RCVD
s->>c: SYN + ACK seq=y ack=x+1
Note left of c: ESTABLISHED
c->>s: ACK seq=x+1 ack=y+1
syncq->>acceptq: put
Note right of s: ESTABLISHED

acceptq->>s:
s->>s: accept()

c-->s: transfer data

2.2.2 四次挥手

在计网的教材中,明确以 Client 作为主动关闭方,实际上这个是不对的,因为 Server 也会主动关闭。只是大多数时候,Client 都是作为主动关闭方。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
sequenceDiagram
participant c as 主动关闭方
participant s as 被动关闭方

Note right of s: ESTABLISHED
Note left of c: ESTABLISHED
c-->s: transfer data
c->>c: close()
c->>s: FIN seq=x
Note left of c: FIN_WAIT_1
Note right of s: CLOSE_WAIT
s->>c: ACK seq=y ack=x+1
Note left of c: FIN_WAIT_2

s-->>c: 此时主动关闭方还需要接收被动关闭方的数据,用于连接关闭的善后处理,这个过程叫半关闭状态,但是这个过程是单向的。假设又发送了n条数据
s->>s: close()
s->>c: FIN + ACK seq=y+n+1 ack=x+1
Note right of s: LAST_ACK
Note left of c: TIME_WAIT
c->>s: ACK seq=x+1 ack=y+n+2
Note left of c: CLOSED
Note right of s: CLOSED

二、复现CLOSE_WAIT

2.1 TCP

2.2 HTTP

发布:2024-07-27 00:54:44
修改:2025-04-04 16:02:29
链接:https://meethigher.top/blog/2024/tcp-state/
标签:java tcp 
付款码 打赏 分享
Shift+Ctrl+1 可控制工具栏