摘要:TRACE是iptables自带的动作,可以追踪符合条件的数据包经过iptbales各个表和链的顺序,是非常方便的iptables调试工具。

问题描述

前不久,在配置 iptables 的规则时,我遇见了一件很奇怪的事情:服务器的 20001 端口是某一 docker 容器 5555 端口的外部映射,我已经打开了这个端口,但是从外部访问此端口时,依然显示 connection refused。

iptables 的完整规则如下:

# Generated by iptables-save v1.4.21 on Mon May 17 21:40:27 2021
*raw
:PREROUTING ACCEPT [160847:20872194]
:OUTPUT ACCEPT [181832:21971180]
COMMIT
# Completed on Mon May 17 21:40:27 2021
# Generated by iptables-save v1.4.21 on Mon May 17 21:40:27 2021
*nat
:PREROUTING ACCEPT [10629:562999]
:INPUT ACCEPT [7171:344376]
:OUTPUT ACCEPT [846:51960]
:POSTROUTING ACCEPT [1076:67148]
:DOCKER - [0:0]
[10299:527998] -A PREROUTING -m addrtype --dst-type LOCAL -j DOCKER
[0:0] -A OUTPUT ! -d 127.0.0.0/8 -m addrtype --dst-type LOCAL -j DOCKER
[565:50464] -A POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.4/32 -d 172.17.0.4/32 -p tcp -m tcp --dport 8080 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.6/32 -d 172.17.0.6/32 -p tcp -m tcp --dport 3000 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.6/32 -d 172.17.0.6/32 -p tcp -m tcp --dport 53 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.6/32 -d 172.17.0.6/32 -p udp -m udp --dport 53 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.7/32 -d 172.17.0.7/32 -p tcp -m tcp --dport 8080 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.5/32 -d 172.17.0.5/32 -p tcp -m tcp --dport 9000 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.5/32 -d 172.17.0.5/32 -p tcp -m tcp --dport 8000 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.3/32 -d 172.17.0.3/32 -p tcp -m tcp --dport 80 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.8/32 -d 172.17.0.8/32 -p tcp -m tcp --dport 80 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.9/32 -d 172.17.0.9/32 -p tcp -m tcp --dport 5555 -j MASQUERADE
[0:0] -A POSTROUTING -s 172.17.0.9/32 -d 172.17.0.9/32 -p tcp -m tcp --dport 80 -j MASQUERADE
[2219:133140] -A DOCKER -i docker0 -j RETURN
[0:0] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 28080 -j DNAT --to-destination 172.17.0.4:8080
[0:0] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 23000 -j DNAT --to-destination 172.17.0.6:3000
[1:44] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 53 -j DNAT --to-destination 172.17.0.6:53
[229:15175] -A DOCKER ! -i docker0 -p udp -m udp --dport 53 -j DNAT --to-destination 172.17.0.6:53
[0:0] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 38080 -j DNAT --to-destination 172.17.0.7:8080
[1:44] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 9000 -j DNAT --to-destination 172.17.0.5:9000
[3:148] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 8000 -j DNAT --to-destination 172.17.0.5:8000
[0:0] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 20080 -j DNAT --to-destination 172.17.0.3:80
[0:0] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 30001 -j DNAT --to-destination 172.17.0.8:80
[1:52] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 20001 -j DNAT --to-destination 172.17.0.9:5555
[0:0] -A DOCKER ! -i docker0 -p tcp -m tcp --dport 20000 -j DNAT --to-destination 172.17.0.9:80
COMMIT
# Completed on Mon May 17 21:40:27 2021
# Generated by iptables-save v1.4.21 on Mon May 17 21:40:27 2021
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [139:17499]
:Blacklist - [0:0]
[146097:18852374] -A INPUT -j Blacklist
[40106:3120882] -A INPUT -p tcp -m tcp --dport 20001 -j ACCEPT
[0:0] -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
[8148:382976] -A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
[552:84484] -A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
[81:20739] -A INPUT -d 127.0.0.1/32 -p tcp -m tcp --dport 3306 -j ACCEPT
[97684:15654705] -A INPUT -m state --state RELATED,ESTABLISHED -m comment --comment "允许已经建立的连接" -j ACCEPT
[204:12222] -A INPUT -i lo -m comment --comment "允许lo口所有流量通过" -j ACCEPT
[4:192] -A INPUT -p tcp -m tcp --dport 9999 -m state --state NEW -m comment --comment "开启APPNODE服务" -j ACCEPT
[7:356] -A INPUT -p tcp -m tcp --dport 8888 -m state --state NEW -m comment --comment "开启APPNODE服务" -j ACCEPT
[0:0] -A INPUT -p tcp -m tcp --dport 20000 -j REJECT --reject-with icmp-port-unreachable
[2964:172367] -A INPUT -m comment --comment "拒绝所有没有显式开启的" -j REJECT --reject-with icmp-host-prohibited
[7:501] -A FORWARD -m comment --comment "拒绝所有没有显式开启的转发" -j REJECT --reject-with icmp-host-prohibited
COMMIT
# Completed on Mon May 17 21:40:27 2021

解决方案

研究规则未果后,我选择使用 iptables 自带的 TRACE 命令帮助进行调试。

TRACE 命令只可以出现在 raw 表中,它会在日志中记录符合条件的数据包经过 iptables 的各个表/链的顺序。在 Centos7 系统中,日志的默认位置是 /var/log/message

TRACE 的使用如下所示:

iptables -t raw -A PREROUTING -p tcp --source <源IP> --dport 20001 -j TRACE

TRACE 输入的日志如下图所示:

image.png

从日志中可以发现,目标数据包被 filter 表的 FORWARD 链阻拦。此时我才想起,Docker 容器的端口映射其实是通过 FORWARD 实现的,而在之前的 iptables 规则中,-A FORWARD -m comment --comment "拒绝所有没有显式开启的转发" -j REJECT --reject-with icmp-host-prohibited 这条规则默认阻断了 FORWARD,导致 Docker 容器端口映射失败,进而产生了 connection refused 错误。