防火墙是保护服务器和基础设施安全的重要工具。在 Linux 生态系统中,iptables 是使用很广泛的防火墙工具之一,它基于内核的包过滤框架(packet filtering framework) netfilter。如果管理员或用户不了解这些系统的架构,那可能就无法创建出可靠的防火墙策略,一方面是因为 iptables 的语法颇有挑战性,另外一方面是 netfilter 框架内部相互交织而变得错综复杂。

本文将带领读者深入理解 iptables 框架,让那些需要创建防火墙策略的用户对它有一个更全面的认识。我们会讨论 iptables 是如何与 netfilter 交互的,几个组件是如何组织成一个全面的过滤和矫正系统(a comprehensive filtering and mangling system)的。

IPTables 表和链

iptables 使用 table 来组织规则,根据用来做什么类型的判断(the type of decisions they are used to make)标准,将规则分为不同 table。例如,

  • 如果规则是处理网络地址转换的,那会放到 nat table
  • 如果是判断是否允许包继续向前,那可能会放到 filter table

在每个 table 内部,规则被进一步组织成 chain,内置的 chain 是由内置的 hook 触发的。chain 基本上能决定规则 何时 被匹配。

下面可以看出,内置的 chain 名字和 netfilter hook 名字是一一对应的:

  • PREROUTING: 由 NF_IP_PRE_ROUTING hook 触发
  • INPUT: 由 NF_IP_LOCAL_IN hook 触发
  • FORWARD: 由 NF_IP_FORWARD hook 触发
  • OUTPUT: 由 NF_IP_LOCAL_OUT hook 触发
  • POSTROUTING: 由 NF_IP_POST_ROUTING hook 触发

chain 使管理员可以控制在包的传输路径上哪个点应用策略。因为每个 table 有多个 chain,因此一个 table 可以在处理过程中的多个地方施加影响。特定类型的规则只在协议栈的特定点有意义,因此并不是每个 table 都会在内核的每个 hook 注册 chain

内核一共只有 5 个 netfilter hook,因此不同 table 的 chain 最终都是注册到这几个点 。例如,有三个 table 有 PRETOUTING chain。当这些 chain 注册到对应的 NF_IP_PRE_ROUTING hook 点时,它们需要指定优先级,应该依次调用哪个 table 的 PRETOUTING chain,优先级从高到低。我们一会就会看到 chain 的优先级问题。

table 种类

先来看看 iptables 提供的 table 类型。这些 table 是按规则类型区分的。

Filter Table

filter table 是最常用的 table 之一,用于判断是否允许一个包通过

在防火墙领域,这通常称作过滤包(filtering packets)。这个 table 提供了防火墙 的一些常见功能。

NAT Table

nat table 用于实现网络地址转换规则。

当包进入协议栈的时候,这些规则决定是否以及如何修改包的源/目的地址,以改变包被路由时的行为。nat table 通常用于将包路由到无法直接访问的网络。

Mangle Table

mangle (修正)table 用于修改包的 IP 头

例如,可以修改包的 TTL,增加或减少包可以经过的跳数。

这个 table 还可以对包打只在内核内有效的 标记(internal kernel “mark”),后续的 table 或工具处理的时候可以用到这些标记。标记不会修改包本身,只是在包的内核表示上做标记。

Raw Table

iptables 防火墙是有状态的:对每个包进行判断的时候是依赖已经判断过的包

建立在 netfilter 之上的连接跟踪(connection tracking)特性使得 iptables 将包看作已有的连接或会话的一部分,而不是一个由独立、不相关的包组成的流。连接跟踪逻辑在包到达网络接口之后很快就应用了。

raw table 定义的功能非常有限,其唯一目的就是提供一个让包绕过连接跟踪的框架

Security Table

security table 的作用是给包打上 SELinux 标记,以此影响 SELinux 或其他可以解读 SELinux 安全上下文的系统处理包的行为。这些标记可以基于单个包,也可以基于连接。

每种 table 实现的 chain

前面已经分别讨论了 table 和 chain,接下来看每个 table 里各有哪些 chain。另外,我 们还将讨论注册到同一 hook 的不同 chain 的优先级问题。例如,如果三个 table 都有 PRETOUTING chain,那应该按照什么顺序调用它们呢?

