IPv4路由数据库之Netlink接口

本文详细介绍了如何通过Netlink接口操作IPv4路由数据库,包括添加、删除路由项,以及路由项的配置和数据结构。内容涵盖rtmsg结构、路由消息属性、fib_config配置,以及inet_rtm_newroute和inet_rtm_delroute函数的使用。
摘要由CSDN通过智能技术生成


用户空间程序如ip和netstat等工具都是通过Netlink接口实现对内核空间中路由表的增删改查,route是通过ioctl()系统调用与内核交互的,这篇笔记介绍了较新的Netlink路由配置接口实现。

涉及的文件有:

源代码路径说明
include/net/ip_fib.hIPv4路由数据库头文件
core/ipv4/fib_frontend.cIPv4路由数据库对外接口实现文件

命令行

ip route命令详细的介绍可以参考手册ip(8),下面是最常用的命令行参数。

ip route { add | del | change | append | replace } ROUTE

ROUTE := NODE_SPEC [ INFO_SPEC ]

NODE_SPEC := [ TYPE ] PREFIX [ tos TOS ] [ table TABLE_ID ] [ proto RTPROTO ] [ scope SCOPE ]
INFO_SPEC := { NH | nhid ID } OPTIONS FLAGS [ nexthop NH ] ...
NH := [ via [ FAMILY ] ADDRESS ] [ dev STRING ] [ weight NUMBER ] NHFLAGS

可以将命令行中路由项的NODE_SPEC和INFO_SPEC分别与内核路由项中的fib_node、fib_info结构对应。

路由项TYPE:可取的值有unicast、unreachable、blackhole、prohibit、local、broadcast、multicast,对应下面rtmsg->type字段。默认的type为unicast;

PREFIX:路由项目的地址前缀,可以附加’/'指定子网掩码,如192.168.1.0/24,如果不指定子网掩码,那么默认长度为32,这样该路由项表示是一个主机路由;

SCOPE: 路由项的作用域,有link、host、global、数字几种,含义对应下面的rtmsg->scope;

via [ FAMILY ] ADDRESS: 下一跳路由的IP地址;

数据结构

路由项配置信息是作为标准的Netlink消息的数据部分传递到内核的,配置内容主要是rtmsg。

路由消息: rtmsg

struct rtmsg
{
	unsigned char rtm_family; // 路由项所属协议族,如AF_INET、AF_INET6;
	 // 路由项目的地址掩码长度,特别注意是掩码长度,和属性中的RTA_DST配合表示路由项的目的网络
	unsigned char rtm_dst_len;
	unsigned char rtm_src_len; // 源地址掩码长度
	unsigned char rtm_tos; // 路由项的服务质量,对应IP首部的服务字段
    // 路由项所述路由表ID,由于只有1个字节,所以取值最大为255,因此用户态能够配置256个路由表,
    // 但是内核实际上并不是只能保存256个路由表,内核对此是没有限制的;
	unsigned char rtm_table; 
	unsigned char rtm_protocol;	// 路由协议,含义见下方
	unsigned char rtm_scope; // 路由项的作用域,含义见下方	
	unsigned char rtm_type;	// 路由项类型,含义见下方

	unsigned rtm_flags;
};

对于路由表ID,内核有如下规划,从表ID的定义可以看出,当支持策略路由时(多路由表),用户态程序能够新建的表的ID范围为1~252。

路由表ID说明
RT_TABLE_UNSPEC0未知,当操作该路由表时,内核默认会操作main表
RT_TABLE_DEFAULT253default表,用于策略路由的默认路由表
RT_TABLE_MAIN254main表,主路由表,不指定具体要操作的表时就是main表
RT_TABLE_LOCAL255local表,该表维护了本机IP地址相关的路由,由内核自己维护,用户态无法操作

对于路由协议,表示的是路由项是如何产生的,可取的值如下,其中常见的是RTPROT_KERNEL和RTPROT_STAIC。

路由协议说明
RTPROT_UNSPEC未知
RTPROT_REDIRECT该路由项是通过ICMP重定向消息添加的
RTPROT_KERNEL该路由项是由内核自己添加的,local表的路由大多数属于这一类
RTPROT_BOOTUP该路由项是在启动时通过boot协议添加的
RTPROT_STAIC该路由项是由管理员静态添加的,用户态配置的路由属于这一类

