Linux下让某程序的所有数据包通过指定网口发出
文章目录
让某程序的所有数据通过指定网口发出。这是结合上一篇搭建的虚拟连接实现的特定功能,达到一些 目的——比如让没有WAN地址的服务通过vps具备的WAN地址提供交互(可以了解一下frp)。
iptables机制
iptables是linux常用的通讯管理工具,是内核netfilter组件的用户空间工具。使用iptables可以
实现各种各样的发包控制。而最关键的是知道IP包是如何在系统中穿梭的。如图所示
iptables就是在包的流通过程中流过的链中添加一定的规则,从而对包作出一些处理以实现目标。
iptables有许多的hook点,通过netfilter提供的接口运行,比如OUTPUT、INPUT、FORWARDING、
POSTROUTIING,就在图中所描述的各个位置。而iptables根据应用的特点,分成几个类型,nat包转发、
mangle包修改、filter包过滤,在各个hook点包流过几类处理的顺序为mangle->nat->filter。
称各个OUTPUT、INPUT之类的为chain,可以在chain中添加rule,被一条chain处理完之后
才会被下一条chain处理。在chain中流过rule的顺序是,先流过先添加的rule。当然,如果
某rule匹配了包,并作出了DROP处理,这个包就被丢弃,后面所有的过程都不进行了,如果进行
RETURN,则分两种情况,如果是在OUTPUT、INPUT等顶级的chain中RETURN,则直接跳过该链
中后续的所有rule,直接设为该链的默认状态,比如PREROUTING一般为ACCEPT。如果是从
上述的顶级链中跳转到的次级甚至更低级的链中RETURN,则返回上级链进行跳转的位置,并继续
流过上级链的rule。
wireguard的fwmark
iptables的mangle->nat->filter流通过程中,可以对包添加mark,用以区分包,从而对不同特征
的包作不同的处理,mark只在内核里面有,在从要网卡发出时会将其去除。wireguard也具备添加mark
的能力,但是它的添加方式是直接在源码里面实现,未利用iptables,直接在生成的包中添加了mark。
也就是在图中的local process部分生成的包就包含了mark,可以在后续的OUTPUT以及POSTROUTING
链中做一些其他的处理。通过以下命令可以验证:
iptables -t mangle -A OUTPUT -m mark --mark $wg_mark -j DROP
由于mangle是从内核空间出来后遇到的第一个chain,在此DROP掉所有包含wireguard的mark的包,
果然就无法ping通server端了。这就说明,wireguard的fwmark是直接在构建包的时候就添加的,而
不是利用iptables在后续过程添加的。
包转发场景
client,与server之间通过虚拟连接连接,IP为10.0.0.3,网口名vlan3, 本地具备路由器分配的
地址192.168.1.3用以普通通信,网口名eth3。
server, 虚拟连接IP为10.0.0.1,网口名vlan1并且规定,从client发来的包只能为10.0.0.0/24网段,具备
公网IP: 111.100.13.92, 网口名eth1
需求: 将client中的程序A的所有包的接收和发送通过虚拟连接进行。
client端
1. 将所有从vlan3接收的包从vlan3返回
ip rule add iif vlan3 table 3
ip route add table 3 via 10.0.0.1
2. 让所有A发出的包通过vlan3发出
2.1 由于iptalbes已经不支持以发出包的程序PID来匹配包,采用发出包的UID来进行匹配,
新建一个用户专门用来运行程序A,我们称之为UserA
useradd UserA
2.2 通过iptables对UserA发出的包添加mark,使用ip-rule根据mark指定路由表,在路由
表中(也就是上面的table 3),所有的包默认通过vlan3发出。
iptables -t mangle -A OUTPUT -m mark --mark wireguard's-mark -j RETURN
# 这一条必须,让`vlan3`发出来的包不会被定向到table3,否则就死循环了。
iptables -t mangle -A OUTPUT -m owner --uid-owner UserA -j MARK --set-mark 1
# 可以直接使用用户名匹配,给所有该用户发出的包打上`1`标签
ip rule add fwmark 1 table 3
# 让所有的`1`标签的包通过路由表3路由
RETURN那一条并非所有的虚拟连接软件都需要,取决于具体的实现。比如openvpn就不需要类似的设置, 其实一般来说在实际网卡发包,经过网卡之后,owner信息还有mark的都被去掉了,因为已经出了内核 空间。至于后面数据到虚拟网卡后,从另外一个连接把数据重新打包发出,那就是虚拟连接工具运行用 户的事情了。但是wireguard可能读取了发包的owner信息,并且将重新打包的包继承了该owner信息。 所以如果在OUTPUT的mangle上不加以区分wireguard的包,就会造成无限循环。当然还有另外一种解决 方式。
iptables -t mangle -A OUTPUT -m owner --uid-owner UserA -j MARK --set-mark 1
# 可以直接使用用户名匹配,给所有该用户发出的包打上`1`标签
ip rule add fwmark 1 table 3
# 让所有的`1`标签的包通过路由表3路由
ip route add table 3 server_wan_ip via 192.168.1.3 dev eth3
这样,在经过路由之后,虽然wireguard再次发包会被mark 1,但是wireguard发包都是发往server
的ip地址,在OUTPUT之后的route decision后,他会被eth3 src=192.168.1.3 dst=server_ip,
而普通的包会变成vlan3 src=192.168.1.3 dst=xxxx,在POSTROUTING上作SNAT
iptables -t nat -A POSTROUTING -o vlan3 -j MASQUERADE
这样也实现了包的正常流通。
PS:之前未作SNAT,上面几步做完发现怎么都无法连接,切换到UserA下
进行网络连接都连不上,所以需要进一步的debug。用到了iptables中的LOG目标。
iptables -t mangle -A OUTPUT -m owner --uid-owner UserA -j LOG --log-prefix "owner-matched: "
# 在上述的mangle型的OUTPUT链中再添加一条LOG记录, log-prefix是自定义log信息的开头
进行该项操作后,再用UserA进行网络请求,通过dmesg查看iptables记录的LOG,发现确实是
匹配到了用户请求,但是Src项竟然是192.168.1.3出口也是网卡eth3。接下来看看,
在OUTPUT之后的route decision之后发生了什么
iptables -t mangle -A POSTROUTING -m mark --mark 1 -j LOG --log-prefix "postrouting: "
发现由于OUTPUT中mark的设置,路由确实是使用了table3, 出口变成了vlan3,但是Src并没有发生
改变,仍然是192.168.1.3,也就是说,虽然在OUTPUT之后再次route decision时根据路由表更改了
出口网卡,但是并不会对源地址进行更改,所以我们需要人为的处理一下。
iptables -t nat -A POSTROUTING -m mark --mark 1 -j SNAT --to-source 10.0.0.3
再次以用户UserA进行网络请求,发现可以正常运作了。当然,这是对所有wireguard发出的包未
进行mark下的规则,否则就要用-o vlan3作限定,并且给table 3添加专门到server的规则。
server端
server端相比就简单许多,由于他的路由表的默认路由就是我们的公网IP,首先为了让10.0.0.1/24
方面的包也会通过该IP转发,需要开启kernel的forwarding选项。编辑/etc/sysctl.conf,添加
net.ipv4.conf.all.forwarding = 1
然后还需要做一个源地址变换,iptables有个MASQUERADE,可以不用人为选取替换的目标IP,而会 智能的选用系统用来连接外部网络的网址进行替换:
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j MASQUERADE
到此,所有从client发出的包都能够从server发出到外网了,但是外网却不能主动发包到内网。 加入client的程序A在端口1000监听,等待外网的主动请求,那么在server端作以下转发即可:
iptables -t nat -A PREROUTING -p tcp --dport 1000 -j DNAT --to 10.0.0.3:1000
iptables -t nat -A PREROUTING -p udp --dport 1000 -j DNAT --to 10.0.0.3:1000
这样在紧跟PREROUTING后的route decision中,会进行forwarding,将其通过虚拟连接转发给
client。完美!
文章作者 thinkeryu
上次更新 2017-10-29