下面的表格展示了 table 和 chain 的关系。横向是 table, 纵向是 chain,Y 表示 这个 table 里面有这个 chain。例如,第二行表示 raw table 有 PRETOUTINGOUTPUT 两 个 chain。具体到每列,从上倒下的顺序就是 netfilter hook 触发的时候,(对应 table 的)chain 被调用的顺序。

有几点需要说明一下。在下面的图中,nat table 被细分成了 DNAT (修改目的地址) 和 SNAT(修改源地址),以更方便地展示他们的优先级。另外,我们添加了路由决策点 和连接跟踪点,以使得整个过程更完整全面:

Tables/Chains PREROUTING INPUT FORWARD OUTPUT POSTROUTING
(路由判断) Y
raw Y Y
(连接跟踪) Y Y
mangle Y Y Y Y Y
nat (DNAT) Y Y
(路由判断) Y Y
filter Y Y Y
security Y Y Y
nat (SNAT) Y Y Y

当一个包触发 netfilter hook 时,处理过程将沿着列从上向下执行。 触发哪个 hook (列)和包的方向(ingress/egress)、路由判断、过滤条件等相关。

特定事件会导致 table 的 chain 被跳过。例如,只有每个连接的第一个包会去匹配 NAT 规则,对这个包的动作会应用于此连接后面的所有包。到这个连接的应答包会被自动应用反方向的 NAT 规则。

Chain 遍历优先级

假设服务器知道如何路由数据包,而且防火墙允许数据包传输,下面就是不同场景下包的游走流程:

  • 收到的、目的是本机的包:PRETOUTING -> INPUT
  • 收到的、目的是其他主机的包:PRETOUTING -> FORWARD -> POSTROUTING
  • 本地产生的包:OUTPUT -> POSTROUTING

综合前面讨论的 table 顺序问题,我们可以看到对于一个收到的、目的是本机的包: 首先依次经过 PRETOUTING chain 上面的 rawmanglenat table;然后依次经 过 INPUT chain 的 manglefiltersecuritynat table,然后才会到达本机 的某个 socket。

IPTables 规则

规则放置在特定 table 的特定 chain 里面。当 chain 被调用的时候,包会依次匹配 chain 里面的规则。每条规则都有一个匹配部分和一个动作部分。

匹配

规则的匹配部分指定了一些条件,包必须满足这些条件才会和相应的将要执行的动作(“ target”)进行关联。

匹配系统非常灵活,还可以通过 iptables extension 大大扩展其功能。规则可以匹配协议类型、目的或源地址、目的或源端口、目的或源网段、接收或发送的接口(网卡)、协议头、连接状态等等条件。这些综合起来,能够组合成非常复杂的规则来区分不同的网络流 量。

目标

包符合某种规则的条件而触发的动作(action)叫做目标(target)。目标分为两种类型:

  • 终止目标(terminating targets):这种 target 会终止 chain 的匹配,将控制权转移回 netfilter hook。根据返回值的不同,hook 或者将包丢弃,或者允许包进行下一 阶段的处理
  • 非终止目标(non-terminating targets):非终止目标执行动作,然后继续 chain 的执行。虽然每个 chain 最终都会回到一个终止目标,但是在这之前,可以执行任意多 个非终止目标

每个规则可以跳转到哪个 target 依上下文而定,例如,table 和 chain 可能会设置 target 可用或不可用。规则里激活的 extensions 和匹配条件也影响 target 的可用性。

跳转到用户自定义 chain

这里要介绍一种特殊的非终止目标:跳转目标(jump target)。jump target 是跳转到其 他 chain 继续处理的动作。我们已经讨论了很多内置的 chain,它们和调用它们的 netfilter hook 紧密联系在一起。然而,iptables 也支持管理员创建他们自己的用于管理目的的 chain。

向用户自定义 chain 添加规则和向内置的 chain 添加规则的方式是相同的。不同的地方在于,用户定义的 chain 只能通过从另一个规则跳转(jump)到它,因为它们没有注册到 netfilter hook

用户定义的 chain 可以看作是对调用它的 chain 的扩展。例如,用户定义的 chain 在结束的时候,可以返回 netfilter hook,也可以继续跳转到其他自定义 chain。

