文章目录
策略路由需要分两块来看:非协议相关(策略路由框架)和协议相关。内核中可以有多个协议族都可以支持策略路由,这部分共性操作可以抽象出来,这就是非协议相关代码要做的事,各个协议族特有的操作通过一组回调函数来实现。这篇笔记就从初始化过程中看看策略路由的实现和组织结构。
源代码路径 | 说明 |
---|---|
net/core/fib_rules.c | 策略路由非协议相关实现 |
net/ipv4/fib_rules.c | 策略路由的IPv4实现 |
include/net/fib_rules.h | 策略路由相关数据结构定义 |
数据结构
在看系统如何组织策略路由之前,先来看看相关的数据结构。
策略路由操作集: fib_rules_ops
策略路由框架代码和各个支持策略路由的协议族之间通过该对象沟通。每个支持策略路由的协议族都需要向框架注册一个该实例对象。
struct fib_rules_ops
{
int family;
// 系统中所有的策略路由操作集对象被组织在net->rules_ops链表中
struct list_head list;
// 具体协议的策略路由规则都是在fib_rule的基础上扩展的,
// 该字段记录了具体协议的策略路由规则大小
int rule_size;
// 不同协议使用的地址长度不同,比如IPv4为4字节,IPv6为32字节,框架会使用该字段对地址进行校验
int addr_size;
// 因为策略路由规则的action可以是跳转到另外一条规则继续匹配(FR_ACT_GOTO),在配置规则时,如果指定
// 的目标规则还不存在,那么当前规则就是unresolved的,此时该字段就会+1,当目标规则被配置时,会
// 处理这种指向,然后递减该字段
int unresolved_rules;
// 记录该协议族的策略路由规则表中target是FR_ACT_GOTO的规则数目
int nr_goto_rules;
// 当策略规则匹配时,调用该函数进行处理
int (*action)(struct fib_rule *, struct flowi *, int, struct fib_lookup_arg *);
// 检查查询条件和指定规则是否匹配
int (*match)(struct fib_rule *, struct flowi *, int);
// 当添加策略路由规则时,调用该函数对协议特有的规则字段进行配置
int (*configure)(struct fib_rule *, struct sk_buff *, struct nlmsghdr *,
struct fib_rule_hdr *, struct nlattr **);
// 根据条件查找指定的策略路由。删除策略路由时使用
int (*compare)(struct fib_rule *, struct fib_rule_hdr *, struct nlattr **);
// dump策略路由规则时,调用该函数填充要返回的信息到skb中
int (*fill)(struct fib_rule *, struct sk_buff *, struct nlmsghdr *, struct fib_rule_hdr *);
// 添加策略路由规则时,如果没有指定优先级并且具体协议有实现该回调,
// 那么调用该函数为规则获取默认的优先级
u32 (*default_pref)(struct fib_rules_ops *ops);
// 添加或者删除策略路由后,需要向用户态发送通知,调用该函数计算发通知时需要带的payload的大小
size_t (*nlmsg_payload)(struct fib_rule *);
// 策略路由规则添加或删除后,应该刷新相关的路由缓存,调用该回调让具体协议实现自己的刷新方式
void (*flush_cache)(void);
// 组播通知的nlgroup组
int nlgroup;
const struct nla_policy *policy; // Netlink消息属性解析policy
// 策略路由规则链表,保存了该协议族所有的策略路由规则
struct list_head rules_list;
struct module *owner;
struct net *fro_net;
};
IPv4策略路由操作集: fib4_rules_ops_template
static struct fib_rules_ops fib4_rules_ops_template = {
.family = AF_INET,
.rule_size = sizeof(struct fib4_rule),
.addr_size = sizeof(u32),
.action = fib4_rule_action,
.match = fib4_rule_match,
.configure = fib4_rule_configure,
.compare = fib4_rule_compare,
.fill = fib4_rule_fill,
.default_pref = fib4_rule_default_pref,
.nlmsg_payload = fib4_rule_nlmsg_payload,
.flush_cache = fib4_rule_flush_cache,
.nlgroup = RTNLGRP_IPV4_RULE,
.policy = fib4_rule_policy,
.owner = THIS_MODULE,
};
通用策略路由规则: fib_rules
策略路由框架对通用的策略路由规则进行了定义,各个协议族可以在此基础上进一步扩展,添加各个协议族特有的字段。
struct fib_rule
{
// 相同协议族的策略路由规则以链表的形式组织到fib_ruls_ops->ruls_list中
struct list_head list;
// 该策略路由规则一旦被匹配,会先持有引用计数,结束后再释放,防止其在被引用期间被修改
atomic_t refcnt;
// 输入网络设备索引和名字,这个字段是策略路由规则的一个匹配条件,如果为-1,表示禁用这条规则
int ifindex;
char ifname[IFNAMSIZ];
// 结合防火墙中的fwmark,通过mark标记,可以让策略路由规则只匹配指定标记的数据包
u32 mark;
u32 mark_mask;
// 策略路由规则的优先级,策略路由规则是按照优先级顺序查找的,值越小,优先级越高
u32 pref;
u32 flags;
// 只有action是FR_ACT_TO_TBL时才有效,标识这条规则引用的路由表ID,其它情况应该是RT_TABLE_UNSPEC
u32 table;
// 策略路由规则匹配后要执行的操作,最常见的就是FR_ACT_TO_TBL,表示查询指定路由表
u8 action;
// 当action是FR_ACT_GOTO时,target记录了目标规则的优先级,ctarget则指向目标规则
u32 target;
struct fib_rule *ctarget;
struct rcu_head rcu;
struct net *fr_net;
};
action决定了当路由规则匹配时,要执行的动作,可取的值如下:
enum
{
FR_ACT_UNSPEC,
FR_ACT_TO_TBL, // 查询指定路由表
FR_ACT_GOTO, // 跳转到指定规则继续匹配
FR_ACT_NOP, // 什么都不做,会继续匹配下一跳策略路由规则
FR_ACT_RES3,
FR_ACT_RES4,
FR_ACT_BLACKHOLE, /* Drop without notification */
FR_ACT_UNREACHABLE, /* Drop with ENETUNREACH */
FR_ACT_PROHIBIT, /* Drop with EACCES */
__FR_ACT_MAX,
};
IPv4策略路由规则: fib4_rule
AF_INET协议族对策略路由规则做了简单的扩展,定义了struct fib4_rule。
struct fib4_rule
{
// 通用规则部分
struct fib_rule common;
// 目的地址和源地址的掩码长度
u8 dst_len;
u8 src_len;
// TOS字段,可以作为一个匹配条件
u8 tos;
__be32 src;
__be32 srcmask;
__be32 dst;
__be32 dstmask;
#ifdef CONFIG_NET_CLS_ROUTE
u32 tclassid;
#endif
};
从数据结构的定义上来看,策略路由核心的数据结构组织并不复杂,可用下图表示:
策略路由框架初始化: fib_rule_init()
struct net {
...
/* core fib_rules */
struct list_head rules_ops; // 策略路由框架将所有的fib_rule_ops对象组织成链表
spinlock_t rules_mod_lock;
...
}
static int __init fib_rules_init(void)
{
int err;
// 向路由套接字注册3个命令,分别用于策略路由规则的添加、删除和查询
rtnl_register(PF_UNSPEC, RTM_NEWRULE, fib_nl_newrule, NULL);
rtnl_register(PF_UNSPEC, RTM_DELRULE, fib_nl_delrule, NULL);
rtnl_register(PF_UNSPEC, RTM_GETRULE, NULL, fib_nl_dumprule);
// 监听网络设备对象状态变化
err = register_netdevice_notifier(&fib_rules_notifier);
if (err < 0)
goto fail;
// 网络子系统相关初始化
err = register_pernet_subsys(&fib_rules_net_ops);
if (err < 0)
goto fail_unregister;
return 0;
...
}
subsys_initcall(fib_rules_init);
// 网络子系统相关初始化
static int fib_rules_net_init(struct net *net)
{
INIT_LIST_HEAD(&net->rules_ops);
spin_lock_init(&net->rules_mod_lock);
return 0;
}
IPv4策略路由初始化
这里以AF_INET协议族的初始化过程为例,看看协议族是如何初始化策略路由的。
int __net_init fib4_rules_init(struct net *net)
{
int err;
struct fib_rules_ops *ops;
// 根据模板新建一个fib_rules_ops实例,该实例会被注册到系统中
ops = kmemdup(&fib4_rules_ops_template, sizeof(*ops), GFP_KERNEL);
if (ops == NULL)
return -ENOMEM;
INIT_LIST_HEAD(&ops->rules_list);
ops->fro_net = net;
// 向框架注册,这个注册和去注册过程非常简单,不再展开
fib_rules_register(ops);
// 这里很重要,初始化默认的策略路由规则
err = fib_default_rules_init(ops);
if (err < 0)
goto fail;
net->ipv4.rules_ops = ops; // 特别的,IPv4的策略路由操作集对象也会在net中保存一个指针,方便引用
return 0;
fail:
/* also cleans all rules already added */
fib_rules_unregister(ops);
kfree(ops);
return err;
}
IPv4添加默认策略路由规则
static int fib_default_rules_init(struct fib_rules_ops *ops)
{
int err;
// 添加一条优先级为0(最高)的策略路由,无条件匹配local表,这使得
// 所有的路由查询都会优先查local表
err = fib_default_rule_add(ops, 0, RT_TABLE_LOCAL, FIB_RULE_PERMANENT);
if (err < 0)
return err;
// 添加一条优先级为0x7FFE的策略路由,无条件匹配,查询main表
err = fib_default_rule_add(ops, 0x7FFE, RT_TABLE_MAIN, 0);
if (err < 0)
return err;
// 添加一条优先级为0x7FFE的策略路由,无条件匹配,查询default表
err = fib_default_rule_add(ops, 0x7FFF, RT_TABLE_DEFAULT, 0);
if (err < 0)
return err;
return 0;
}
int fib_default_rule_add(struct fib_rules_ops *ops, u32 pref, u32 table, u32 flags)
{
struct fib_rule *r;
//按照规则大小分配内存
r = kzalloc(ops->rule_size, GFP_KERNEL);
if (r == NULL)
return -ENOMEM;
//初始化引用计数为1,action是查询指定路由表
atomic_set(&r->refcnt, 1);
r->action = FR_ACT_TO_TBL;
r->pref = pref;
r->table = table;
r->flags = flags;
r->fr_net = ops->fro_net;
/* The lock is not required here, the list in unreacheable
* at the moment this function is called */
list_add_tail(&r->list, &ops->rules_list);
return 0;
}
响应网络设备状态变化: fib_rules_event()
static struct notifier_block fib_rules_notifier = {
.notifier_call = fib_rules_event,
};
static int fib_rules_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct net_device *dev = ptr;
struct net *net = dev_net(dev);
struct fib_rules_ops *ops;
ASSERT_RTNL();
rcu_read_lock();
// 处理网络设备对象注册和去注册事件
switch (event) {
case NETDEV_REGISTER:
list_for_each_entry(ops, &net->rules_ops, list)
attach_rules(&ops->rules_list, dev);
break;
case NETDEV_UNREGISTER:
list_for_each_entry(ops, &net->rules_ops, list)
detach_rules(&ops->rules_list, dev);
break;
}
rcu_read_unlock();
return NOTIFY_DONE;
}
attach_rules()/detach_rules()
// 网络设备对象注册时,如果指定了生效的网络设备对象,则设置它
static void attach_rules(struct list_head *rules, struct net_device *dev)
{
struct fib_rule *rule;
list_for_each_entry(rule, rules, list) {
if (rule->ifindex == -1 &&
strcmp(dev->name, rule->ifname) == 0)
rule->ifindex = dev->ifindex;
}
}
// 网络设备对象去注册时,如果指定了生效的网络设备对象,则设置它为-1
static void detach_rules(struct list_head *rules, struct net_device *dev)
{
struct fib_rule *rule;
list_for_each_entry(rule, rules, list)
if (rule->ifindex == dev->ifindex)
rule->ifindex = -1;
}
总结
策略路由的初始化做的事情其实很清晰:
框架部分初始化:
- 向RT_NETLINK注册三个子命令,分别用于策略路由的增加、删除和查询;
- 初始化管理各个协议族fib_rules_ops对象的链表和锁;
- 向网络设备接口层注册回调,当网卡状态发生变化时,能够更新策略路由数据库;
对于协议族相关的初始化,以AF_INET为例:
- 向框架注册自己的fib_rules_ops对象;
- 创建三条默认的策略路由, 分别用于查询local、main和default表;