第90章 网络防火墙:iptables基础、规则设置、包过滤

说实话,很多搞C语言网络编程的朋友,一听到「防火墙」三个字就觉得那是运维的事。我以前也这么想,直到有一次我写的一个UDP服务上线后,被扫描工具打成了筛子……嗯,从那以后我老老实实把iptables学了一遍。

其实iptables没那么神秘。它就是个内核里的包过滤框架。你写网络程序,数据从网卡进来,到用户态之前,会经过一系列规则链。iptables就是让你控制这些链的。

iptables的核心概念

先搞清楚几个基本东西:

  • 表(table):不同功能的规则集合。最常用的是 filter 表(过滤包)和 nat 表(地址转换)。
  • 链(chain):规则挂在链上。比如 INPUT 链处理进来的包,OUTPUT 链处理出去的包。
  • 规则(rule):一条具体的匹配条件和动作。匹配到了就执行动作,比如 ACCEPT(放行)或 DROP(丢弃)。

我个人的习惯是,先画一张图把数据流向理清楚,再动手写规则。下面这张图你一看就明白了:

iptables 数据包流向图 网络接口 PREROUTING 路由 INPUT 链 本地进程 FORWARD OUTPUT 链 本地进程 POSTROUTING

你看,数据包从网络接口进来,先经过 PREROUTING,然后路由判断。如果是发给本机的,走 INPUT 链;如果是转发的,走 FORWARD 链。本机发出的包走 OUTPUT 链,最后出去前经过 POSTROUTING

filter 表:最常用的包过滤

filter 表有三个内置链:INPUTFORWARDOUTPUT。说白了,你写网络程序最常打交道的就是 INPUT 链——控制哪些包能到达你的服务。

举个例子,你写了一个 TCP 服务监听 8080 端口,想只允许内网访问:

# 允许内网访问 8080
iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 8080 -j ACCEPT

# 拒绝其他所有访问 8080
iptables -A INPUT -p tcp --dport 8080 -j DROP

这里 -A 表示追加规则到链末尾,-s 指定源地址,-p 指定协议,--dport 指定目标端口,-j 指定动作。

小技巧:规则是有顺序的。iptables 从上往下匹配,匹配到第一条就执行,后面的规则就不看了。所以「允许内网」要放在「拒绝所有」前面。

规则匹配条件详解

iptables 的匹配条件很丰富,我列个表给你看:

参数 含义 示例
-s 源 IP 地址 -s 10.0.0.0/8
-d 目标 IP 地址 -d 172.16.0.1
-p 协议(tcp/udp/icmp) -p tcp
--sport 源端口 --sport 1024:65535
--dport 目标端口 --dport 80
-i 入站网卡 -i eth0
-o 出站网卡 -o eth1
-m state 连接状态匹配 -m state --state ESTABLISHED

我记得有一次排查线上问题,发现某个服务总是连不上。查了半天,原来是规则里 --dport 写成了 --sport。这种低级错误,你写代码时也容易犯,多检查一遍。

状态防火墙:让规则更智能

iptables 支持连接跟踪(conntrack),能识别包属于哪个连接。这玩意儿太有用了。

你想想看,如果只开放了 80 端口,那别人发回来的响应包怎么进来?没有状态跟踪的话,你得手动放行所有高端口,那跟没防火墙差不多。

用状态匹配就简单了:

# 允许已建立的连接和相关的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 允许新的 SSH 连接
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -j ACCEPT

这样,只要是你主动发起的连接,回来的包自动放行。别人想从外面连进来,只能走你明确开放的端口。

核心思路:默认拒绝 + 白名单放行。先设置默认策略为 DROP,然后一条一条加允许规则。这样最安全。

实战:保护你的 C 语言网络服务

假设你写了一个 C 语言的 TCP 回显服务器,监听 8888 端口。你想只允许公司内网(192.168.1.0/24)访问,同时允许本机调试(127.0.0.1)。

完整的规则集如下:

# 清空现有规则
iptables -F
iptables -X

# 设置默认策略
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT

# 允许回环接口
iptables -A INPUT -i lo -j ACCEPT

# 允许已建立的连接
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT

# 允许内网访问 8888
iptables -A INPUT -s 192.168.1.0/24 -p tcp --dport 8888 -j ACCEPT

# 允许本机调试
iptables -A INPUT -s 127.0.0.1 -p tcp --dport 8888 -j ACCEPT

# 允许 SSH(防止把自己锁在外面)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
警告:设置默认 DROP 策略前,一定要先加好 SSH 放行规则。我曾经在远程服务器上执行 iptables -P INPUT DROP 忘了加 SSH 规则,结果直接断连,只能让机房同事重启机器……别学我。

规则持久化

iptables 规则重启后就没了。你得保存一下:

# Debian/Ubuntu
iptables-save > /etc/iptables/rules.v4

# CentOS/RHEL
service iptables save

或者写到启动脚本里,每次开机自动加载。我个人习惯写个 shell 脚本,把规则都放进去,需要改规则时直接改脚本再执行一遍,方便管理。

查看和调试规则

几个常用命令:

# 查看所有规则(带序号)
iptables -L -n --line-numbers

# 查看特定表的规则
iptables -t nat -L -n

# 删除某条规则(按序号)
iptables -D INPUT 3

# 插入规则到指定位置
iptables -I INPUT 2 -s 10.0.0.5 -j DROP

调试时可以用 LOG 动作,把匹配到的包记到日志里:

iptables -A INPUT -p tcp --dport 8888 -j LOG --log-prefix "IPTABLES: " --log-level 4

然后 tail -f /var/log/syslog 看日志,就知道哪些包被拦截了。这招我在排查诡异连接问题时帮了大忙。

总结一下

iptables 说白了就是三件事:选表、选链、写规则。filter 表做包过滤,nat 表做地址转换。规则从上往下匹配,匹配到就执行。状态跟踪让规则更简洁安全。

你写 C 语言网络程序时,养成一个好习惯:服务启动前先把防火墙规则配好。别等出了问题再补,那时候可能已经晚了。


公众号:蓝海资料掘金营,微信deep3321