这种设计使框架具有强大的分支功能,使得管理员可以组织更大更复杂的网络规则。

IPTables 和连接跟踪

在讨论 raw table 和匹配连接状态的时候,我们介绍了构建在 netfilter 之上的连接跟踪系统。连接跟踪系统使得 iptables 基于连接上下文而不是单个包来做出规则判断,给 iptables 提供了有状态操作的功能。

连接跟踪在包进入协议栈之后很快就开始工作了。在给包分配连接之前所做的工作非常少,只有检查 raw table 和一些基本的完整性检查。

跟踪系统将包和已有的连接进行比较,如果包所属的连接已经存在就更新连接状态,否则就创建一个新连接。如果 raw table 的某个 chain 对包标记为目标是 NOTRACK,那这 个包会跳过连接跟踪系统。

连接跟踪系统中的连接状态有:

  • NEW:如果到达的包关联不到任何已有的连接,但包是合法的,就为这个包创建一个新连接。对面向连接的(connection-aware)的协议例如 TCP 以及非面向连接的(connectionless )的协议例如 UDP 都适用
  • ESTABLISHED:当一个连接收到应答方向的合法包时,状态从 NEW 变成 ESTABLISHED。对 TCP 这个合法包其实就是 SYN/ACK 包;对 UDP 和 ICMP 是源和目的 IP 与原包相反的包
  • RELATED:包不属于已有的连接,但是和已有的连接有一定关系。这可能是辅助连接( helper connection),例如 FTP 数据传输连接,或者是其他协议试图建立连接时的 ICMP 应答包
  • INVALID:包不属于已有连接,并且因为某些原因不能用来创建一个新连接,例如无法识别、无法路由等等
  • UNTRACKED:如果在 raw table 中标记为目标是 UNTRACKED,这个包将不会进入连接跟踪系统
  • SNAT:包的源地址被 NAT 修改之后会进入的虚拟状态。连接跟踪系统据此在收到反向包时对地址做反向转换
  • DNAT:包的目的地址被 NAT 修改之后会进入的虚拟状态。连接跟踪系统据此在收到反向包时对地址做反向转换

这些状态可以定位到连接生命周期内部,管理员可以编写出更加细粒度、适用范围更大、更安全的规则。

命令备忘

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Usage: iptables -[ACD] chain rule-specification [options]
       iptables -I chain [rulenum] rule-specification [options]
       iptables -R chain rulenum rule-specification [options]
       iptables -D chain rulenum [options]
       iptables -[LS] [chain [rulenum]] [options]
       iptables -[FZ] [chain] [options]
       iptables -[NX] chain
       iptables -E old-chain-name new-chain-name
       iptables -P chain target [options]
       iptables -h (print this help information)

Commands:
  --append  -A chain		Append to chain
  --check   -C chain		Check for the existence of a rule
  --delete  -D chain		Delete matching rule from chain
  --delete  -D chain rulenum
				Delete rule rulenum (1 = first) from chain
  --insert  -I chain [rulenum]
				Insert in chain as rulenum (default 1=first)
  --replace -R chain rulenum
				Replace rule rulenum (1 = first) in chain
  --list    -L [chain [rulenum]]
				List the rules in a chain or all chains

Options:
[!] --protocol	-p proto	protocol: by number or name, eg. `tcp'
[!] --source	-s address[/mask][...]
				source specification
[!] --destination -d address[/mask][...]
				destination specification
[!] --in-interface -i input name[+]
				network interface name ([+] for wildcard)
 --jump	-j target
				target for rule (may load target extension)
				  --goto      -g chain
                              jump to chain with no return
  --match	-m match
				extended match (may load extension)
  --numeric	-n		numeric output of addresses and ports
[!] --out-interface -o output name[+]
				network interface name ([+] for wildcard)
  --table	-t table	table to manipulate (default: `filter')
  --verbose	-v		verbose mode
  --line-numbers		print line numbers when listing

