策略路由的实现

概述

IPv4 rule route 配置示意图

测路路由介绍

关于策略路由是什么,为什么需要策略路由, man手册里写的这一段,我认为是最经典的一个解释。
清晰明了,这里借用并翻译下。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Classic routing algorithms used in the Internet make routing
decisions based only on the destination address of packets (and
in theory, but not in practice, on the TOS field).

In some circumstances, we want to route packets differently
depending not only on destination addresses but also on other
packet fields: source address, IP protocol, transport protocol
ports or even packet payload. This task is called 'policy
routing'.

To solve this task, the conventional destination based routing
table, ordered according to the longest match rule, is replaced
with a 'routing policy database' (or RPDB), which selects routes
by executing some set of rules.

尝试翻译下:

1
2
3
4
5
6
传统的路由方法,是基于报文的目的地地址做路由查找(理论上支持,但没啥卵用的 TOS 字段就不讨论了)

在一些场景下, 我们不仅想根据你报文的目的地做选择路由, 同时还想根据报文的其他字段:源地址、ip 协议、tcp 端口 甚至是报文负载,选择路由。
这个功能就被称为"测路路由(policy route)"

为了实现这个功能,传统的基于路由表和最长匹配规则的路由查找方式,就被'routing policy database(RPDB)'取代(包装+升级)了。RPDB通过执行一些规则路由集合来选择路由。

对需求背景说的非常清楚,具体实现,后续我们展开讲解。
取代,我个人任务这里用replaced 不够严谨:
这里的RPDB取代传统路由,是在原有传统路由的基础上,增加新的模块,提供更多样的路由查找方式。也许原作想表达的是路由查找接口被取掉。
所以这里保留直译。

如何使用

假设一个场景:为了方便,这里使用一个容器做实验,容器有三个网口,veth0,veth2,veth4, 并配有/24掩码的IP地址。
veth0: 192.168.0.1/24
veth2: 192.168.2.1/24
veth4: 192.168.4.1/24
系统配置一条缺省路由指向veth4

添加rule

添加table

规则路由(rule route)

这部分详见ip rule命令帮助手册,
https://man7.org/linux/man-pages/man8/ip-rule.8.html
简要总结:

  • 支持多种形式的路由查找,不再仅仅局限于传统路由,根据目的地址查找一种模式。
  • 每条规则(rule) 都有一个优先级, 内核按照priority从高到底按顺序放到一个链表中。priority值越小,优先级越高,0对应优先级最高。
  • rule route采用了类似match-action模式, 不过rule route称之为SELECTORACTION
  • SELECTOR 支持多种形式,比如IP、PORT、进出网口、tos以及非操作(not)
  • ACTION 中几个重要的类型:
    • table:到指定的TABLE_ID对应的路由表里查找(所以这里要求必须支持多table)
    • goto: 跳转到指定的rule。这里只能往链表中,当前节点后面的节点去跳,不能往前跳。
    • nat: 支持IP地址nat(TODO nat 废弃?)
  • 路由查找时候使用,按链表里顺序(优先级)依次比较每个节点的的match 条件,如果满足则执行对应的 action,否则跳到下一个节点,继续执行。如果满足
    • ACTION 是跳转,则直接跳过中间节点,继续匹配链表剩余节点。
    • action是table X,则使用目的地ip去路由表table X里查找最长匹配的路由,查找的路由结果即为最终路由查找结果。
      说明:
  • route里goto 可以反复多次嵌套多条rule,其他action均不能嵌套执行。比如lookup,table查找的结果就是最终结果,不会再有其他跳转和嵌套。

使用场景

差异化网络服务:比如内网的公网网关,有两个公网出口,有两类流量经过这个网关,流量1: 架设在内部的视频服务器,通过DNAT映射到外网的一个端口,并采用IP直通模式到服务器,出流量特定,固定的源IP/port。 其他内网设备访问公网的流量。
需求:希望视频流量走公网口A。其他流量走公网出口B。
这时候,可以通过一个策略路由,把源ip+port的流量指定走网口 A,缺省路由走公网B。

