此文仅献给那些像我一样完全看不懂 iptables 规则的人。IptalesHowto。
基本命令
列出当前已有的规则:
sudo iptables -L
如果是在崭新的系统上,返回如下:
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain FORWORD (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
基本选项 ——–
下面列表中仅有会在本文示例中出现的选项,并非所有选项。你并不需要马上就搞明白这些选项,当在下面示例中遇到这些选项时,回来查看即可。
- -A - 添加(Append)新规则到规则链。可用的规则链有 INPUT FORWARD 和 OUTPUT,本文示例中仅处理 INPUT 链,也就是说这些规则仅会影响进入服务器的通信。
- -L - 列出当前已有规则。
- -m conntrack - 允许规则基于连接状态进行匹配,与
--ctstate
配合使用。 - –ctstate - 定义规则可匹配的连接状态,有以下几种状态:
- NEW - 连接还未被查看。
- RELATED - 连接还未被查看,但与它相关的连接已经被许可。
- ESTABLISHED - 已建立的连接。
- INVALID - 由于某种原因未被标识状态的连接。
- -m limit - 规则按一定的频率匹配,与
--limit
配合使用。一般用于记录日志。- –limit - 规则匹配的最高频率,格式为一个数字后面跟 “/second” “/minute” “/hour” 或者 “/day”,比如 “3/minute”, 这意味着平均每分钟最多匹配 3 次。如果不设置此选项默认为 “3/hour”。
- -p - protocol 连接协议。
- –dport - 目标端口(destination port),值可以为单个端口号,也可以给出一个端口范围,比如
1000:1010
将匹配从 1000 到 1010 这 10 个端口。 - -j - 跳(jump)到某个试图达成的目标。默认情况下,iptables 提供了 4 个目标:
- ACCEPT - 接受数据包并终止应用接下来的规则。
- REJECT - 拒绝数据包,通知发送者,并终止应用接下来的规则。
- DROP - 默默的忽略数据包并终止应用接下来的规则。
- LOG - 记录数据包并继续应用接下来的规则。允许使用
--log-prefix
与--log-level
选项。
- –log-prefix - 日志信息前缀,值需要用双引号括起来。
- –log-level - 系统日志等级,一般
7
就够了。 - -i - 仅匹配特定接口(interface)上的数据包。
- -I - 插入(insert)一条规则,有 2 个选项:需要插入的规则链以及插入到第几条。比如
-I INPUT 5
意味着插入到 INPUT 链,并作为链中的第五条。 - -v - 显示更多的规则信息。
- -s –source - 指定来源地址。
- -d –destination - 指定目标地址。
- -o –out-interface - 输出网络接口名。
允许已建立的会话(session)
允许已建立的会话接收通信:
sudo iptables -A INPUT -m conntract --ctstate ESTABLISHED,RELATED -j ACCEPT
ESTABLISHED,RELATED
用逗号隔开的两个状态,中间不能有空格。如果上面的命令报错,说明你使用的是阉割版的 VPS(没想到 linode 的 VPS 就是阉割版的),可以使用下面的命令代替:
sudo iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
允许在特定端口上接收通信
一般我们都使用 ssh 管理服务器,因此首先需要让 ssh 不被防火墙的阻止,ssh 默认使用 22 端口,因此我们告诉 iptables 允许在 22 端口上使用 tcp 协议的通信:
sudo iptables -A INPUT -p tcp --dport ssh -j ACCEPT
回顾一下上面『基本选项』列表,你就能明白这条命令让 iptables 做了什么:
- -A INPUT - 给 INPUT 规则链添加一条规则。
- -p tcp - 检查是否是 tcp 协议。
- –dport ssh - 如果是 tcp 协议,检查目标端口是否是 ssh 使用的端口。
- -j ACCEPT - 如果还是,接受这个输入。
现在让我们看看当前已经应用的规则:
sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
下面允许 80 端口:
sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
再次查看:
sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:www
我们已经允许了在 ssh 和 web 端口上接收通信,但现在还没有阻止什么,因此目前所有的通信依然可以到达服务器。
阻止通信
一旦数据包被某条规则接受(accept),它就不会再受到接下来的规则影响。因为我们上面已经允许了 ssh 和 web 端口,在后面再添加一条忽略所有数据包的规则后,web 和 ssh 并不会因此受影响。
sudo iptables -A INPUT -j DROP
sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:www
DROP all -- anywhere
由于上面的命令没有指定网络接口和协议,因此除了 ssh 和 web 外任何网络接口以及任何端口上的通信都将被阻止。
编辑 iptables
目前的问题是上面阻止通信的规则杀伤力太大,由于没有指定网络接口,因此经过本地接口的通信也会被阻止,比如在同一台机器上应用程序通过 TCP 连接 mysql 数据库。然而我们现在再添加一条允许本地接口通信的规则已经为时已晚,因为上一条规则已经忽略了数据包并停止应用接下来的规则。解决办法就是使用 -I
把规则插入到规则链的顶部。
sudo iptables -I INPUT 1 -i lo -j ACCEPT
sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere
ACCEPT all -- anywhere anywhere state RELATED,ESTABLISHED
ACCEPT tcp -- anywhere anywhere tcp dpt:ssh
ACCEPT tcp -- anywhere anywhere tcp dpt:www
DROP all -- anywhere
可以看到目前的规则链中,第一条与最后一条很相似,为了查看更多的详细信息可以这样:
sudo iptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
0 0 ACCEPT all -- lo any anywhere anywhere
0 0 ACCEPT all -- any any anywhere anywhere state RELATED,ESTABLISHED
0 0 ACCEPT tcp -- any any anywhere anywhere tcp dpt:ssh
0 0 ACCEPT tcp -- any any anywhere anywhere tcp dpt:www
0 0 DROP all -- any any anywhere anywhere
记录日志
如果你想记录一下有哪些通信被抛弃,可以执行:
sudo iptables -I INPUT 5 -m limit --limit 5/min -j LOG --log-prefix "iptables denied: " --log-level 7
前面已经介绍过插入规则的选项,所以我们从 -m limit 开始说起:
- -m limit –limit 5/min - 控制规则匹配的速率,平均每分钟最多5次,也就是说如果一分钟内有6条符合规则的通信,只会记录前5条。
- -j LOG - 将符合规则的数据包记录到日志里。
- –log-prefix “iptables denied:” - 日志信息的前缀为 “iptables denied:”。
- –log-level - 系统日志级别为 7。
保存规则
假如我现在重启服务器,我们上面添加的规则将会全部丢失。为了避免每次都要重新添加的麻烦,可以将规则保存到一个文件中,然后每当服务器重启后自动添加文件中的规则。有多种方法可以达到这个目的,这里只说一种。首先保存当前的规则到一个文件:
sudo iptables-save > /etc/iptables.rules
然后在 /etc/network/if-pre-up.d
添加一个文件,比如 iptables,内容如下:
#!/bin/sh
iptables-restore < /etc/iptables.rules
最后,将此文件设为可执行文件:
chmod +x /etc/network/if-pre-up.d/iptables