用户空间程序如ip和netstat等工具都是通过Netlink接口实现对内核空间中路由表的增删改查,route是通过ioctl()系统调用与内核交互的,我们不关注。内核支持的路由表项相关的操作如下图所示:
涉及的文件有:
源代码路径 | 说明 |
---|---|
include/net/ip_fib.h | 路由数据库头文件 |
路由信息都是作为标准的Netlink消息的数据部分传递到内核的,整个数据包的结构如下图所示:
首先是标准的netlink消息头nlmsghdr,这里不对这部分内容作介绍,重点看后面的rtmsg和Attributes。
路由表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。
路由协议 | 说明 |
---|---|
RTPROT_UNSPEC | 未知 |
RTPROT_REDIRECT | 该路由项是通过ICMP重定向消息添加的 |
RTPROT_KERNEL | 该路由项是由内核自己添加的,local表的路由大多数属于这一类 |
RTPROT_BOOTUP | 该路由项是在启动时通过boot协议添加的 |
RTPROT_STAIC | 该路由项是由管理员静态添加的,用户态配置的路由属于这一类 |
/* 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 | 该路由项是由管理员静态添加的,用户态配置的路由属于这一类 |
路由表项类型 | 说明 |
---|---|
RTN_UNSPEC | 一个未初始化的值 |
RTN_LOCAL | 该路由项的目的地址是一个本地地址 |
RTN_UNICAST | 该路由是一条到单播地址直连或者非直连路由,添加路由时如果不指定路由类型,那么默认取该值 |
RTN_MULTICAST | 该该路由项的目的地址是一个多播地址 |
RTN_BROADCAST | 该该路由项的目的地址是一个广播地址 |
RTN_ANYCAST | IPv4中不使用 |
RTN_BLACKHOLE/RTN_UNREACHABLE/RTN_PROHIBIT/RTN_THROW | 这几种都会路由失败,只是失败方式不同而已 |
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 | 缓存的路由信息 |
在内核,为了保存一个路由配置消息的参数,定义了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;
}
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;
}
实现逻辑和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;
}
当用户空间使用ip route show命令时,会向kernel查询当前路由表中的路由项,在内核中的接口是inet_dump_fib(),该接口由于不会对路由表内容进行修改,这里暂且不分析。