查看规则

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 查看 filter 对应的规则
$ sudo iptables -t filter -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  link-local/16        anywhere             tcp dpt:10250
ACCEPT     tcp  --  192.168.0.0/16       anywhere             tcp dpt:10250
ACCEPT     tcp  --  172.16.0.0/12        anywhere             tcp dpt:10250
ACCEPT     tcp  --  30.0.0.0/8           anywhere             tcp dpt:10250
ACCEPT     tcp  --  11.0.0.0/8           anywhere             tcp dpt:10250
ACCEPT     tcp  --  9.0.0.0/8            anywhere             tcp dpt:10250
ACCEPT     tcp  --  10.0.0.0/8           anywhere             tcp dpt:10250
ACCEPT     tcp  --  localhost/8          anywhere             tcp dpt:10250
KUBE-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes service portals */
KUBE-EXTERNAL-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes externally-visible service portals */
KUBE-FIREWALL  all  --  anywhere             anywhere
DROP       tcp  --  anywhere             anywhere             tcp dpt:10250

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
KUBE-FORWARD  all  --  anywhere             anywhere             /* kubernetes forwarding rules */
KUBE-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes service portals */
DOCKER-USER  all  --  anywhere             anywhere

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
KUBE-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes service portals */
KUBE-FIREWALL  all  --  anywhere             anywhere

# 查看 filter 对应的 INPUT 链规则
$ sudo iptables -t filter -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     tcp  --  link-local/16        anywhere             tcp dpt:10250
ACCEPT     tcp  --  192.168.0.0/16       anywhere             tcp dpt:10250
ACCEPT     tcp  --  172.16.0.0/12        anywhere             tcp dpt:10250
ACCEPT     tcp  --  30.0.0.0/8           anywhere             tcp dpt:10250
ACCEPT     tcp  --  11.0.0.0/8           anywhere             tcp dpt:10250
ACCEPT     tcp  --  9.0.0.0/8            anywhere             tcp dpt:10250
ACCEPT     tcp  --  10.0.0.0/8           anywhere             tcp dpt:10250
ACCEPT     tcp  --  localhost/8          anywhere             tcp dpt:10250
KUBE-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes service portals */
KUBE-EXTERNAL-SERVICES  all  --  anywhere             anywhere             ctstate NEW /* kubernetes externally-visible service portals */
KUBE-FIREWALL  all  --  anywhere             anywhere
DROP       tcp  --  anywhere             anywhere             tcp dpt:10250

详细信息查看,-nvL 参数,使用 –line-numbers 即可显示规则的编号:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# pkts: 对应规则匹配到的报文的个数。
# bytes: 对应匹配到的报文包的大小总和。
# target: 规则对应的target,往往表示规则对应的动作,即规则匹配成功后需要采取的措施。
# prot: 表示规则对应的协议,是否只针对某些协议应用此规则。
# opt: 表示规则对应的选项。
# in: 表示数据包由哪个接口(网卡)流入,我们可以设置通过哪块网卡流入的报文需要匹配当前规则。
# out: 表示数据包由哪个接口(网卡)流出,我们可以设置通过哪块网卡流出的报文需要匹配当前规则。
# source: 表示规则对应的源头地址,可以是一个IP,也可以是一个网段。
# destination: 表示规则对应的目标地址。可以是一个IP,也可以是一个网段。

