背景
最近在做一个项目,使用一个公司内部开发的消息通信模块遇到了一个问题,就是进行本地socket通信,连接消息服务进程监听的一个端口时发现TCP连接始终建立不了,netstat查看端口始终处于SYN_SENT状态,当时花了好几天才最终定位出原因,是因为这个端口在iptables中设置了规则,只允许特定UID的进程能连接这个端口,由于没加规则,导致socket通信始终建立不起来。
经此一事,就反思后续这类问题该如何去定位,由于问题原因是由iptables导致的,所以在寻找有没有手段去分析数据包是否被拦截。
问题场景还原
问题场景描述
1、有A、B两个进程,B进程监听端口PortB
2、iptables设置了INPUT
规则,任何发往端口PortB的数据包都会被丢弃(DROP)
3、A进程调用connect
接口连接 127.0.0.1 的端口PortB,netstat命令查看端口PortB的连接,状态始终为SYN_SENT
问题场景构造
1、分别构造一个client和server程序,client连接server特定端口,这里从网上找了一个python写的client-server程序 tcp_sixteen.py,程序默认连接1060端口。
1)启动server,执行
1 | python3 tcp_sixteen.py server 127.0.0.1 |
2)启动client,执行
1 | python3 tcp_sixteen.py client 127.0.0.1 |
client会向server发送一个消息,server收到消息后会返回一个响应。server端输出如下:
client端输出如下:
由图可知,client端 (127.0.0.1, 41292)
连接server端 (127.0.0.1, 1060)
。client端的端口是任意的,server端的端口固定是1060
2、新增一条iptables的filter表INPUT
规则,拦截传给端口1060的数据包,执行命令
1 | sudo iptables -A INPUT -p tcp --dport 1060 -j DROP |
可以查看当前filter表的规则
1 | sudo iptables -t filter -L -v -n --line-numbers |
已新增一条规则:丢弃(DROP)所有发往1060端口的数据包
3、再一次执行client,向server发送消息,此时由于iptables规则,client发送消息超时
使用netstat
查看端口1060的连接情况,发现有个连接127.0.0.1:59346 -> 127.0.0.1:1060
一直处于SYN_SENT状态,说明iptables规则生效了,已还原问题场景
1 | sudo netstat -tunalp | grep :1060 |
问题定位
预备知识
由于涉及到iptables使用,需要了解关键概念:表Table、链Chain,还有使用iptables的TRACE
功能,需要设置并查看跟踪日志。这些预备知识可以参考下面文章进行学习:
- ArchLinux Wiki: iptables
- Chapter 6. Traversing of tables and chains
- 鸟哥私房菜:7.2.2 iptables的表格(table)与链(chain)
- 数据包如何游走于 Iptables 规则之间?
IPTables Trace
要定位是否由iptables设置的规则拦截导致的,这里需要用到iptables的TRACE
功能。要使用TRACE
功能,首先需要了解一个数据包从client发往server,需要经过iptables哪些表和链
搬一张ArchLinux Wiki的iptables教程中的图
由上图可以看到,本节点进程A发数据包给同节点的进程B,需要经过的表和链如下:
1、进程A发出数据包(从图中 Local Process 节点开始)
- Local Process
- Routing Decision
- raw表OUTPUT链
- mangle表OUTPUT链
- nat表OUTPUT链
- filter表OUTPUT链
- Routing Decision
- mangle表POSTROUTING链
- nat表POSTROUTING链
- NETWORK
2、发往进程B的数据包(从图中最顶端的节点 NETWORK开始)
- NETWORK
- raw表PREROUTING链
- mangle表PREROUTING链
- nat表PREROUTING链
- Routing Decision
- mangle表INPUT链
- filter表INPUT链
- Local Process
TRACE
功能只能在iptables的raw表中使用,需要在raw表的链中添加规则,跟踪特定数据包,输出其经过的表和链到日志中,通过分析日志可以知道哪些规则拦截了数据包
可以看出,本节点进程A和B间的TCP通信会经过raw表的OUTPUT、PREROUTING链,可以在其中一个新增trace规则进行数据包跟踪
Trace Packet
在raw表OUTPUT链中新增目的端口1060的trace规则,执行以下命令
1 | sudo iptables -t raw -A OUTPUT -p tcp --dport 1060 -j TRACE |
再次执行client,向server发消息,使用netstat
查看端口1060连接情况
client尝试从端口55714发送TCP请求给端口1060,但是都没收到server的ACK。这时可以查看内核日志,grep下trace关键字
1 | sudo dmesg | grep -i trace |
trace规则抓到日志如下
1 | // 第一次TCP请求 |
日志中关键字段:
- 源端口Source Port:关键字
SPT=
- 目的端口Destination Port:关键字
DPT=
- 经过的规则:关键字
TRACE: xxx表:xxx链:<policy|rule>:规则ID
从第一次TCP请求日志可以获取到以下信息:
TCP请求源端口为55714,目的端口为1060
经过的规则顺序为:
- raw表OUTPUT链policy 2
- filter表OUTPUT链policy 1
- raw表PREROUTING链policy 1
- filter表INPUT链rule 1
可以分析出从端口55714发送的TCP请求最终只走到filter表INPUT链的规则1,说明这个规则可能导致TCP请求未发到server端。通过iptables命令查看这个规则:
1 | sudo iptables -t filter -L -v -n --line-numbers |
规则1刚好就是之前新增的拦截规则,至此可以得出结论是由于filter表的INPUT规则导致TCP请求未成功发送