对于路由项的作用域表示的是本机距离该路由项表示的目的地址的距离。如下,值(0, 200)之间的数字是可以由管理员自己配置的。值越大,距离本机越近。所以叫它作用域其实是不利于理解的。

路由作用域说明
RT_SCOPE_UNIVERSE0表示目的地和本机距离超过了1跳
RT_SCOPE_SITE200
RT_SCOPE_LINK253表示目的地位于和本机直接相连的网络,如同一局域网内的其它主机地址
RT_SCOPE_HOST254表示目的地是本地地址
RT_SCOPE_NOWHERE255表示目的地不存在,这是个保留值

对于路由表项的类型,它一定程度上决定了数据包匹配该路由表项后应该采取的动作。

路由表项类型说明
RTN_UNSPEC一个未初始化的值
RTN_LOCAL该路由项的目的地址是主机本地地址
RTN_UNICAST该路由是一条到直连或者非直连目的地址的单播路由,添加路由时如果不指定路由类型,那么默认取该值
RTN_MULTICAST该该路由项的目的地址是一个多播地址
RTN_BROADCAST该该路由项的目的地址是一个广播地址
RTN_ANYCASTIPv4中不使用
RTN_UNREACHABLE路由结果是不可达,会给源地址回复主机不可达ICMP报文
RTN_BLACKHOLE路由结果是不可达,会悄悄的丢弃数据包,不做任何回应
RTN_PROHIBIT路由结果为不可达,会给源地址回复"communication administratively prohibited"类型的ICMP报文
RTN_THROW路由结果为不可达,会给源地址回复网络不可达ICMP报文
  • rtm_flags: 路由标记,

Netlink消息属性

netlink消息中,可以配置如下属性,某些属性和rtmsg中的字段是有重复的,当重复时,以属性为准,不过用户态配置程序也不应该为二者指定不一样的值。

属性说明
RTA_UNSPEC忽略,无意义的属性
RTA_DST该路由项的目的地址
RTA_SRC该路由项的源地址
RTA_IIF该该路由项的输入网络设备索引
RTA_OIF该该路由项的输出网络设备索引
RTA_GATEWAY该该路由项的网关地址
RTA_PRIORITY该该路由项的优先级
RTA_PREFSRC该该路由项的首选源地址
RTA_METRICS该该路由项和协议有关的度量值,如RTT等
RTA_MULTPATH多路径路由下有意义,即该路由项的下一跳地址
RTA_PROTOINFO基于策略路由的防火墙信息
RTA_FLOW基于策略路由的分类标签
RTA_CACHEINFO缓存的路由信息

路由项配置: fib_config

在内核中,Netlink消息携带的路由配置信息最后是转换为fib_config对象后向路由数据库配置的,该结构的内容基本上就是上述内容的直接赋值。

struct fib_config {
	u8 fc_dst_len;
	u8 fc_tos;
	u8 fc_protocol;
	u8 fc_scope;
	u8 fc_type;
	/* 3 bytes unused */
	u32	fc_table;	//路由表ID
	__be32 fc_dst;
	__be32 fc_gw;
	int	fc_oif;
	u32	fc_flags;
	u32 fc_priority;
	__be32 fc_prefsrc;
	struct nlattr *fc_mx;	//保存属性配置中的RTA_METRICS
	struct rtnexthop *fc_mp;
	int	fc_mx_len;
	int	fc_mp_len;
	u32	fc_flow;
	u32	fc_nlflags; // Netlink消息标准首部中的flag
	// 调用者信息,如pid等
	struct nl_info	fc_nlinfo;
 };

Netlink消息到fib_config转换: rtm_to_fib_config()

static int rtm_to_fib_config(struct net *net, struct sk_buff *skb,
			    struct nlmsghdr *nlh, struct fib_config *cfg)
{
	struct nlattr *attr;
	int err, remaining;
	struct rtmsg *rtm;
	// 校验参数的合法性,如大小、对齐方式等
	err = nlmsg_validate(nlh, sizeof(*rtm), RTA_MAX, rtm_ipv4_policy);
	if (err < 0)
		goto errout;
	memset(cfg, 0, sizeof(*cfg));