$ sudo iptables -t filter -nvL INPUT
Chain INPUT (policy ACCEPT 368 packets, 95061 bytes)
num   pkts bytes target     prot opt in     out     source               destination
1    82356 7089K ACCEPT     tcp  --  *      *       169.254.0.0/16       0.0.0.0/0            tcp dpt:10250
2        0     0 ACCEPT     tcp  --  *      *       192.168.0.0/16       0.0.0.0/0            tcp dpt:10250
3        0     0 ACCEPT     tcp  --  *      *       172.16.0.0/12        0.0.0.0/0            tcp dpt:10250
4        0     0 ACCEPT     tcp  --  *      *       30.0.0.0/8           0.0.0.0/0            tcp dpt:10250
5        0     0 ACCEPT     tcp  --  *      *       11.0.0.0/8           0.0.0.0/0            tcp dpt:10250
6        0     0 ACCEPT     tcp  --  *      *       9.0.0.0/8            0.0.0.0/0            tcp dpt:10250
7        0     0 ACCEPT     tcp  --  *      *       10.0.0.0/8           0.0.0.0/0            tcp dpt:10250
8        0     0 ACCEPT     tcp  --  *      *       127.0.0.0/8          0.0.0.0/0            tcp dpt:10250
9    4383K  152M KUBE-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate NEW /* kubernetes service portals */
10   4383K  152M KUBE-EXTERNAL-SERVICES  all  --  *      *       0.0.0.0/0            0.0.0.0/0            ctstate NEW /* kubernetes externally-visible service portals */
11     53M   26G KUBE-FIREWALL  all  --  *      *       0.0.0.0/0            0.0.0.0/0
12       0     0 DROP       tcp  --  *      *       0.0.0.0/0            0.0.0.0/0            tcp dpt:10250
  • Chain INPUT (policy ACCEPT 368 packets, 95061 bytes)
    • policy表示当前链的默认策略,policy ACCEPT 表示上图中 INPUT 的链的默认动作为 ACCEPT
    • packets表示当前链(上例为 INPUT 链)默认策略匹配到的包的数量,0 packets 表示默认策略匹配到 0 个包。
    • bytes表示当前链默认策略匹配到的所有包的大小总和。

修改规则

添加规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 在指定表的指定链的尾部添加一条规则
# 命令语法:iptables -t 表名 -A 链名 匹配条件 -j 动作
$ sudo iptables -t filter -A INPUT -s 192.168.1.146 -j DROP

# 在指定表的指定链的首部添加一条规则
# 命令语法:iptables -t 表名 -I 链名 匹配条件 -j 动作
$ sudo iptables -t filter -I INPUT -s 192.168.1.146 -j ACCEPT

# 在指定表的指定链的指定位置添加一条规则
# 命令语法:iptables -t 表名 -I 链名 规则序号 匹配条件 -j 动作
$ sudo iptables -t filter -I INPUT 5 -s 192.168.1.146 -j REJECT

# 设置指定表的指定链的默认策略(默认动作),并非添加规则
# 命令语法:iptables -t 表名 -P 链名 动作
$ sudo iptables -t filter -P FORWARD ACCEPT

删除规则:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# 按照规则序号删除规则,删除指定表的指定链的指定规则,-D选项表示删除对应链中的规则。
# 命令语法:iptables -t 表名 -D 链名 规则序号
$ sudo iptables -t filter -D INPUT 3

# 按照具体的匹配条件与动作删除规则,删除指定表的指定链的指定规则。
# 命令语法:iptables -t 表名 -D 链名 匹配条件 -j 动作
$ sudo iptables -t filter -D INPUT -s 192.168.1.146 -j DROP

# 删除指定表的指定链中的所有规则,-F选项表示清空对应链中的规则,执行时需三思。
# 命令语法:iptables -t 表名 -F 链名
$ sudo iptables -t filter -F INPUT

# 删除指定表中的所有规则,执行时需三思。
# 命令语法:iptables -t 表名 -F
$ sudo iptables -t filter -F

修改规则:

注意点:如果使用-R 选项修改规则中的动作,那么必须指明原规则中的原匹配条件,例如源 IP,目标 IP 等。

1
2
3
4
5
6
7
# 修改指定表中指定链的指定规则,-R选项表示修改对应链中的规则,使用-R选项时要同时指定对应的链以及规则对应的序号,并且规则中原本的匹配条件不可省略。
# 命令语法:iptables -t 表名 -R 链名 规则序号 规则原本的匹配条件 -j 动作
$ sudo iptables -t filter -R INPUT 3 -s 192.168.1.146 -j ACCEPT

# 上述示例表示修改filter表中INPUT链的第3条规则,将这条规则的动作修改为ACCEPT, -s 192.168.1.146为这条规则中原本的匹配条件,如果省略此匹配条件,修改后的规则中的源地址可能会变为0.0.0.0/0。

# 其他修改规则的方法:先通过编号删除规则,再在原编号位置添加一条规则。

修改指定表的指定链的默认策略(默认动作),并非修改规则,可以使用如下命令。

1
2
# 命令语法:iptables -t 表名 -P 链名 动作
$ sudo iptables -t filter -P FORWARD ACCEPT

