Linux kernel路由机制分析(下)
原贴地址:http://velep.com/archives/403.html
四.主要路由流程分析
前面已经介绍过,IP层会在输入和输出两个时候去调用路由部分代码。输入路由过程更为复杂一些也更具代表性,所以我们下面主要分析一下IP包输入时的路由流程。
下图描述了这个流程:
当有数据到达网络设备的时候,会产生一个中断,中断处理函数会调用驱动层的net_rx函数,net_rx进而产生个软中断进入net_rx_action函数,进而如是发现这个数据帧是IP包的话,它就调用IP协议层的ip_rcv函数,它进而又调用ip_rcv_finish函数。在这个函数,它调用路由代码的IP接口函数ip_route_input进行路由。可以看到传递给路由代码的参数有5个:skb IP包缓冲区,iph->daddr IP包的目的地址,iph->saddr IP包源地址,iph->tos 服务类型,dev 输入的网络设备。当这个ip_route_input函数返回时,就意味着路由工作已经结束,如果返回值是0,那么就说明已经成功找到了路由。那么这个路由查询结果放在哪里呢?它就在skb->dst,它指向的就是查到的路由缓存中的一个结点。下边通过调用skb->dst->input(skb)就可以对这个IP进行处理了。这个input是路由缓存结点中的一个函数指针,如果这个路由项表示转发的,那么这个指针实际上指向的是ip_local_deliver,而如果是传送给本地的,那么指向的是ip_forward。ip_local_deliver会将这个IP包进一步传给上层协议层处理,ip_forward则会再将这个IP包从网络设备发送出去。
我们再来看一下路由的具体流程。
首先调用的是ip_route_input,它的任务主要是查路由缓存,如果找到了那么它给skb->dst赋值并返回,如是没找到,它会调用ip_route_input_slow去查询路由策略数据库。
下面是经过简化的代码和注释:
int ip_route_input(struct sk_buff *skb, u32 daddr, u32 saddr, u8 tos, struct net_device *dev)
{
int iif = dev->ifindex;
hash = rt_hash_code(daddr, saddr ^ (iif << 5), tos);
/* 遍历hash table */
for (rth = rt_hash_table[hash].chain; rth; rth = rth->u.rt_next ) {
/* 只有这五个量都匹配才算命中,要比较这么多量是因为在基于策略的路由中,有一个量不同就有可能选择不同的策略。 */
if ( rth->key.dst == daddr && rth->key.src == saddr &&
rth->key.iif == iif && rth->key.oif == 0 &&
rth->key.tos == tos ) {
rth->u.dst.lastuse = jiffies;
dst_hold(&rth->u.dst);
rth->u.dst.__use++;
/* 关键的一步,为dst为赋值 */
skb->dst = (struct dst_entry*)rth;
return 0;
}
}
/* 如果缓存查不到,那么调用这个函数 */
return ip_route_input_slow(skb, daddr, saddr, tos, dev);
}
ip_route_input_slow函数的主要任务是去调用路由策略数据库的查找函数fib_lookup进行查找,然后更新路由缓存。
因为这个函数很长,我们用下面的流程图来表示一些主要的流程:
当调用过fib_lookup后,函数会根据查找的结构进行不同的处理。一般情况是转发或者本地,这两种的情况都会先分配一个新的路由缓存结点,填充适当的值然后插入到缓存中;两者的不同主要在于,设置dst.input函数分别为ip_forward或ip_local_deliver,转发的情况还要绑定关于下一跳信息的neighbour(这个结构主要用来得到网段上邻居的物理地址)。除了转发或本地还有可能是其它情况,比如有错误,没查到,丢弃,NAT等。
fib_lookup函数是路由策略数据库的查询接口,它首先查找策略表,找到一条匹配的策略,然后再执行该策略所对应的动作,动作一般来说就是要查找对应的一张路由表,所以接下来会调用fn_hash_lookup函数进行处理。
下面是这个函数的简化后的代码和相关注释:
fib_lookup(const struct rt_key *key, struct fib_result *res)
{
/* 循环遍历策略表 */
for (r = fib_rules; r; r=r->r_next) {
/* 如果有一项不符,继续查找下一个 */
if ( ((saddr^r->r_src) & r->r_srcmask) ||
((daddr^r->r_dst) & r->r_dstmask) ||
(r->r_tos && r->r_tos != key->tos) ||
(r->r_ifindex && r->r_ifindex != key->iif) )
continue;
/* 判断策略的动作 */
switch (r->r_action) {
case RTN_UNICAST:
case RTN_NAT:
policy = r;
break;
default:
case RTN_BLACKHOLE:
read_unlock(&fib_rules_lock);
return -EINVAL;
}
/* 得到策略所对应的路由表 */
if ((tb = fib_get_table(r->r_table)) == NULL) continue;
/* 查找路由表 */
err = tb->tb_lookup(tb, key, res);
/* 返回0表示查找成功 */
if (err == 0) { res->r = policy; return 0; }
/* 如果有错误,则返回错误号,如果是-EAGAIN或正数则查下一策略 */
if (err < 0 && err != -EAGAIN) return err;
}
return -ENETUNREACH;
}
fn_hash_lookup函数的主要功能即是对路由表的查找。如下:
int fn_hash_lookup(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
{
/* 从大到小遍历区域 */
for (fz = t->fn_zone_list; fz; fz = fz->fz_next) {
fn_key_t k = fz_key(key->dst, fz);
/* 遍历一区域内的hash table */
for (f = fz_chain(k, fz); f; f = f->fn_next) {
if (!fn_key_eq(k, f->fn_key)) {
if (fn_key_leq(k, f->fn_key)) break;
else continue;
}
/* 找到匹配的路由项 */
if (f->fn_state&FN_S_ZOMBIE) continue;
/* 进行语义上的检查和设置
如果是单播,把fib_info赋给res
如果是其它,相应作一些处理 */
err = fib_semantic_match(f->fn_type, FIB_INFO(f), key, res);
/* 没有错误的情况 */
if (err == 0) {
res->type = f->fn_type;
res->prefixlen = fz->fz_order;
goto out;
}
if (err < 0) goto out;
}
}
/* 如果没有找到匹配的路由项,返回正值表示上层函数处理下一个策略 */
err = 1;
out:
return err;
}
五.一些细节问题
1. 关于路由中的错误处理
这里的错误是指找不到路由项,还包括丢弃、禁止、不可到达等情况。这些情况产生的原因可能是因为路由表中找不到相应的项或是用户设置了相应的策略或路由项对特定IP包进行丢弃等处理。
在这种情况下fib_lookup会返回一个错误值,如-ENETUNREACH,-BLACKHOLE等。接着在ip_route_input_slow中
if ((err = fib_lookup(&key, &res)) != 0) {
if (!IN_DEV_FORWARD(in_dev))
goto e_inval;
goto no_route;
}
即会跳到no_route处:
no_route:
rt_cache_stat[smp_processor_id()].in_no_route++;
spec_dst = inet_select_addr(dev, 0, RT_SCOPE_UNIVERSE);
res.type = RTN_UNREACHABLE;
goto local_input;
它把res.type标记成RTN_UNREACHABLE然后跳到本地包情况的处理代码,先是更新路由缓存,然后遇到如下代码:
if (res.type == RTN_UNREACHABLE) {
rth->u.dst.input= ip_error;
rth->u.dst.error= -err;
rth->rt_flags &= ~RTCF_LOCAL;
}
rth->rt_type = res.type;
goto intern;
即判断如果res.type是RTN_UNREACHABLE标记,那么给函数指针dst.input赋为ip_err,将dst.error赋为-err。然后插入到缓存。
最后IP层调用的skb->dst->input实际上就是ip_err(),进行处理错误,如发送ICMP包。
2. 策略性路由NAT功能的实现
linux内核的路由机制是可以实现静态NAT的(即是IP影射是静态不变的)。其中,源地址的SNAT是通过动作为NAT的策略来完成的,目的地址的DNAT是通过类型为NAT的路由项来完成的。
在ip_route_input_slow中,执行完fib_lookup后会有如下代码:
u32 src_map = saddr;
if (res.r)
src_map = fib_rules_policy(saddr, &res, &flags);
if (res.type == RTN_NAT) {
key.dst = fib_rules_map_destination(daddr, &res);
fib_res_put(&res);
free_res = 0;
if (fib_lookup(&key, &res))
goto e_inval;
free_res = 1;
if (res.type != RTN_UNICAST)
goto e_inval;
flags |= RTCF_DNAT;
}
key.src = src_map;
首先,执行fib_rule_policy函数,将判断如果刚才查策略表时查到的是动作为NAT的策略,那么将策略对应的影射源地址赋给src_map,最后会将这个src_map赋给key.src。这就记录了SNAT的地址。
然后,if (res.type == RTN_NAT) 判断查路由表项的类型如果是NAT,那么将路由表项中的影射目的地址赋给key.dst,这就记录了DNAT的地址,然后用这个地址再调用fib_lookup函数查一遍影射后的目的地址的路由。
在下面更新缓存的时候有如下代码:
rth->rt_src_map = key.src;
rth->rt_dst_map = key.dst;
这就把影射后的地址入到了缓存结点中。
进而在执行ip_forward函数进行转发时,有如下代码:
if (rt->rt_flags & RTCF_NAT) {
if (ip_do_nat(skb)) {
kfree_skb(skb);
return NET_RX_BAD;
}
}
即如果是NAT,执行ip_do_nat函数做NAT,实际上就是根据skb-dst->rt_src_map和skb-dst->rt_dst_map做地址替换。
六.总结
通过对kernel路由代码的分析,使我加深了对操作系统特别是网络部分的理解。通过分析源码中的具体数据结构和算法,对“程序=数据结构+算法”这条简单的公式有了更加深刻的理解。
Linux kernel路由机制分析(下)相关推荐
- Linux x86_64 APIC中断路由机制分析
不同CPU体系间的中断控制器工作原理有较大差异,本文是<Linux mips64r2 PCI中断路由机制分析>的姊妹篇,主要分析Broadwell-DE X86_64 APIC中断路由原理 ...
- Linux kernel 同步机制(下篇)
之前的文章 Linux kernel同步机制 在上一部分,我们讨论了最基本常见的几类同步机制,这一部分我们将讨论相对复杂的几种同步机制,尤其是读写信号量和RCU,在操作系统内核中有相当广泛的应用. 读 ...
- Linux内核学习(六):linux kernel的Kconfig分析
Linux内核学习(六):linux kernel的Kconfig分析 前面我们知道了makefile文件,makefile文件会结合配置文件.config来进行操作.这里就再来看看生成内核.conf ...
- linux发挥显卡性能,Linux Kernel 2.6.30下Intel显卡性能有大幅提升!
我是Intel GMA950集成显卡的用户,而且一直也是Compiz的用户的. 早在Beryl时代,GMA950就给3D桌面提供了相当的动力.可惜在最新发布的Ubuntu 9.04中,Compiz的性 ...
- [转载]Linux 线程实现机制分析
自从多线程编程的概念出现在 Linux 中以来,Linux 多线应用的发展总是与两个问题脱不开干系:兼容性.效率.本文从线程模型入手,通过分析目前 Linux 平台上最流行的 LinuxThreads ...
- Linux 线程实现机制分析
本文转自:http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 一.基础知识:线程和进程 按照教科书上的定义,进程是资源管理的最小单位 ...
- Linux 线程实现机制分析--转
http://www.ibm.com/developerworks/cn/linux/kernel/l-thread/ 一.基础知识:线程和进程 按照教科书上的定义,进程是资源管理的最小单位,线程是程 ...
- Linux Kernel Oops异常分析
0.linux内核异常常用分析方法 异常地址是否在0附近,确认是否是空指针解引用问题 异常地址是否在iomem映射区,确认是否是设备访问总线异常问题,如PCI异常导致的地址访问异常 异常地址是否在st ...
- linux kernel idr机制
在我们实际编程中,有时候需要做这么一件事情..就是一个ID对应一个地址..就好像你的身份证对应你的人一样,只要知道号.就能快速的找到与之对应的地址.有人说,用个数组不就行了..但是数组是定长的,不方便 ...
- openVswitch(OVS)源代码之linux RCU锁机制分析
前言 本来想继续顺着数据包的处理流程分析upcall调用的,但是发现在分析upcall调用时必须先了解linux中内核和用户空间通信接口Netlink机制,所以就一直耽搁了对upcall的分析.如果对 ...
最新文章
- VS代码提示不出现或者提示变成英文或者各种奇葩问题的解决
- ubuntu下 apache phpmyadmin 的安装和使用
- 外企软件测试笔试,外企软件测试面试题目50道
- python-数据容器-有序容器与无序容器
- 每半个小时执行一次_活动执行主要做些工作?
- 1011 A+B 和 C (15 分)—PAT (Basic Level) Practice (中文)
- SSO 自动登录 跨站点 解决方案。
- 使用HttpClient下载网络图片
- 通过一个简单例子看懂遗传算法,附MATLAB代码
- Google Colab 挂载 Google Drive
- SEO基础知识完美教程
- gcc -m32报错解决
- html网页的主题标签是什么6,HTML标签及标签属性大全(网页制作必备知识)
- 一步一步学RMAN第五篇 RMAN基础知识补充 一
- Azkaban环境配置-尚硅谷大数据培训
- fstream、ifstream、ofstream创建新文件
- 解决webpack : 无法加载文件 C:\Users\XXX\AppData\Roaming\npm\webpack.ps1因为在此系统上禁止运行脚本
- 鲲鹏微认证的 一些知识点
- Yarn的资源配置参数
- linux下搜狗安装目录,搜狗输入法Linux版配置文件详解
热门文章
- php5.3 appache phpstudy win7win8win10下 运行速度慢
- Java为何大行其道
- [转]android:clipToPadding和android:clipChildren
- _itoa_s替换 itoa
- POJ 2449 Remmarguts' Date (SPFA + A星算法) - from lanshui_Yang
- 将一个JDBC的ResultSet转成XML并输出到文件
- Java设计模式之四 ----- 适配器模式和桥接模式
- Android 端 Rsa加密数据
- 《Python数据挖掘:概念、方法与实践》一1.3 在数据挖掘中使用哪些技术
- nodejs学习—安装