一个普通技术宅的点点滴滴

0%

包的路由转圈圈——谈谈使用nftables配置透明代理碰到的那些坑

透明代理,可以使得在代理后面的所有流量都经过代理,而发出流量的软件本身并不知道代理的存在。但网上大多配置教程都以iptables为主,由于本人用的是nftables,就尝试依葫芦画瓢搞了一个,同时也大概弄清除了其中的原理,供参考。

首先要准备一个透明代理客户端,如ss_redir,在PROXY_PORT进行监听。然后针对TCP和UDP设置不同的规则。


TCP部分

TCP是比较容易进行转发的,只需要在nftables里面进行设置forward就行了。
为了让配置文件看起来更直观,首先将需要绕过不走代理的ip(本地地址、远程代理服务器的地址等)放到一个文件里,这里以/etc/whitelist.ips为例子,文件结构如下所示。

PS:这里非常重要,必须配置对,否则会形成环路循环转发

/etc/whitelist.ips

1
2
3
4
5
define whitelist = {
127.0.0.1/24,
192.168.0.0/16,
.....
}

注意这里不要出现有交集的ip区间,否则会报错。

然后开始编辑nftables的配置文件(使用nft命令时,当priority为负值时会出错,遇到这种情况时需要直接编辑/etc/nftables.conf以后使用nft flush ruleset && nft -f /etc/nftables.conf导入配置文件。

首先在配置文件开头插入这一句,导入白名单文件

1
include "/etc/whitelist.ips"

然后对应iptables的nat表,配置文件添加如下的一个nat表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
table ip nat {
set whitelistset {
type ipv4_addr
flag interval
elements = $whitelist
}
chain proxy {
ip daddr @whitelist return
ip protocol tcp redirect to :[PROXY_PORT]
}
chain output {
type nat hook output priority filter; policy accept;
jump proxy
}
chain prerouting {
type nat hook output prerouting priority dstnat; policy accept;
jump proxy
}
}

上面的PROXY_PORT换成你实际的透明代理的端口,可以看到这样会将这台机器上产生的流量(由output链控制)和经过这台机器路由的流量(比如这台机器是路由器,由prerouting链控制)转发到本地的PROXY_PORT端口。
在linux中,代理程序可以通过调用一些内核提供的API来获取TCP包转发前的目的地址和端口,这样的话直接转发给透明代理就可以很好的处理。但是UDP不能这样做,内核并不允许获取原来的这些信息。所以对于UDP,我们就需要用到另外一种方法。


UDP部分

UDP的透明代理,需要借助一个内核模块,叫做TPROXY,他可以在不改变数据包的目的地址的情况下进行路由转发,nftables中可以调用这个模块,在很多iptables配置的文章中也已经介绍过,这里不再展开做介绍。
同样的,编辑/etc/nftables.conf,对应iptables中的mangle表,添加如下配置的一个表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
table ip mangle{
set whitelistset {
type ipv4_addr
flags interval
elements = $whitelist
}
chain output {
type route hook output priority mangle;policy accept;
ip daddr @whitelistset return
ip protocol udp mark set 0x233
}
chain prerouting {
type filter hook prerouting priority mangle; policy accept;
ip daddr @whitelistset return
ip protocol udp tproxy to 127.0.0.1:PROXY_PORT
}
}

上面我们做的,在mangle表中创建了两条链,output链的type为route,这里的route就是对应了iptables的mangle表,但是route type只能hook output点,所以prerouting链的type就变成了filter。
由于TPROXY只能在prerouting链处理,在nftables的流向图中(文后附),output链之后,还跟了一个reroute的流程,要触发这个reroute,我们就需要给数据包头部打上标签(任意一个数值,这里是0x233)。这样在reroute阶段,系统会检测到数据包的头部发生了改变,于是数据包被打到prerouting的地方重新进行路由。
而在prerouting阶段,被reroute的数据包和经过本机路由的数据包一起,如过目的地址不在白名单中,就tproxy打到透明代理去。整个过程非常清晰明了。

PS:在网上的许多教程中,都提到需要进行这样的两条操作

1
2
ip rule add fwmark 0x233 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100

其作用是将标记为0x233,并且目标为本地地址的数据包经过回环网口送出,但是经过本人实际测试,加和不加并没有什么区别,我认为即使不配置,默认情况系统也应该已经能够知道这条规则,所以并不需要添加。如果有不对的地方,希望读者不吝赐教。