第90章 网络防火墙:iptables基础、规则设置、包过滤
说实话,很多搞C语言网络编程的朋友,一听到「防火墙」三个字就觉得那是运维的事。我以前也这么想,直到有一次我写的一个UDP服务上线后,被扫描工具打成了筛子……嗯,从那以后我老老实实把iptables学了一遍。
其实iptables没那么神秘。它就是个内核里的包过滤框架。你写网络程序,数据从网卡进来,到用户态之前,会经过一系列规则链。iptables就是让你控制这些链的。
iptables的核心概念
先搞清楚几个基本东西:
- 表(table):不同功能的规则集合。最常用的是
filter表(过滤包)和nat表(地址转换)。 - 链(chain):规则挂在链上。比如
INPUT链处理进来的包,OUTPUT链处理出去的包。 - 规则(rule):一条具体的匹配条件和动作。匹配到了就执行动作,比如
ACCEPT(放行)或DROP(丢弃)。
我个人的习惯是,先画一张图把数据流向理清楚,再动手写规则。下面这张图你一看就明白了:
你看,数据包从网络接口进来,先经过 PREROUTING,然后路由判断。如果是发给本机的,走 INPUT 链;如果是转发的,走 FORWARD 链。本机发出的包走 OUTPUT 链,最后出去前经过 POSTROUTING。
filter 表:最常用的包过滤
filter 表有三个内置链:INPUT、FORWARD、OUTPUT。说白了,你写网络程序最常打交道的就是 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 的匹配条件很丰富,我列个表给你看:
| 参数 | 含义 | 示例 |
|---|---|---|
-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
这样,只要是你主动发起的连接,回来的包自动放行。别人想从外面连进来,只能走你明确开放的端口。
实战:保护你的 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
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 语言网络程序时,养成一个好习惯:服务启动前先把防火墙规则配好。别等出了问题再补,那时候可能已经晚了。