网络配置与抓包:把包看穿
第 8 章教你把网络接通,这一章教你把网络看清。"连不上""时快时慢""莫名被拒"这三类悬案,靠猜是猜不出来的,只能靠三条命令给出事实:ss 看连接状态、tcpdump 看包到底有没有到、nftables 看包该不该放行。先把"事实"拿到手,再谈结论。
netfilter 五个钩子:包在 Linux 里到底走的哪条路
是什么:netfilter 是内核里的一套包处理框架,它是真正干活的;iptables、nftables、ufw、firewalld 全都只是"给 netfilter 下命令的前端"。内核在包的处理路径上埋了五个钩子(hook),包经过哪个钩子,决定了你能不能用某条规则拦住它。
为什么:新手最常见的困惑就是"我明明写了放行规则,为什么还是不通"——十有八九是规则写在了错误的链上。比如宿主机上的容器流量走的是 FORWARD 而不是 INPUT,你在 INPUT 上写再多也没用。
路一个包进出的完整路径
① 从网卡进来 → PREROUTING(还没决定去哪,DNAT 目标地址转换在这里做)→ 内核判断"目的地址是不是本机":
├─ 是本机 → INPUT → 交给本机进程;本机进程回包时走 OUTPUT → POSTROUTING(SNAT 源地址转换在这)→ 出网卡。
└─ 不是本机(要转发) → FORWARD → POSTROUTING → 出网卡。容器、NAT 网关、路由器上的流量走的都是这条路。
记法:本机自己的流量走 INPUT/OUTPUT,帮别人转的流量走 FORWARD。只放行 INPUT,等于只允许别人访问你,没允许你帮别人转发。
| 钩子 | 处理的是哪些包 | 典型用途 |
|---|---|---|
| PREROUTING | 刚从网卡进来、还没判断去向的包。 | DNAT:把 外网IP:8080 转到 内网IP:80。 |
| INPUT | 目标是本机进程的包。 | 放行 SSH / HTTP,拦扫描(最常见的一条链)。 |
| FORWARD | 只是路过、要转给别人的包。 | 容器、K8s、NAT 网关、软路由。 |
| OUTPUT | 本机进程自己发出去的包。 | 限制本机访问外网(出站管控)。 |
| POSTROUTING | 马上要离开网卡的包(无论来自本机还是转发)。 | SNAT / MASQUERADE:让内网机器共用出口 IP。 |
"连不上"的排队排查顺序:从上往下,哪一步断,问题就在哪一层
服务监听在 127.0.0.1:8080 时,只有本机能连,外面怎么调防火墙都没用。这是"本机能 curl 通、外部连不上"的头号原因。判断方法就一条:ss -tlnp 的 Local Address 列写的是 127.0.0.1:8080 还是 0.0.0.0:8080。先改监听地址,再怀疑防火墙。
iptables:四张表、五条链,老牌但还没死
是什么:iptables 用"表(table)+ 链(chain)+ 规则(rule)"描述防火墙策略。表决定"干什么事",链决定"在哪个钩子干"。四张表按优先级从高到低是:raw → mangle → nat → filter,不写 -t 时默认操作 filter 表(我们 90% 的时间只用它)。
为什么还要学它:虽然新发行版底层已经换成 nftables,但 Docker、大量运维脚本、无数网上的教程还在用 iptables 语法;而且 RHEL 9 / Ubuntu 24.04 上你敲的 iptables 命令其实是 iptables-nft 兼容层,它在背后翻译成 nftables 规则。看得懂 iptables,才看得懂 Docker 往你机器里塞了什么。
怎么用:先看,再改;改完立刻保存。
| 动作 | 含义与典型场景 |
|---|---|
-A | Append,追加到链的末尾。前面如果有 DROP 规则,你这条永远轮不到——这是"我放行了却还是不通"的第一嫌疑人。 |
-I | Insert,插到最前面(或 -I INPUT 2 插到第 2 条)。救火时补放行 SSH,一律用它。 |
-D | Delete,按序号或规则原文删除。 |
-P | Policy,设置链的默认策略。只对链自身生效,对 FORWARD 等其他链无效,别以为改了 INPUT 就全关了。 |
远程服务器上执行 iptables -P INPUT DROP 之前,必须先确认 22 端口已经在放行规则里、且位置在前面。改完不要关掉当前终端,新开一个窗口验证还能连上,再结束工作。真锁死了,只能去云厂商控制台的 VNC / 救援终端救砖。同一台机器上执行 iptables -F(清空规则)更是高危动作——它同时会清掉放行 SSH 的规则,而默认策略若是 DROP,你就立刻失联。
Docker 共存注意:Docker 会自己往 iptables 里插规则,别跟它抢
别别和 Docker 抢方向盘
不建议手工去改 Docker 建的 DOCKER / DOCKER-ISOLATION 链——它们在 dockerd 重启或容器增删时会被重建,你的规则会凭空消失。想限制容器端口对外的访问,用 DOCKER-USER;想彻底不让 Docker 动 iptables,用 dockerd --iptables=false(但那样端口映射也就失效了)。
nftables:新发行版的默认后端,也是 iptables 的接班人
是什么:nftables 是内核 3.13 引入、用来取代 xtables(iptables/ip6tables/arptables/ebtables)的新一代框架。它不再有四张固定表的包袱,而是由你自己定义表和链,一条规则里可以同时写 IPv4、IPv6、端口集合、接口条件。
为什么值得学:三个实打实的好处——① 一套规则管 IPv4 + IPv6(iptables 时代你得写两遍,然后其中一份一定会忘);② 原子替换,用 nft -f ruleset.nft 整体加载,不会出现"改到一半规则乱七八糟"的窗口,也不会有 iptables 那种几十条规则逐条生效的慢;③ 集合匹配是原生的,{ 22, 80, 443 } 直接写,不用 multiport 扩展模块。
| 维度 | iptables | nftables |
|---|---|---|
| 表/链 | filter / nat / mangle / raw 四张固定表,链名固定(INPUT/OUTPUT/FORWARD)。 | 自定义表(按 inet/ip/ipv6/bridge/netdev 家族),链要自己写 type hook priority。 |
| 协议族 | iptables / ip6tables / arptables 各写一套。 | inet 家族同时覆盖 IPv4 与 IPv6,写一遍即可。 |
| 生效方式 | 逐条命令、逐条生效,大规则集加载慢。 | 整份 ruleset 一次性原子替换,不存在中间状态。 |
| 默认后端 | Debian 10 之前的默认;现在敲 iptables 多被翻译成 nft 规则。 | RHEL 9 / Debian 12 / Ubuntu 22.04+ 的默认后端。 |
| 持久化 | rules.v4 + netfilter-persistent,或 iptables-services。 | 写进 /etc/nftables.conf,由 nftables.service 开机加载。 |
| 适合谁 | 存量脚本、老文档、Docker 生成的规则。 | 新机器、自己写规则、需要 v4/v6 统一管理。 |
从零配一份 nftables 规则:注意顺序——先放行,最后才 drop
很多人的"配好了防火墙,重启后全没了"就是这个原因:nftables 只在内存里生效,必须把规则写进 /etc/nftables.conf 并 systemctl enable nftables。另外,RHEL 9 上老教程教的 /etc/sysconfig/iptables 已经不再被读取,照抄会白忙活一场。
-j DROP 是静默丢弃:客户端只能一直重试到超时(默认要等十几秒甚至几分钟),日志里什么都没写。-j REJECT 会立刻回一个 ICMP 不可达或 TCP RST,客户端秒失败。所以排错阶段用 REJECT 能立刻定位,稳定后再考虑改成 DROP(对外网扫描更"隐形")。如果抓包发现只有 SYN 没有 SYN-ACK,也没有 RST,那基本就是被 DROP 了。
ss 与 netstat:谁在监听、谁在连接
是什么:ss(socket statistics)直接读内核里的 socket 表,比老的 netstat 快得多。netstat 属于 net-tools 包,早已停止维护,Ubuntu 18.04+ / RHEL 8+ 默认都不装了——所以别再把 netstat 写进新脚本。
为什么它是排障第一站:它一次性回答三个最关键的问题:端口在不在听、听在哪个地址、被哪个进程占着。这三个答案拿到,一半的问题就已经定性了。
| 命令 | 干什么 |
|---|---|
ss -tulnp | 最常用:t=TCP、u=UDP、l=只列监听、n=不解析域名、p=带进程。看"服务到底起没起"。 |
ss -s | 连接数总览:各状态各有多少、占了多少内存。判断"连接数是不是打满了"。 |
ss -tan state established | 只看已建立的连接。后面可以带条件:( sport = :443 or dport = :443 )。 |
ss -tnp dst 10.0.0.5 | 看"我这台机器哪些进程在连 10.0.0.5"。 |
ss -tlnp 'sport = :8080' | 8080 到底被谁占了(比 lsof -i:8080 更快)。 |
ss -ti | 进阶神器:打印 TCP 内部状态 rtt、cwnd、retrans、rto,排查"连接慢、吞吐上不去"。 |
ss -K | 按条件强杀连接(ss -K dst 1.2.3.4),比重启服务轻。 |
实战:一次典型输出怎么读
| netstat(已退役) | 现在用 |
|---|---|
netstat -tulnp | ss -tulnp |
netstat -an | grep ESTABLISHED | ss -tan state established |
netstat -s | ss -s(或 nstat -az 看更细的计数) |
netstat -rn | ip route |
netstat -i | ip -s link |
不一定。TIME-WAIT 是主动关闭连接的一方必须经历的正常状态(默认持续 60 秒),它的作用是防止最后一个 ACK 丢了导致对端重传时收到错误的旧连接数据。判断标准是端口够不够而不是"数量吓不吓人":本机连出去的可用端口由 net.ipv4.ip_local_port_range 决定(通常 3 万多个),短连接服务确实可能被耗尽。此时该做的是连接池化/长连接,而不是去搜"怎么最快干掉 TIME-WAIT"。
tcpdump:用事实说话,别猜
是什么:tcpdump 是命令行抓包工具,基于 libpcap,能在网卡层面把流经的包原样记下来。它是网络排障里唯一的"现场录像"。
为什么必须会:因为"服务端没收到请求"和"客户端没发出请求"这两种情况的日志长得一模一样——都是两边都没报错。只有抓包能一刀切开:包到网卡了吗?回包了吗?回的什么?
判三个"一句话定性"的抓包套路
① 只有 SYN,没有 SYN-ACK,也没有 RST。请求发出去了,对方(或中间设备)静默丢弃了。基本可以断定是 DROP 类防火墙,或者路由不通。此时服务端日志当然什么都没有——因为包根本没到应用。
② 有 SYN,回的是 RST。网络是通的,但没有进程在监听那个端口(内核代回 RST),或者被 REJECT 规则拦下。这时候该回去查 ss -tlnp 和进程日志。
③ 握手顺利、请求也发了,但对端长时间不回应。包到了、服务在跑,那就是应用层慢(数据库卡、锁等待、下游超时)。抓包到此结束,接下来该上 strace 或应用自己的监控了。
容器有自己的网络命名空间,在宿主机上 tcpdump -i any 默认看不到容器内部 veth 之外的流量(同一个 veth 上的包可能只在容器侧可见)。正确姿势是进容器的网络命名空间里抓:先用 docker inspect -f '{{.State.Pid}}' 容器名 拿到主进程 PID,再执行
sudo nsenter -t <PID> -n tcpdump -i any -nn port 8080 —— 这也是排查"容器里服务连不上"的万能入口。如果机器上根本没有 tcpdump,宿主机装一次然后 nsenter 进去用即可(二进制是共享的,只有网络视图被隔离)。
三条铁律:① 一定加过滤条件(port 443、host 10.0.0.5),裸抓 -i any 在高流量机器上会瞬间吃满 CPU 并产生 GB 级文件;② 一定加 -c 或 -C/-W 轮转,-w 写出去的文件能把 /var 撑爆,进而拖死整台机器;③ 别在生产上长时间开着,抓完立刻 Ctrl-C。抓包本身也是"观测者效应"的典型——它会改变你正在观测的系统。
四个真实故障现场:从现象到命令
| 现象 | 第一条命令 | 常见结论 |
|---|---|---|
| 本机 curl 通,外部连不上 | ss -tlnp | grep 端口 | 监听在 127.0.0.1 而非 0.0.0.0;其次是本机防火墙没放行;第三是云厂商的安全组还没开(这一层在机器外面,本机改什么都没用)。 |
| 端口能连上但立刻断开 | sudo tcpdump -nn 'tcp[tcpflags] & tcp-rst != 0' | 服务端进程拒绝后主动关闭;或 conntrack 表满(dmesg | grep -i conntrack 出现 "table full");或连接数到了 ulimit -n 上限。 |
| 能连但很慢 | ss -ti 看 retrans 和 rtt | retrans 持续增长说明丢包(看 mtr 定位是哪一跳);rtt 突然翻倍多为跨网/拥塞;两者都正常那就是应用层慢,别赖网络。 |
| 规则明明放行了还是不通 | sudo iptables -S / sudo nft list ruleset | 规则顺序(DROP 在前面先匹配了);写错表(该写 -t nat);容器流量走 FORWARD;SELinux 或安全组挡着。 |
① 先理解 netfilter 五个钩子:DNAT 在 PREROUTING、本机流量走 INPUT/OUTPUT、帮别人转发走 FORWARD、SNAT 在 POSTROUTING。规则写错链,等于没写。
② iptables 看规则用 -S、改规则用 -I(插最前)、远程操作前先放行 22;新机器优先学 nftables,记得写 /etc/nftables.conf 并 enable 服务。
③ ss -tulnp 是第一站、ss -ti 是进阶、tcpdump 是终审法官:有 SYN 无回应=DROP,有 RST=没人听,握手完但慢=应用层问题。