路由数据库之Netlink接口

用户空间程序如ip和netstat等工具都是通过Netlink接口实现对内核空间中路由表的增删改查,route是通过ioctl()系统调用与内核交互的,我们不关注。内核支持的路由表项相关的操作如下图所示:
路由数据库之Netlink接口_第1张图片
涉及的文件有:

源代码路径 说明
include/net/ip_fib.h 路由数据库头文件

1. 数据结构

路由信息都是作为标准的Netlink消息的数据部分传递到内核的,整个数据包的结构如下图所示:
路由数据库之Netlink接口_第2张图片
首先是标准的netlink消息头nlmsghdr,这里不对这部分内容作介绍,重点看后面的rtmsg和Attributes。

1.1 rtmsg

  • rtm_family:1个字节,即路由项所属协议族,如AF_INET、AF_INET6;
  • rtm_dst_len:1个字节,目的地址掩码长度;
  • rtm_src_len:1个字节,源地址掩码长度;
  • rtm_tos:1个字节,路由的服务质量字段;
  • rtm_table:1个字节,路由项所述路由表ID,由于只有1个字节,所以取值最大为255,因此用户态能够操作256个路由表,但是内核实际上并不是只能保存256个路由表,实际上内核是没有任何限制的;
路由表ID 说明
RT_TABLE_UNSPEC 0 未知,当操作该路由表时,内核默认会操作main表
RT_TABLE_DEFAULT 253 default表,用于策略路由的默认路由表
RT_TABLE_MAIN 254 main表,主路由表,不指定具体要操作的表时就是main表
RT_TABLE_LOCAL 255 local表,该表维护了本机IP地址相关的路由,由内核自己维护,用户态无法操作

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

  • rtm_protocol:1个字节,路由协议,表示的是路由项是如何产生的,常见的两个字段是RTPROT_KERNEL和RTPROT_STAIC。
路由协议 说明
RTPROT_UNSPEC 未知
RTPROT_REDIRECT 该路由项是通过ICMP重定向消息添加的
RTPROT_KERNEL 该路由项是由内核自己添加的,local表的路由大多数属于这一类
RTPROT_BOOTUP 该路由项是在启动时通过boot协议添加的
RTPROT_STAIC 该路由项是由管理员静态添加的,用户态配置的路由属于这一类
  • rtm_scope:路由项的作用域。所谓路由项的作用域,表示的是本机距离该路由项表示的目的地址的距离。如下,值(0, 200)之间的数字是可以由管理员自己配置的。值越大,距离本机越近。
/* rtm_scope

   Really it is not scope, but sort of distance to the destination.
   NOWHERE are reserved for not existing destinations, HOST is our
   local addresses, LINK are destinations, located on directly attached
   link and UNIVERSE is everywhere in the Universe.

   Intermediate values are also possible f.e. interior routes
   could be assigned a value between UNIVERSE and LINK.
*/
enum rt_scope_t
{
	RT_SCOPE_UNIVERSE=0,
/* User defined values  */
	RT_SCOPE_SITE=200,
	RT_SCOPE_LINK=253,
	RT_SCOPE_HOST=254,
	RT_SCOPE_NOWHERE=255
};
路由作用域 说明
RT_SCOPE_UNIVERSE 0 未知
RT_SCOPE_SITE 200 该路由项是通过ICMP重定向消息添加的
RT_SCOPE_LINK 253 该路由项是由内核自己添加的,local表的路由大多数属于这一类
RT_SCOPE_HOST 254 该路由项是在启动时通过boot协议添加的
RT_SCOPE_NOWHERE 255 该路由项是由管理员静态添加的,用户态配置的路由属于这一类
  • rtm_type:路由表项的类型,它一定程度上定义了数据包匹配该路由表项后应该采取的动作。
路由表项类型 说明
RTN_UNSPEC 一个未初始化的值
RTN_LOCAL 该路由项的目的地址是一个本地地址
RTN_UNICAST 该路由是一条到单播地址直连或者非直连路由,添加路由时如果不指定路由类型,那么默认取该值
RTN_MULTICAST 该该路由项的目的地址是一个多播地址
RTN_BROADCAST 该该路由项的目的地址是一个广播地址
RTN_ANYCAST IPv4中不使用
RTN_BLACKHOLE/RTN_UNREACHABLE/RTN_PROHIBIT/RTN_THROW 这几种都会路由失败,只是失败方式不同而已
  • rtm_flags: 路由标记,

1.2 Attributes

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 缓存的路由信息

1.3 struct fib_config

在内核,为了保存一个路由配置消息的参数,定义了struct fib_config,该结构的内容基本上就是netlink消息中各个成员的直接赋值:

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;
	//调用者信息,如pid等
	struct nl_info		fc_nlinfo;
 };

内核使用函数rtm_to_fib_config()函数实现netlink消息数据包skb到该结构的转换:

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;
}

2. 路由添加接口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;
	//解析路由配置信息到结构struct fib_config中
	err = rtm_to_fib_config(net, skb, nlh, &cfg);
	if (err < 0)
		goto errout;
	//根据路由表ID,找到对应的路由表结构struct 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;
}

3. 路由删除接口: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;
}

4 路由项的Dump

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

你可能感兴趣的:(linux网络)