引言


iptables 是配置 linux 网络必不可少的命令,但是网上文章都过于专注于命令细节上的操作,比如创建一个 Rule,删除一个 Chain,新手很容易看得云里雾里。要想快速理解 iptables,应该先弄懂其总体设计,之后涉及到细节上的操作,再去看文档,手册或者博客才能事半功倍,真正明白这些命令在干什么。

整体逻辑


之所以网上一些大段 iptables 操作脚本看起来复杂迷惑,就是 iptables 设计得非常灵活,不仅仅可以当防火墙,还可以进行端口转发,ip 分组过滤等等复杂的功能,甚至可以把它当成微型的编程语言,所以我们要先俯瞰 iptables 整体的操作逻辑,之后再陷入细节时才不会慌张。

Table, Chain 和 Rule

最简单的说法就是 Table 由很多个 Chain 组成,而 Chain 由一些 Rule 串起来组成, 所以称之为”链“。

Rule 就是对于请求一个断言,如果请求满足断言就执行指定的操作(官方文档中称这个操作为目标,Target),举个例子,”如果请求来自 192.168.19.123,就将其拒绝“,这就是一个 Rule,而”拒绝“就是一个目标,常见的目标有接受,拒绝,转发,调用另一个 Chain 等等。

请求会在 Chain 中自上而下遍历,直到遇到一个匹配的 Rule,然后调用它的目标。

Chains

四表五链

iptables 中 Table 的数目是规定死的四个:

还有五个预定义的 Chain:

Chains

路由决策是指判断数据包的目的地是否是本机,如果是则进入 INPUT Chain,否则进入 FORWARD Chain

PREROUTING ,POSTROUTIONG 和 FORWARD 只有作为路由器使用时才会被调用,正常电脑就只会经过 INPUT 和 OUTPUT

每个 Table 都含有几个预定义 Chain:

Table Chains

对于不同 Table 的同名 Chain,执行的优先级为(从高到低):

将内置的所有 Chain 串起来看就象下图:

内置 Chain 总体视图

目标

比如下面的命令:

iptables -A INPUT -p tcp --dport 80 -j ACCEPT

表示如果发现目标端口是 80 的 tcp 流量就放行,其中 -p tcp --dport 80 就是条件,而 ACCEPT 就称作目标(Target)了。

-A INPUT 表示将这条 Rule 追加到(Append)INPUT Chain 的最后面,这里没有指定 Table,默认就是 filter,也可以通过 -t 指定 Table。

常见的目标有:

匹配规条件除了上面两个,还有一些常用的:

Chain 类似与函数?

其实除了内置的 Chain ,用户还可以创建自定义 Chain:

iptables -t nat -N DD

这就表示在 nat Table 中新建一条名为 DD 的 Chain。

在之前的我们画的 Chain 完整调用图中似乎没有看到自定义 Chain 的位置,那到底要怎么使用自定义 Chain 呢?

其实 Chain 与 Chain 之间是可以互相调用的。之前提到过 -j 参数用来指定目标,其实目标可以是表中的另一个 Chain,像这样:

iptables -t nat -A PREROUTING -p tcp -j DD

表示在 PREROUTING 链中的所有 tcp 协议包都会调用 DD Chain。

这里调用有可能一去不复返(比如在 DD 中遇到了 ACCEPT, DROP 等等),也有可能像函数调用的那样 RETURN 回来,没错,有一个特殊的目标就叫做 RETURN,用于从调用链中返回:

iptables -t nat -A DD -p tcp -j RETURN 

其他常用命令

查看一个 Chain 中所有的规则:

# --line-numbers 表示要显示行号
$ iptables -t nat -L DD --line-numbers
Chain DD (1 references)
num  target     prot opt source               destination         
1    DD         tcp  --  anywhere             anywhere      

删除 Chain 中指定行号的规则(删除 DD 中行号为 1 的规则):

iptables -t nat -D DD 1

删除整个 Chain:

sudo iptables -t nat  --delete-chain DD

案例:路由器的透明代理配置


为了方便,我们常会在路由器上配置透明代理,这个通过 OpenWrt 或者梅林等固件在图形界面上“点点点”就能实现,那我们为什么还要学习手动配置呢?

这里我们以给 TROJAN 配置透明代理为例(别的协议其实也差不多。。。),首先我们以 nat 模式启动 TROJAN 客户端(而不是我们平常用的 client 模式),nat 的配置怎么写可以看 官方配置文档。假设我们将其启动在了 1234 端口。

## 定义 TROJAN Chain
iptables -t nat -N TROJAN # 新建一个名为 TROJAN 的链
# 直连局域网地址 192.168.0.0/16
iptables -t nat -A TROJAN -d 192.168.0.0/16 -j RETURN  
# 直连 SO_MARK 为 0xff 的流量(0xff 是 16 进制数,数值上等同与上面配置的 255),此规则目的是避免代理本机(网关)流量出现回环问题
iptables -t nat -A TROJAN -p tcp -j RETURN -m mark --mark 0xff 
iptables -t nat -A TROJAN -p tcp -j REDIRECT --to-ports 1234 # 流量转发到 1234 端口(即 Trojan)

## 将 TROJAN Chain 添加到 PREROUTING 实现对局域网设备的透明代理
iptables -t nat -A PREROUTING -p tcp -j TROJAN
## 将 TROJAN Chain 添加到 OUTPUT 实现对本设备的透明代理
iptables -t nat -A OUTPUT -p tcp -j TROJAN

看一下 TROJAN 链的形状:

$ iptables -t nat -L TROJAN
Chain TROJAN (0 references)
target     prot opt source               destination
RETURN     all  --  anywhere             192.168.0.0/16
RETURN     tcp  --  anywhere             anywhere             mark match 0xff
REDIRECT   tcp  --  anywhere             anywhere             redir ports 1234

可以顺手看一下 OpenWrt 上启动了 ShadowSocksR Plus 插件后,它的 iptables 有什么变化(点击 Status->Firewall,展示的其实就是 iptables):

ShadowSocksR Plus+ 的核心 iptables

是不是感觉和我们写得差不多,除此之外,它还利用 iptables 的 ipset (就里面有很多 ip 的一个集合,可以用于判断 ip 是否在里面,这里就不详述了)功能实现到了 gfwlist:

ShadowSocksR Plus+ 实现 gfwlist

除此之外 ShadowSocksR Plus 还实现了定时更新 gfwlist 的功能,这些功能如果我们都要自己实现的话,尽管不难,工作量还是不小的,如果只是因为协议不支持的的话,我们只要先登录 OpenWrt 命令行手动启动一个协议客户端,把 SS_SPEC_WAN_FW 链的最后一条规则改成自己的端口就可以复用 ShadowSocksR Plus 的绝大多数功能了,可见看得懂 iptables 还是有点卵用的。