老张的不幸生活

iptables 入门

#linux

此文仅献给那些像我一样完全看不懂 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

基本选项 ——–

下面列表中仅有会在本文示例中出现的选项,并非所有选项。你并不需要马上就搞明白这些选项,当在下面示例中遇到这些选项时,回来查看即可。

  1. -A - 添加(Append)新规则到规则链。可用的规则链有 INPUT FORWARD 和 OUTPUT,本文示例中仅处理 INPUT 链,也就是说这些规则仅会影响进入服务器的通信。
  2. -L - 列出当前已有规则。
  3. -m conntrack - 允许规则基于连接状态进行匹配,与 --ctstate 配合使用。
  4. –ctstate - 定义规则可匹配的连接状态,有以下几种状态:
    1. NEW - 连接还未被查看。
    2. RELATED - 连接还未被查看,但与它相关的连接已经被许可。
    3. ESTABLISHED - 已建立的连接。
    4. INVALID - 由于某种原因未被标识状态的连接。
  5. -m limit - 规则按一定的频率匹配,与 --limit 配合使用。一般用于记录日志。
    1. –limit - 规则匹配的最高频率,格式为一个数字后面跟 “/second” “/minute” “/hour” 或者 “/day”,比如 “3/minute”, 这意味着平均每分钟最多匹配 3 次。如果不设置此选项默认为 “3/hour”。
  6. -p - protocol 连接协议。
  7. –dport - 目标端口(destination port),值可以为单个端口号,也可以给出一个端口范围,比如 1000:1010 将匹配从 1000 到 1010 这 10 个端口。
  8. -j - 跳(jump)到某个试图达成的目标。默认情况下,iptables 提供了 4 个目标:
    1. ACCEPT - 接受数据包并终止应用接下来的规则。
    2. REJECT - 拒绝数据包,通知发送者,并终止应用接下来的规则。
    3. DROP - 默默的忽略数据包并终止应用接下来的规则。
    4. LOG - 记录数据包并继续应用接下来的规则。允许使用 --log-prefix--log-level 选项。
  9. –log-prefix - 日志信息前缀,值需要用双引号括起来。
  10. –log-level - 系统日志等级,一般 7 就够了。
  11. -i - 仅匹配特定接口(interface)上的数据包。
  12. -I - 插入(insert)一条规则,有 2 个选项:需要插入的规则链以及插入到第几条。比如 -I INPUT 5 意味着插入到 INPUT 链,并作为链中的第五条。
  13. -v - 显示更多的规则信息。
  14. -s –source - 指定来源地址。
  15. -d –destination - 指定目标地址。
  16. -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 做了什么:

  1. -A INPUT - 给 INPUT 规则链添加一条规则。
  2. -p tcp - 检查是否是 tcp 协议。
  3. –dport ssh - 如果是 tcp 协议,检查目标端口是否是 ssh 使用的端口。
  4. -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 开始说起:

  1. -m limit –limit 5/min - 控制规则匹配的速率,平均每分钟最多5次,也就是说如果一分钟内有6条符合规则的通信,只会记录前5条。
  2. -j LOG - 将符合规则的数据包记录到日志里。
  3. –log-prefix “iptables denied:” - 日志信息的前缀为 “iptables denied:”。
  4. –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