系列链接
- 土制 Linux 路由器(Chapter 1):设置一个简单的家用 Linux 路由器
- 土制 Linux 路由器(Chapter 2):策略路由
- Work in progress…
写在前面
传统的路由只是通过目标地址来转发,而无法依据诸如源地址,协议等转发数据包。
为了解决这个痛点,策略路由就诞生了。
策略路由则是通过匹配一系列的规则(rules),符合规则,则让数据包按照匹配规则执行对应的动作(action),装这一系列 rules 和 actions 的东西叫做 RPDB (routing policy database) 。
系统则通过 查询 RPDB -> 对应匹配条件 -> 对应 action
这个流程来决定如何把包转发出去。
例如可以让来自 192.168.1.2 查 810 号路由表,根据 810 号路由表来转发。
此篇将简短的介绍 Linux 上的策略路由,也就是 ip-rule
(配合 nftables) 的使用方法,以及会附上一两个实验 (不过环境还是需要自己搭建喔) 。
因为想控制篇幅,加上 IPv6 和 IPv4 大致配置流程相同,所以只会涉及到 IPv4 的部分, IPv6 部分读者可以自行尝试。
环境则是以上篇为基础。
主角是我们全能的 iproute2
, nftables 则是负责更改规则来打标(fwmark)。
如果发行版自带的是 busybox 的
ip
命令,个人推荐单独安装一个iproute2
, AlpineLinux 可以直接使用 apk 包管理进行安装。
废话不多嗦,咱们就开始吧。
正文
ip-rule
初始的规则和它们对应执行的动作
我们可以先看一下 ip rule
的输出
alpinerouter:~# ip rule
0: from all lookup local
32766: from all lookup main
32767: from all lookup default
初始状态下我们会有这三个规则。
这三个规则是匹配全部的,优先级从高到低。
第一个 local 表优先级最高,装了local的和广播的路由。
可以通过 ip route show table local
来查看。
|
|
main 表则是我们所熟悉的默认查找到的表,输入 ip route show
或者 ip route show table main
即可看到。
|
|
第三个是 default
表,默认为空,如果前面没有匹配到就会查这个表,这条规则也可以被删除。
对于 IPv6 ,可以用 ip -6 rule
和 ip -6 route show table $table_id
来查看,和 IPv4 差不多,不过相对于 IPv4 来说,默认配置少了 default 表。
使用 ip rule
添加和删除规则
我们可以来看一下 ip rule help
来查看使用方法。
|
|
常用的几个 SELECTOR(筛选条件)
from PREFIX
和to PREFIX
,即利用源地址和目标地址来匹配。fwmark FWMARK
,常用于防火墙打标,根据标记匹配。iif STRING
和oif STRING
,根据入接口和出接口匹配。pref NUMBER
,即设定要添加的规则的优先级,越小越优先。uidrange NUMBER-NUMBER
,即通过 uid 来选择,安卓的分应用代理也是这个原理。ipproto
即根据 ipv4 包中承载的协议来匹配(看的是 ip 包头部!),例如 TCP 和 UDP。sport
和dport
则是根据源端口和目标端口来匹配。
常用的 ACTION(执行的操作)
table TABLE_ID
,匹配到之后走特定的路由表。
Suppress PrefixLength 的补充
给定的解释是,不匹配任何小于等于这个前缀长度的路由。
这个的应用在 wg-quick 这个脚本中有体现。
如果把 WireGuard 的配置文件中 Peer 的 AllowedIPs 设置为 0.0.0.0/0 或者 ::/0 ,抑或两者都有,并且没有设定 Table = off
,wg-quick 启动它会自动给加上 from all lookup main suppress_prefixlength 0
的规则。
这个的意思就是,来自所有地方的数据包,首先查找 main 表,但是不匹配长度小于等于 0 的路由,也就是长度等于零的不匹配了,那0.0.0.0/0是什么呢?是默认路由。
也就是说,匹配了这个规则之后, main 表内的默认路由即使存在也不会对应给转发到默认网关了,而是继续匹配下一条规则。
具体可以阅读这里:StackOverflow: What does ip4 rule add table main suppress prefixlength 0 meaning.
添加与删除规则的例子
-
添加:
- 默认状态下,我们可以通过
ip rule add iif eth1 from 192.168.10.2/32 table 233
使从192.168.10.2
并且入接口为eth1
的数据包去查 233 号路由表。如果不指定优先级的话,优先级会从 32765(32766-1) 开始依次向下加入规则,读者可以自行尝试。
- 默认状态下,我们可以通过
-
删除:
- 上面的规则,可以通过
ip rule del table 233
这种较模糊的指定来删除(这种是通过 action 指定,也可以通过 selector 来指定),不过如果有其他 action 也是 table 233 的话,会按优先度从高到低删除。 - 因为每条规则的
pref
(优先级) 是唯一的,也可以通过指定ip rule del pref $pref_num
来精确删除;优先级可以通过ip rule
列出,例如默认配置下main
表对应优先级为 32766 。
- 上面的规则,可以通过
ip-route
添加/删除特定路由表的路由条目
添加规则之后,如果对应的路由表内没有东西的话,也是行不通的。
在上面,我们添加了让 192.168.10.2/32 走路由表 233 的规则。
此处我们可以执行以下命令让来自 192.168.10.2/32 的所有包都发到 eth2 的 192.168.70.1 上:
ip route add default via 192.168.70.1 dev eth2 table 233
这样就可以啦。
如果要删除的话可以直接通过:
ip route del default via 192.168.70.1 dev eth2 table 233
务必记住要在后面指定对应 table 号,如果不指定的话就会直接动 main 表了。
nftables
在策略路由中,nftables 主要是通过更细粒度和更灵活的匹配,给包打上标记,最后配合 ip-rule 中 fwmark
这个 SELECTOR
让包对应走不同的路由表。
不过需要注意的是,如果您正在使用 fastpath bypass 这种跳过 hooks 处理的方式来降低路由器的转发负载,那么通过打路由标记来影响路由选择这种方式就行不通了。打标的操作是在 postrouting 这个钩子执行的,而跳过的时候,则是从 ingress(prerouting 之前) 直接调到了neigh_xmit(postrouting 之后)了。
如果想要让打标影响路由选择,就应该在挂载点为 prerouting
这里添加规则,即在路由选择之前打上标记(mark before routing decisions)。
例如,如果需要来自 192.0.3.1/32 的去往 192.0.2.2 的,目标为tcp端口号 22,80,443 的连接上所有数据包都打上 0x1 的标记,并且在本机可以用conntrack -L -m 1
可以列出,可以在 /etc/nftables.nft
中加入如下内容:
|
|
之后 nft -f /etc/nftables.nft
以保存配置。
以上的思路是,先让匹配条件的数据包的连接打上标记 0x1(即连接打上connection mark),之后第二条规则则是让包打上和连接标记相同的标记(即包打上packet fwmark)。
之后的路由选择就看 ip rule 和 ip route 的功能了。
如何让三者配合使用呢?
大致思路:
- 使用
ip rule
命令加一条规则,例如让 路由标记(fwmark) 为 0x1 的数据包查找编号为 1 的路由表。 - 使用
ip route
在编号为 1 的路由表上加入路由条目。 - 使用 nftables 给符合条件的数据包打上 0x1 的路由标记。
(其中路由标记可以根据需要选择。)
之后符合条件( fwmark 为 0x1 )的数据包就会走编号为1的路由表了。
可能还有一个需要注意的地方, Alpine Linux 的 IPv4 Reverse Path Filtering 默认为1,如果开启策略路由有去程通但是回来不通的问题,可以通过sysctl关闭rp_filter。
|
|
就可以持久化了。
实验
讲了这么多,接下来就做实验吧!
实验一: 基于端口号的分流
小明在家整了一个实体机开虚拟机给群友们玩,但担心群友用他家的ISP网络看涩涩,于是他选择了一个匿名上网工具,这个匿名上网工具可以在 Linux 路由器上开出一个网络接口,接口名为 usb0,小明可以通过这个来匿名上网。
但是小明又不想影响到群友到虚拟机的联通性,于是他决定从虚拟机局域网来的端口为 10000 及以上的连接直接走 ISP(ppp0) ,而目标端口为 10000 以下的连接走 usb0 这个匿名上网通道出去,并且因为群友需要测试延迟,他希望 ICMP echo-request 也直接走 ISP(ppp0) 出去。
(其中 ppp0 和 usb0 接口出都需要进行 masquerade 操作)
你可以帮到他吗?
小明也想通过 conntrack 命令看到群友走 ISP 出去的时候的连接,你可以帮到他吗?(可选)
拓扑图长这样:
实验二: 基于 GeoIP 的策略路由
李华是一名 HomeLab 爱好者,他家接了两条宽带。其中一条是粘不通(ppp0),另外一条是移不冻(ppp1)。他听说粘不通到美国连接性很好,移不冻到香港连接性很好。
他决定他家的流量到美国的 IP 走粘不通,到香港的 IP 则走移不冻,剩下默认走粘不通。并且需要配置好相关的NAT规则。
你可以帮到他吗?
实验二材料: nftables GeoIP repository
如果实验二需要帮助,可以参考:nftables wiki: GeoIP Matching
参考资料与推荐阅读
- 本篇涉及:
- 没有涉及到或涉及不完全的:
末尾
此篇本来想要写实验的,但是感觉太多了,这次就没有去写它,不过基础部分应该还是讲到了。两个实验的话读者可以自行尝试去做。
不过实验只是实验,有可以上网的两个不同的接口就可以做了,接口名也不是必须是那个名称。
关于第二个上网卡,个人是使用的 4G USB Dongle 去模拟的(关于上网卡为什么是usb0的这件事)。
实验的话下次会写的, 都会有的,都会有的。(心虚
不过这大概还是基础的一部分,更高级的一些选项咱倒是没有用过了。
以及 wg-quick 的策略路由实现也推荐读者去看一下,它是一套完整的通过 ip-rule
, ip-route
以及 nftables
的策略路由实现(AllowedIPs 前缀长度为0的时候)。
同样,如果有发现文章中有误,欢迎指出来喔。
评论区正在施工中~