上例表示将 filter 表中 FORWARD 链的默认策略修改为 ACCEPT

保存规则:

1
2
3
4
5
6
7
8
# 保存规则命令如下,表示将iptables规则保存至/etc/sysconfig/iptables文件中,如果对应的操作没有保存,那么当重启iptables服务以后规则失效
$ service iptables save

# 或者使用如下方法保存规则
$ iptables-save > /etc/sysconfig/iptables

# 可以使用如下命令从指定的文件载入规则,注意:重载规则时,文件中的规则将会覆盖现有规则。
$ iptables-restore < /etc/sysconfig/iptables

匹配条件

首先我们要明确一点,当规则中同时存在多个匹配条件时,多个条件之间默认存在”与”的关系,即报文必须同时满足所有条件,才能被规则匹配。

基本匹配条件总结

-s 用于匹配报文的源地址,可以同时指定多个源地址,每个 IP 之间用逗号隔开,也可以指定为一个网段。

1
2
3
$ iptables -t filter -I INPUT -s 192.168.1.111,192.168.1.118 -j DROP
$ iptables -t filter -I INPUT -s 192.168.1.0/24 -j ACCEPT
$ iptables -t filter -I INPUT ! -s 192.168.1.0/24 -j ACCEPT

-d 用于匹配报文的目标地址,可以同时指定多个目标地址,每个 IP 之间用逗号隔开,也可以指定为一个网段。

1
2
3
$ iptables -t filter -I OUTPUT -d 192.168.1.111,192.168.1.118 -j DROP
$ iptables -t filter -I INPUT -d 192.168.1.0/24 -j ACCEPT
$ iptables -t filter -I INPUT ! -d 192.168.1.0/24 -j ACCEPT

-p 用于匹配报文的协议类型,可以匹配的协议类型 tcp、udp、udplite、icmp、esp、ah、sctp 等(centos7 中还支持 icmpv6、mh)。

1
2
$ iptables -t filter -I INPUT -p tcp -s 192.168.1.146 -j ACCEPT
$ iptables -t filter -I INPUT ! -p udp -s 192.168.1.146 -j ACCEPT

-i 用于匹配报文是从哪个网卡接口流入本机的,由于匹配条件只是用于匹配报文流入的网卡,所以在 OUTPUT 链与 POSTROUTING 链中不能使用此选项。

1
2
$ iptables -t filter -I INPUT -p icmp -i eth4 -j DROP
$ iptables -t filter -I INPUT -p icmp ! -i eth4 -j DROP

-o 用于匹配报文将要从哪个网卡接口流出本机,由于匹配条件只是用于匹配报文流出的网卡,所以在 INPUT 链与 PREROUTING 链中不能使用此选项。

1
2
$ iptables -t filter -I OUTPUT -p icmp -o eth4 -j DROP
$ iptables -t filter -I OUTPUT -p icmp ! -o eth4 -j DROP

扩展匹配条件总结

tcp 扩展模块

常用的扩展匹配条件如下:

  • -p tcp -m tcp –sport 用于匹配 tcp 协议报文的源端口,可以使用冒号指定一个连续的端口范围
  • -p tcp -m tcp –dport 用于匹配 tcp 协议报文的目标端口,可以使用冒号指定一个连续的端口范围
1
2
3
4
5
$ iptables -t filter -I OUTPUT -d 192.168.1.146 -p tcp -m tcp --sport 22 -j REJECT
$ iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m tcp --dport 22:25 -j REJECT
$ iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m tcp --dport :22 -j REJECT
$ iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m tcp --dport 80: -j REJECT
$ iptables -t filter -I OUTPUT -d 192.168.1.146 -p tcp -m tcp ! --sport 22 -j ACCEPT
multiport 扩展模块

常用的扩展匹配条件如下:

  • -p tcp -m multiport –sports 用于匹配报文的源端口,可以指定离散的多个端口号,端口之间用逗号隔开
  • -p udp -m multiport –dports 用于匹配报文的目标端口,可以指定离散的多个端口号,端口之间用逗号隔开
