策略路由之概述

本文介绍了策略路由的数据结构,包括fib_rules_ops、fib4_rules_ops_template和fib4_rule,以及策略路由的初始化过程,如IPv4策略路由规则的设置和网络设备状态变化时的响应。
摘要由CSDN通过智能技术生成


策略路由需要分两块来看:非协议相关(策略路由框架)和协议相关。内核中可以有多个协议族都可以支持策略路由,这部分共性操作可以抽象出来,这就是非协议相关代码要做的事,各个协议族特有的操作通过一组回调函数来实现。这篇笔记就从初始化过程中看看策略路由的实现和组织结构。

源代码路径说明
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;
}

总结

策略路由的初始化做的事情其实很清晰:

框架部分初始化:

  1. 向RT_NETLINK注册三个子命令,分别用于策略路由的增加、删除和查询;
  2. 初始化管理各个协议族fib_rules_ops对象的链表和锁;
  3. 向网络设备接口层注册回调,当网卡状态发生变化时,能够更新策略路由数据库;

对于协议族相关的初始化,以AF_INET为例:

  1. 向框架注册自己的fib_rules_ops对象;
  2. 创建三条默认的策略路由, 分别用于查询local、main和default表;
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值