策略路由实例

1
2
3
4
5
6
7
8
9
10
11
12
root@martin-Standard-PC-Q35-ICH9-2009:/home/martin# ip rule show
0: from all lookup local
32763: from all sport 100-1000 goto 32766
32764: from 2.1.1.1/24 lookup 20000
32765: from 1.1.1.1/24 lookup 10000
32766: from all lookup main
32767: from all lookup default
root@martin-Standard-PC-Q35-ICH9-2009:/home/martin# ip r show table 10000
default via 192.168.100.1 dev veth0
root@martin-Standard-PC-Q35-ICH9-2009:/home/martin# ip r show table 20000
default via 192.168.122.200 dev enp1s0
root@martin-Standard-PC-Q35-ICH9-2009:/home/martin#

IPv4 rule route 配置示意图

没有嵌套

内核代码解析

根据内核v6.6代码整理

数据结构

每个netns下有个IPv4相关的路由信息变量struct netns_ipv4 ipv4,
然后通过rules_ops指向IPv4对应的struct fib_rules_ops.
在这个ops下有一个rules_list链表,串联了所有的rule route。
每个rule对应一个结构体struct fib_rule

  • 每次增加rule规则时候,struct netns_ipv4里的变量fib_has_custom_rules被更新为true,以记录规则路由被启用了。
  • ops的初始化
    系统初始化时候,在fib4_rules_init里,通过复制模版创建一个ops,并把net信息记录到’ops’里。
    这个新创建的’ops’又被挂到’net’下的rule_ops链表里. 最后这个ops被保存到’IPv4’下的rules_ops
    1
    2
    3
    4
    5
    6
    fib4_rules_init
    --> fib_rules_register(&fib4_rules_ops_template, net)
    --> --> ops = kmemdup && ops->fro_net = net ;//复制模版创建一个ops,并把net信息记录到ops里。
    --> --> __fib_rules_register
    --> net->ipv4.rules_ops = ops;
    --> net->ipv4.fib_has_custom_rules = false;

IPv4 rule route 配置示意图

1
2
3
4
5
 61 struct net {
...
125 struct list_head rules_ops;
...
134 struct netns_ipv4 ipv4;
1
2
3
4
5
6
7
8
9
10
44 struct netns_ipv4 {
...
59 #ifdef CONFIG_IP_MULTIPLE_TABLES
60 struct fib_rules_ops *rules_ops;
61 struct fib_table __rcu *fib_main;
62 struct fib_table __rcu *fib_default;
63 unsigned int fib_rules_require_fldissect;
64 bool fib_has_custom_rules;
65 #endif
66 bool fib_has_custom_local_routes;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 60 struct fib_rules_ops {
61 int family;
62 struct list_head list;
...
74 int (*match)(struct fib_rule *,
75 struct flowi *, int);
76 int (*configure)(struct fib_rule *,
77 struct sk_buff *,
78 struct fib_rule_hdr *,
79 struct nlattr **,
80 struct netlink_ext_ack *);
...
92
94 struct list_head rules_list;
...
98 };
1
2
3
4
5
6
7
8
9
10
11
 20 struct fib_rule {
21 struct list_head list;
22 int iifindex;
...
27 u32 table;
28 u8 action;
33 __be64 tun_id;
34 struct fib_rule __rcu *ctarget;
35 struct net *fr_net;
...
47 };

函数调用关系

IPv4路由查找的关键函数
+ ip_route_output_flow:路由查找的入口函数
+ fib_table_lookup: 在一个具体table里,查找路由
tcp/udp/icmp/raw等类型的socket发送数据时候, 先调用到函数ip_route_output_flow,进而调用到fib_lookup,在这里通过宏CONFIG_IP_MULTIPLE_TABLES并分两种场景支持多table单table

  • 单table:只有一个main路由table表,不支持多table。`
  • 多table: 支持多个table。这是支持规则路由的一起必要条件。

case 1