内核版本:3.4.39

Linux路由子系统代码量虽说不是很多,但是难度还是有的,最近在分析路由子系统这一块,对它的框架有了基本的了解,如果要想掌握的话估计还得再花点时间阅读代码,先把框架记录下来。路由子系统可以划分为三个子部分,路由缓存,路由策略和路由表,前两者已经总结过了,今天再总结下路由表。路由表和其它模块类似,都有初始化、添加、删除、查询等操作,要说区别吧,可能是数据结构组织不一样,不同的数据结构需要不同的算法。

看《深入理解Linux网络技术内幕》这本书路由子模块这一部分,它介绍的路由表是基于hash表来组织,但是新版本的内核这一块已经改成lpc-trie树来组织,lpc-trie树,网上简称字典树,lpc表示path compression(路径压缩), level compression(平面压缩),路由表的添加、删除、查找都是基于该树实现,具体的实现还是蛮复杂的,先看下它的组织图:

上图左边部分fib_table_hash就表示路由表hash数组,hash值就是路由表ID,每个路由表都由一个fib_table结构体表示,这个结构体尾部存放一个占位指针,用来指向路由trie树,树种由很多中间节点和叶子节点,中间节点的结构体为tnode,叶子节点为leaf, 无论是中间节点还是叶子节点,都含有一个key值,该值即为ipv4地址,同一条路径上的节点拥有相同的前缀,比如1.1.1.1和1.1.1.2,leaf_info包含了子网掩码长度,fib_alias包含了路由项里面的tos等信息,fib_alias指向fib_info,这里面也包含了路由信息,fib_nh用来保存下一跳网关信息,可以看到,一个路由项由多个数据结构组成,之所以用这么多结构体而不是用一个超大的结构体是因为路由里面很多信息是可以共用的,比如说相同的下一跳等等,考虑到大型骨干路由器路由表项可以达到数万到数以百万,如果每个路由项都要一个大结构体的话,估计内存有点紧张,不如将路由项分割成多个块,相同的块可以共享,有一点需要注意,每个路由项都有一个唯一的fib_alias结构体。

路由表初始化流程就是申请缓存、注册netlink消息处理函数:

路由初始化主要函数是ip_fib_init():

void __init ip_fib_init(void)
{//注册netlink路由添加、删除和dump命令处理函数rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, NULL);rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL, NULL);rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib, NULL);//初始化路由表和路由缓存register_pernet_subsys(&fib_net_ops);//注册通知链处理函数,监听系统其它模块信息register_netdevice_notifier(&fib_netdev_notifier);register_inetaddr_notifier(&fib_inetaddr_notifier);//初始化路由用到的缓存池fib_trie_init();
}

当使用ip route add添加路由时会通过netlink将信息下发下来,然后调用路由系统注册的netlink处理函数,这里是inet_rtm_newroute,该函数即对下发参数进行合理性检查,检查通过则添加到对应的trie路由树中,没指定路由表id的话,默认添加到main表。

fib_net_ops是个函数集,在子系统启动的过程中会被调用:

static struct pernet_operations fib_net_ops = {.init = fib_net_init,.exit = fib_net_exit,
};

fib_net_init是启动过程中的处理函数,主要申请路由表缓存:

static int __net_init fib_net_init(struct net *net)
{int error;//初始化路由缓存和策略error = ip_fib_net_init(net);if (error < 0)goto out;//创建netlinkerror = nl_fib_lookup_init(net);if (error < 0)goto out_nlfl;//初始化proc文件error = fib_proc_init(net);if (error < 0)goto out_proc;
out:return error;out_proc:nl_fib_lookup_exit(net);
out_nlfl:ip_fib_net_exit(net);goto out;
}

路由表缓存的申请是ip_fib_net_init函数:

//创建路由表缓存和默认策略或者默认路由表
static int __net_init ip_fib_net_init(struct net *net)
{int err;size_t size = sizeof(struct hlist_head) * FIB_TABLE_HASHSZ;/* Avoid false sharing : Use at least a full cache line */size = max_t(size_t, size, L1_CACHE_BYTES);//创建路由表缓存,net->ipv4.fib_table_hash = kzalloc(size, GFP_KERNEL);if (net->ipv4.fib_table_hash == NULL)return -ENOMEM;//初始化策略路由和路由表err = fib4_rules_init(net);if (err < 0)goto fail;return 0;fail:kfree(net->ipv4.fib_table_hash);return err;
}