	rtm = nlmsg_data(nlh);
	cfg->fc_dst_len = rtm->rtm_dst_len;
	cfg->fc_tos = rtm->rtm_tos;
	cfg->fc_table = rtm->rtm_table;
	cfg->fc_protocol = rtm->rtm_protocol;
	cfg->fc_scope = rtm->rtm_scope;
	cfg->fc_type = rtm->rtm_type;
	cfg->fc_flags = rtm->rtm_flags;
	cfg->fc_nlflags = nlh->nlmsg_flags;

	cfg->fc_nlinfo.pid = NETLINK_CB(skb).pid;
	cfg->fc_nlinfo.nlh = nlh;
	cfg->fc_nlinfo.nl_net = net;

	if (cfg->fc_type > RTN_MAX) {
		err = -EINVAL;
		goto errout;
	}
	// 解析属性,这里要注意的是有些属性和上面的字段是重复的,很显然,当配置了属性时,以属性值为准
	nlmsg_for_each_attr(attr, nlh, sizeof(struct rtmsg), remaining) {
		switch (nla_type(attr)) {
		case RTA_DST:
			cfg->fc_dst = nla_get_be32(attr);
			break;
		case RTA_OIF:
			cfg->fc_oif = nla_get_u32(attr);
			break;
		case RTA_GATEWAY:
			cfg->fc_gw = nla_get_be32(attr);
			break;
		case RTA_PRIORITY:
			cfg->fc_priority = nla_get_u32(attr);
			break;
		case RTA_PREFSRC:
			cfg->fc_prefsrc = nla_get_be32(attr);
			break;
		case RTA_METRICS:
			cfg->fc_mx = nla_data(attr);
			cfg->fc_mx_len = nla_len(attr);
			break;
		case RTA_MULTIPATH:
			cfg->fc_mp = nla_data(attr);
			cfg->fc_mp_len = nla_len(attr);
			break;
		case RTA_FLOW:
			cfg->fc_flow = nla_get_u32(attr);
			break;
		case RTA_TABLE:
			cfg->fc_table = nla_get_u32(attr);
			break;
		}
	}
	return 0;
errout:
	return err;
}

添加路由项: inet_rtm_newroute()

static int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
{
	struct net *net = skb->sk->sk_net;
	struct fib_config cfg;
	struct fib_table *tb;
	int err;

	// 解析路由配置信息到fib_config对象中
	err = rtm_to_fib_config(net, skb, nlh, &cfg);
	if (err < 0)
		goto errout;
	// 根据路由表ID,找到对应的路由表fib_table对象,如果还没有对应的路由表,
	// 则新建一个路由表,然后返回其指针
	tb = fib_new_table(net, cfg.fc_table);
	if (tb == NULL) {
		err = -ENOBUFS;
		goto errout;
	}
	// 调用路由表的insert()回调,不同的数据结构实现方式不同
	err = tb->tb_insert(tb, &cfg);
errout:
	return err;
}

删除路由项: inet_rtm_delroute()

实现逻辑和inet_rtm_addroute()基本一致,不再过多赘述。

static int inet_rtm_delroute(struct sk_buff *skb, struct nlmsghdr* nlh, void *arg)
{
	struct net *net = skb->sk->sk_net;
	struct fib_config cfg;
	struct fib_table *tb;
	int err;

	err = rtm_to_fib_config(net, skb, nlh, &cfg);
	if (err < 0)
		goto errout;
	tb = fib_get_table(net, cfg.fc_table);
	if (tb == NULL) {
		err = -ESRCH;
		goto errout;
	}
	// 调用路由表的tb_delete()接口
	err = tb->tb_delete(tb, &cfg);
errout:
	return err;
}

路由项的Dump

当用户空间使用ip route show命令时,会向kernel查询当前路由表中的路由项,在内核中的接口是inet_dump_fib(),该接口由于不会对路由表内容进行修改,这里暂且不分析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值