1
2
3
4
5
$ iptables -t filter -I OUTPUT -d 192.168.1.146 -p udp -m multiport --sports 137,138 -j REJECT
$ iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m multiport --dports 22,80 -j REJECT
$ iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m multiport ! --dports 22,80 -j REJECT
$ iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m multiport --dports 80:88 -j REJECT
$ iptables -t filter -I INPUT -s 192.168.1.146 -p tcp -m multiport --dports 22,80:88 -j REJECT

自定义链

创建自定义链

1
2
# 示例:在filter表中创建IN_WEB自定义链
$ iptables -t filter -N IN_WEB

引用自定义链

1
2
# 示例:在INPUT链中引用刚才创建的自定义链
$ iptables -t filter -I INPUT -p tcp --dport 80 -j IN_WEB

重命名自定义链

1
2
# 示例:将IN_WEB自定义链重命名为WEB
$ iptables -E IN_WEB WEB

删除自定义链

删除自定义链需要满足两个条件

  • 自定义链没有被引用
  • 自定义链中没有任何规则
1
2
# 示例:删除引用计数为0并且不包含任何规则的WEB链
$ iptables -X WEB

Netfilter 防火墙原理

简单说 netfilter 机制就是对进出主机的数据包进行过滤。我们可以通过 iptables 设置一些规则 (rules)。所有进出主机的数据包都会按照一定的顺序匹配这些规则,如果匹配到某条规则,就执行这条规则对应的行为,比如抛弃该数据包或接受该数据包。下图展示了 netfilter 依据 iptables 规则对数据包过滤的大致过程:

img
img

对数据包进行过滤。检查通过则接受 (ACCEPT)数据包进入主机获取资源,如果检查不通过,则予以丢弃 (DROP)!如果所有的规则都没有匹配上,就通过默认的策略 (Policy)决定数据包的去向。注意,上图中的规则是有顺序的!比如数据包与 rule 1 指定的规则匹配,那么这个数据包就会执行 action 1 指定的行为,而不会继续匹配后面的规则了。

下面我们看一个例子。假设我们的 Linux 主机提供了 web 服务,所以需要放行访问 80 端口的数据包。 但是你发现来自 13.76.1.65 的数据包总是恶意的尝试入侵我们的 web 服务器,所以需要丢弃来自 13.76.1.65 数据包。 我们的 web 服务器并不提供 web 服务之外的其它服务,所以直接丢弃所有的非 web 请求的数据包。 总结后就是我们需要下面三条规则:

  • Rule 1 丢弃来自 13.76.1.65 数据包
  • Rule 2 接受访问 web 服务的数据包
  • Rule 3 丢弃所有的数据包

如果我们不小心把上面的规则顺序写错了,比如写成了下面的样子:

  • Rule 1 接受访问 web 服务的数据包
  • Rule 2 丢弃来自 13.76.1.65 数据包
  • Rule 3 丢弃所有的数据包

这时来自 13.76.1.65 的数据包是可以访问 web 服务的,因为来自 13.76.1.65 的数据包是符合第一条规则的,所以会被接受,此时就不会再考虑第二条规则了。

Iptables 中的 table 与 chain

Iptables 用表 (table)来分类管理它的规则 (rule),这也是 iptables 名称的由来。根据 rule 的作用分成了好几个表,比如用来过滤数据包的 rule 就会放到 filter 表中,用于处理地址转换的 rule 就会放到 nat 表中,其中 rule 就是应用在 netfilter 钩子上的函数,用来修改数据包的内容或过滤数据包。下面我们简单的介绍下最常用的 filter 表和 nat 表。 filter 从名字就可以看出,filter 表里面的规则主要用来过滤数据,用来控制让哪些数据可以通过,哪些数据不能通过,它是最常用的表。 nat 里面的规则都是用来处理网络地址转换的,控制要不要进行地址转换,以及怎样修改源地址或目的地址,从而影响数据包的路由,达到连通的目的,这是路由器必备的功能。

下图展示了 iptables 中常用的 tables 及其 rule chains:

img
img