上述就是路由表初始化的过程

看下路由表是怎么添加的,一般情况下应用层添加路由有两种手段,一种是使用ip route添加,另一种是使用route添加,虽然都是添加路由,但是它俩和路由系统通信机制不一样,前者使用netlink,后者使用ioctl。看下ip route的添加,当调用ip route命令的时候,该命令会将参数通过netlink传递给内核的netlink模块,然后调用相应的事件处理函数,添加的时候调用的是inet_rtm_newroute:

//添加路由
static int inet_rtm_newroute(struct sk_buff *skb, struct nlmsghdr *nlh, void *arg)
{struct net *net = sock_net(skb->sk);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的路由表存在则返回该表,不存在则新建tb = fib_new_table(net, cfg.fc_table);if (tb == NULL) {err = -ENOBUFS;goto errout;}//插入路由err = fib_table_insert(tb, &cfg);
errout:return err;
}

该函数首先是将应用层下发的信息转换成一个标准的配置结构体里面,然后检查指定的路由表是否存在,最终调用fib_table_insert来添加路由。

ioctl添加基本上和netlink相同,除了通信机制的不同,但是对于路由表的操作都是相同的接口:

int ip_rt_ioctl(struct net *net, unsigned int cmd, void __user *arg)
{struct fib_config cfg;struct rtentry rt;int err;switch (cmd) {//添加路由case SIOCADDRT:        /* Add a route *///删除路由case SIOCDELRT:      /* Delete a route */if (!capable(CAP_NET_ADMIN))return -EPERM;//复制应用层数据if (copy_from_user(&rt, arg, sizeof(rt)))return -EFAULT;rtnl_lock();//将应用层数据转换成路由子系统可识别的结构体err = rtentry_to_fib_config(net, cmd, &rt, &cfg);if (err == 0) {struct fib_table *tb;if (cmd == SIOCDELRT) {//删除操作tb = fib_get_table(net, cfg.fc_table);if (tb)err = fib_table_delete(tb, &cfg);elseerr = -ESRCH;} else {//添加操作tb = fib_new_table(net, cfg.fc_table);if (tb)err = fib_table_insert(tb, &cfg);elseerr = -ENOBUFS;}/* allocated by rtentry_to_fib_config() */kfree(cfg.fc_mx);}rtnl_unlock();return err;}return -EINVAL;
}

可以看到添加操作都是调用fib_table_insert操作,该操作就是对trie路由树进行添加和删除处理。

初始化和配置看完了,看下查询是怎么回事。

系统查询路由通常由两个地方,一个是收到报文的时候,另一个是发送报文的时候。

当然查找路由不一定非要查询路由表,首先是查找路由缓存,没有命中的话则查询策略路由,根据策略路由动作再来查询路由表

从上图可以看到,收发报文最终都是调用fib_table_lookup函数来查找路由表,这个函数就是在trie树中查找匹配的路由项。查找流程还是蛮复杂的,应该说关于trie树的操作都是有点难度,无论是插入还是查询,这一块我目前还没有完全搞清楚,待后续有足够的实力再来讲一下trie树的操作。有兴趣的同学可以去参考下trie树的一篇论文,路由表的实现是参考该论文的,链接放在参考目录里。

参考目录:

1. 《Linux Kernel Networking -  Implementation and Theory》

2. 《深入理解Linux网络技术内幕》

3.  《Implementing a dynamic compressed trie》   https://pdfs.semanticscholar.org/e880/05c8801983758917bf6e647da97f1027c86b.pdf

tcp/ip 协议栈Linux内核源码分析八 路由子系统分析三 路由表相关推荐

  1. tcp/ip 协议栈Linux内核源码分析14 udp套接字接收流程一

    内核版本:3.4.39 前面两篇文章分析了UDP套接字从应用层发送数据到内核层的处理流程,这里继续分析相反的流程,看看数据是怎么从内核送到应用层的. 与发送类似,内核也提供了多个接收数据的系统调用接口 ...

  2. tcp/ip 协议栈Linux内核源码分析九 IPv6分片ip6_fragment 分析

    内核版本:3.4.39 IPv6的分片流程和IPv4基本一致,这一点内核源码作者也说了.流程比较简单,分片的时候判断是否满足快速分片,满足的话直接一个接一个加上分片扩展选项发送出去,不满足的话就只能走 ...

  3. tcp/ip 协议栈Linux内核源码分析15 udp套接字接收流程二

    内核版本:3.4.39 上篇我们分析了UDP套接字如何接收数据的流程,最终它是在内核套接字的接收队列里取出报文,剩下的问题就是谁会去写入这个队列,当然,这部分工作由内核来完成,本篇剩下的文章主要分析内 ...

  4. tcp/ip 协议栈Linux内核源码分析12 udp套接字发送流程一

    内核版本:3.4.39 因为过往的开发工作中既包括内核网络层模块的开发,又包括应用层程序的开发,所以对于网络数据的通信有那么一些了解.但是对于网络通信过程中,内核和应用层之间接口是如何运作的不是很清楚 ...

  5. tcp/ip 协议栈Linux内核源码分析十 邻居子系统分析一 概述通用邻居框架

    内核版本:3.4.39 为什么需要邻居子系统呢?因为在网络上发送报文的时候除了需要知道目的IP地址还需要知道邻居的L2 mac地址,为什么是邻居的L2地址而不是目的地的L2地址呢,这是因为目的地网络可 ...

  6. tcp/ip 协议栈Linux内核源码分析七 路由子系统分析二 策略路由

    内核版本:3.4.39 策略路由就是根据配置策略查找路由表,早期的Linux版本是不支持策略路由的,默认的查找策略就是先查找local路由表,找不到再继续查找main表,当支持策略路由功能时,内核最多 ...

  7. tcp/ip 协议栈Linux内核源码分析六 路由子系统分析一路由缓存

    内核版本:3.4.39 收到报文或者发送报文的时候都需要查找路由表,频繁的路由表查找操作时需要耗费一部分CPU的,Linux提供了路由缓存来减少路由表的查询,路由缓存由hash表组织而成,路由缓存的初 ...

  8. tcp/ip 协议栈Linux内核源码分析13 udp套接字发送流程二

    内核版本:3.4.39 继续UDP套接字发送,上一篇讲到了sock_sendmsg,这里继续,下面是sock_sendmsg的相关代码 int sock_sendmsg(struct socket * ...

  9. tcp/ip 协议栈Linux内核源码分析11 邻居子系统分析二 arp协议的实现处理

    内核版本:3.4.39 内核邻居子系统定义了一个基本的框架,使得不同的邻居协议可以共用一套代码.比起其它的内核模块,邻居子系统框架代码还是比较简单易懂的.邻居子系统位于网络层和流量控制子系统中间,它提 ...

最新文章

  1. 联泰集群发布水晶系列工作站,用于深度学习场景
  2. Java基础篇:常用类
  3. java 延迟初始化_java-不正确的延迟初始化
  4. Python学习-基础篇3-函数篇(2)
  5. C# byte[]、struct、intptr等的相互转换
  6. linux常用安装命令集锦
  7. Vue2.0增删改查案例(Vue+Less+LocalStorage)
  8. 队列模拟约瑟夫问题(洛谷P1996题题解,Java语言描述)
  9. sql计数_SQL计数区分功能概述
  10. 微软将终止支持 Win7;今日头条不与微信竞争;诺基亚芬兰裁员 | 极客头条
  11. ME53N采购申请查询时增加屏幕的增…
  12. java实验报告大全
  13. vs2015安装msdn_vs2015中文旗舰版下载
  14. 数据结构视频教程 -《数据结构C++ 复旦大学》
  15. android sqlite数据库 emoji表情,Android的Emoji表情
  16. 激光雷达运动畸变矫正
  17. 高中数学必修5;不等式常用题型解题巧技巧(名师总结)
  18. 基于S32K的MBD开发环境搭建
  19. 玩转B2B平台的商业模式你选对了吗?
  20. js 比较两个字符串形式的时间的大小

热门文章

  1. 根据文字计算Label的尺寸
  2. C/C++如何传递二维数组?
  3. POJ 1057 File Mapping 最详细的解题报告
  4. vsftp 安装日志
  5. 俄亥俄州立大学计算机科学排名,俄亥俄州立大学CS专业排名2020年
  6. 学习率对神经网络的影响-乙烷,乙烯,乙炔的分子模型试验数据对比
  7. 【控制】多智能体系统总结。1. 系统模型。2.控制目标。3.模型转换。
  8. 【控制】《多智能体系统一致性协同演化控制理论与技术》纪良浩老师-第7章-二阶时滞多智能体系统分组一致性
  9. 【MORE协议】基于MORE的改进协议设计的MATLAB仿真
  10. Eclipse 从git导入maven多模块项目