从上图可以看出,filter 和 nat 表中默认都存在着数条 rule chain。也就是说表中的规则 (rule)又被编入了不同的链 (chain),由 chain 来决定什么时候触发 chain 上的这些规则。 Iptables 里面有 5 个内置的 chain:

  • PREROUTING:接收的数据包刚进来,还没有经过路由选择,即还不知道数据包是要发给本机还是其它机器。这时会触发该 chain 上的规则。
  • INPUT:已经经过路由选择,并且该数据包的目的 IP 是本机,进入本地数据包处理流程。此时会触发该 chain 上的规则。
  • FORWARD:已经经过路由选择,但该数据包的目的 IP 不是本机,而是其它机器,进入 forward 流程。此时会触发该 chain 上的规则。
  • OUTPUT:本地程序要发出去的数据包刚到 IP 层,还没进行路由选择。此时会触发该 chain 上的规则。
  • POSTROUTING:本地程序发出去的数据包,或者转发 (forward)的数据包已经经过了路由选择,即将交由下层发送出去。此时会触发该 chain 上的规则。

我们可以通过下图来理解这五条默认的规则链:

img
img

从上图可知,不考虑特殊情况的话,一个数据包只会经过下面三个路径中的一个: A,主机收到目的 IP 是本机的数据包 B,主机收到目的 IP 不是本机的数据包 C,本机发出去的数据包

  • 路径 A,数据包进入 Linux 主机访问其资源,在路由判断后确定是向 Linux 主机请求数据的数据包,此时主要是通过 filter 表的 INPUT 链来进行控制。
  • 路径 B,数据包经由 Linux 主机转发,没有使用主机资源,而是流向后端主机。在路由判断之前对数据包进行表头的修改后,发现数据包需要透过防火墙去后端,此时的数据包就会通过路径 B。也就是说,该封包的目标并非我们的 Linux 主机。该场景下数据包主要经过的链是 filter 表的 FORWARD 以及 nat 表的 POSTROUTING 和 PREROUTING。
  • 路径 C,数据包由 Linux 主机向外发送。比如响应客户端的请求要求,或者是 Linux 主机主动发送出去的数据包,都会通过路径 C。它会先进行路由判断,在确定了输出的路径后,再通过 filter 表的 OUTPUT 链来传送。当然,最终还是会经过 nat 表的 POSTROUTING 链。

由此我们可以总结出下面的规律。 Filter 表主要跟进入 Linux 主机的数据包有关,其 chains 如下:

  • INPUT:主要与想要进入 Linux 主机的数据包有关
  • OUTPUT:主要与 Linux 主机所要发送出去的数据包有关
  • FORWARD:与 Linux 主机没有关系,它可以对数据包进行转发

Nat (地址转换) 表主要在进行来源与目的之 IP 或 port 的转换,其 chains 如下:

  • PREROUTING:在进行路由判断之前所要执行的规则 (DNAT)
  • POSTROUTING:在进行路由判断之后所要执行的规则 (SNAT)
  • OUTPUT:与发送出去的数据包有关

Iptables 中的规则 (rules)

规则 (rules)存放在特定表的特定 chain 上,每条 rule 包含下面两部分信息:

Matching Matching 就是如何匹配一个数据包,匹配条件很多,比如协议类型、源/目的 IP、源/目的端口、in/out 接口、包头里面的数据以及连接状态等,这些条件可以任意组合从而实现复杂情况下的匹配。

Targets Targets 就是找到匹配的数据包之后怎么办,常见的有下面几种:

  • DROP:直接将数据包丢弃,不再进行后续的处理
  • RETURN: 跳出当前 chain,该 chain 里后续的 rule 不再执行
  • QUEUE: 将数据包放入用户空间的队列,供用户空间的程序处理
  • ACCEPT: 同意数据包通过,继续执行后续的 rule
  • 跳转到其它用户自定义的 chain 继续执行

比如下面的规则,只要是来自内网的 (192.168.1.0/24)数据包都被接受:

$ sudo iptables -A INPUT -i eth0 -s 192.168.1.0/24 -j ACCEPT

用户自定义 chains

除了 iptables 预定义的 5 个 chain 之外,用户还可以在表中定义自己的 chain,用户自定义的 chain 中的规则和预定义 chain 里的规则没有区别,不过由于自定义的 chain 没有和 netfilter 里面的钩子进行绑定,所以它不会自动触发,只能从其它 chain 的规则中跳转过来。比如 docker 就创建了一些自定义的 chain:

img
img

参考资料