第十二章 trie路由--基于Linux3.10

下载地址《http://download.csdn.net/detail/shichaog/8620701》

路由表的构建途径:

 通过用户命令[route(ioctl) 、ip route(netlink)]静态配置

通过路由协议动态配置,这些协议是BGP(Border Gateway Protocol)、EGP(Exterior Gateway Protocol)以及OSPF(Open Shortest Path First)

这一章的内容基于route方法,其它的配置路由的方法不在这章中,但是上面的方法区别在于配置方法,而对应调用的路由核心函数以及操作的核心路由数据结构是一样的,这章的主要内容就是关于这些和核心函数和核心数据结构的。

路由相关数据结构在include/net/route.h

struct ip_rt_acct {
__u32 o_bytes;  //发送数据的字节数
__u32 o_packets;
__u32 i_bytes;
__u32 i_packets;
};

这个结构体在ip_rcv_finish中被使用到,由于网络数据包的统计,分别按照byte和packet两种方法计数,ip_rcv_finish在网络层接收中分析过,这里会再一次看到在网络层被跳过的关于路由相关的代码,下面的代码片段就是上面统计信息被赋值的一个地方:

static int ip_rcv_finish(struct sk_buff *skb) 
{
#ifdef CONFIG_IP_ROUTE_CLASSID
if (unlikely(skb_dst(skb)->tclassid)) {
struct ip_rt_acct *st = this_cpu_ptr(ip_rt_acct);
u32 idx = skb_dst(skb)->tclassid;
st[idx&0xFF].o_packets++;
st[idx&0xFF].o_bytes += skb->len;
st[(idx>>16)&0xFF].i_packets++;
st[(idx>>16)&0xFF].i_bytes += skb->len;
}
#endif
}

由上面的使用可以知道,定义了基于路由的分类器就会使用该字段。该字段根据idx索引可构成具有256个成员的数组。其初始化在ip_rt_init中完成。

rt_cache_stat

路由表缓存的统计信息,除了输入输出路由信息统计,还有垃圾回收信息。

fib_result

查找路由表会得到此结构。

struct fib_result {
unsigned char  prefixlen;
unsigned char  nh_sel;
unsigned char  type;
unsigned char  scope;
u32 tclassid;
struct fib_info *fi;
struct fib_table *table;
struct list_head *fa_head;
};

struct fib_rule

策略路由使用的结构。

struct fib_rule {
struct list_headlist;
atomic_t  refcnt;
int iifindex;
int oifindex;
u32 mark;
u32 mark_mask;
u32 pref;
u32 flags;
u32 table;
u8 action;
u32 target;
struct fib_rule __rcu*ctarget;
char iifname[IFNAMSIZ];
char oifname[IFNAMSIZ];
struct rcu_headrcu;
struct net *  fr_net;
};

struct flowi

流量控制,作为路由表查找的键值。

struct flowi {
union {
struct flowi_common__fl_common;
struct flowi4  ip4;
struct flowi6  ip6;
struct flowidndn;
} u;
#define flowi_oif u.__fl_common.flowic_oif
#define flowi_iif u.__fl_common.flowic_iif
#define flowi_mark u.__fl_common.flowic_mark
#define flowi_tos u.__fl_common.flowic_tos
#define flowi_scope u.__fl_common.flowic_scope
#define flowi_proto u.__fl_common.flowic_proto
#define flowi_flags u.__fl_common.flowic_flags
#define flowi_secid u.__fl_common.flowic_secid
} __attribute__((__aligned__(BITS_PER_LONG/8)));

fib_table

路由表在内核中的表示为fib_table的一个结构体,其定义位于include/net/ip_fib.h文件。

struct fib_table {
struct hlist_nodetb_hlist;
u32 tb_id;
int tb_default;
int tb_num_default;
unsigned long  tb_data[0];
};

tb_id用于标识路由表所属,其可选字段在include/uapi/linux/rtnetlink.h文件中;

enum rt_class_t {
	RT_TABLE_UNSPEC=0, 
/* User defined values */
	RT_TABLE_COMPAT=252, 
	RT_TABLE_DEFAULT=253, 
	RT_TABLE_MAIN=254, 
	RT_TABLE_LOCAL=255, 
	RT_TABLE_MAX=0xFFFFFFFF
};

从枚举类型名称,如果没有使用策略路由,那么只有RT_TABLE_MAIN和RT_TABLE_LOCAL两种类型的路由表存在。

struct fib_info

多个路由项共享该一些字段:

struct fib_info {
struct hlist_nodefib_hash;
struct hlist_nodefib_lhash;
struct net  *fib_net;
int fib_treeref;
atomic_t  fib_clntref;
unsigned int  fib_flags;
unsigned char  fib_dead;
unsigned char  fib_protocol;
unsigned char  fib_scope;
unsigned char  fib_type;
__be32  fib_prefsrc;
u32 fib_priority;
u32 *fib_metrics;
#define fib_mtu fib_metrics[RTAX_MTU-1]
#define fib_window fib_metrics[RTAX_WINDOW-1]
#define fib_rtt fib_metrics[RTAX_RTT-1]
#define fib_advmss fib_metrics[RTAX_ADVMSS-1]
int fib_nhs;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int fib_power;
#endif
struct rcu_headrcu;
struct fib_nh  fib_nh[0];
#define fib_dev fib_nh[0].nh_dev
};

路由项别名:

struct fib_alias {
struct list_headfa_list;
struct fib_info*fa_info;
u8 fa_tos;
u8 fa_type;
u8 fa_state;
struct rcu_headrcu;
};

下一跳,使用route或者ip route 可以添加。

struct fib_nh {
struct net_device*nh_dev;
struct hlist_nodenh_hash;
struct fib_info*nh_parent;
unsigned int  nh_flags;
unsigned char  nh_scope;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int nh_weight;
int nh_power;
#endif
#ifdef CONFIG_IP_ROUTE_CLASSID
__u32 nh_tclassid;
#endif
int nh_oif;
__be32  nh_gw;
__be32  nh_saddr;
int nh_saddr_genid;
struct rtable __rcu * __percpu *nh_pcpu_rth_output;
struct rtable __rcu*nh_rth_input;
struct fnhe_hash_bucket*nh_exceptions;
};

struct dst_entry路由表入口项

struct dst_ops 路由入口项的操作函数集,如垃圾回收函数就在这里。

struct rtable  路由表

路由三大块:路由缓存、路由表、路由信息查找,这三大块依赖的重要数据数据结构在路由子系统初始化时完成。

路由子系统初始化:

2655 int __init ip_rt_init(void)
2656 {
/*ip_rt_acct 字段的意义在前面*/
2659 #ifdef CONFIG_IP_ROUTE_CLASSID
2660     ip_rt_acct = __alloc_percpu(256 * sizeof(struct ip_rt_acct), __alignof__(struct ip_rt_acct));
2661     if (!ip_rt_acct)
2662         panic("IP: failed to allocate ip_rt_acct\n");
2663 #endif
//创建rtalble大小的路由表缓存,这是上述路由三大块中的路由缓存使用的,没有使用malloc的原因是加速网络数据包的传递
2665     ipv4_dst_ops.kmem_cachep =
2666         kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0,
2667                   SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
2668 
2669     ipv4_dst_blackhole_ops.kmem_cachep = ipv4_dst_ops.kmem_cachep;
/*路由项垃圾回收门限,对无效的路由项回收其占用内存的门限,初始设置门限为最大值*/
2677     ipv4_dst_ops.gc_thresh = ~0; 
/*路由表膨胀的最大尺寸,#define INT_MAX ((int)(~0U>>1))*/
2678     ip_rt_max_size = INT_MAX;
/*****************************
注册两种类型的内核通知链,netdev_chain和inetaddr_chain,这两种通知链对应的回调函数是inetdev_event和fib_inetaddr_event,设备的状态的改变(up、down、register和unregistere)会调用回调函数:
devinet_init---向--netdev_chain---添加--inetdev_event回调函数;  回调函数完成设备的初始化、删除等操作 
ip_fib_init---向--netdev_chain---添加--fib_netdev_event回调函数;回调函数完成该设备相关路由表的使能、禁止、刷新等操作。
                -向--inetaddr_chain---添加--fib_inetaddr_event回调函数;回调函数完成该设备相关路由表路由项的添加和删除操作。
***************************/
2680     devinet_init();
2681     ip_fib_init();
2696     return rc;
2697 }

12.2 LC-trie(字典树、单词查找树)

现在内核采用的是trie算法组织路由表,这里的trie其实就是tree的意思。本章会以ifconfig和route两个命令作为引子,以这两个例子详细看一下trie路由算法在Linux下的实现,本节是现在内核默认trie路由算法的一个简单的算法简介,并将trie路由算法的节点、叶子和Linux内核下具体的数据结构对应起来。至于早期的哈希算法,这里就丝毫没有涉及了。

先从一个引子说起,如果让你使用百度词典查找apple这个单词,会发现在你输入一个字母后,其下拉栏会显示若干的备选单词。如图12.2.1显示的,会有appliance、apple等提示单词,问题来了,百度是按照什么规则给出的提示的呢?

1、首先这些单词必须遵循字母顺序,不能用户输入a,下来栏中出现个b打头的单词。

2、首先这些词在字典里必须是存在的,或者是一些组织机构的,总而言之就是这个词目前是存在的。

3、这些词并没有按照26个字母表的顺序给出,图中很明显,ace比and要排在前面,这里加入了词频(概率)权重因子。


图12.2.1 apple字典提示

路由的算法就有点类似上面下拉栏的实现算法。但是上述频率的概念没有在路由算法本身体现,所以这里也就略过。下面还是以单词为例来看看LC-trie是如何组织的。图12.2.1中蓝色一栏就是我们要找的单词。

图12.1.2是对要查找的单词构建的一颗字典树,虚线左右两边都对应这个树,它们的不同在于深度。我们以左边的示例来说明字典树是如何组织的,对于apple这个单词,

1、树的根为空,NULL

2、最后一个字符是e,e被称为叶子

3、中间的字符,如a、p、l等,被称为节点。

4、尽量利用前缀节点,比如approve和apple有相同的前缀app,黄色那个支路就是用来表示approve的。


图12.2.2 apple字典树拓扑

图12.2.2中左右两幅图是用来说明rebalance这个概念的,这棵树不能太瘦也不能太胖,也即其广度和深度要在一个合理的比率上。所以当我们觉得这棵树太瘦时,可以进行压缩,比如将app压缩成一个节点,这就意味着先前复用a、ap的节点会被创建,因为这时没有a、ap节点了。如果太胖,那就拉长,就是上述过程的逆过程。

上述的过程虽然和内核管理路由表的方法有点差别,但思想是一样的,图12.1.3是具有10.12.39.0和192.168.0.10两项的路由表。这个10.12.39.0并不是使用route add 192.168.0.10 eth0 命令分配的,而是使用ifconfig eth0 10.12.39.221 netmask255.255.255.0 配置本机IP地址时设置的。至于为什么配置本机IP地址的ifconfig会设置这么一个路由项,主要原因是规范中要求:主机号全零的用于标识一个网段,这个地址会被用作路由项,不能被分配给主机使用,Linux默认在设置主机IP时,都会配置一个主机号全零的IP作为路由项。这个内容在12.2节中会看到代码的实现。

图12.2.4是在图12.2.3的基础之上使用route命令添加一项组成的路由表拓扑图,需要说明的是这两幅图是路由项组织的核心结构,这里略去了其和路由缓存、arp缓存、网络命名空间等之间的交互。图中黄色部分是数据结构,淡蓝色部分是对应数据结构的成员,成员的等号右边是其具有的值。

Struct fib_table:代表一个路由表,

tb_hlist,对于ipv4是&net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]指向的哈希表,用于索引该路由项,对于本机IP地址由&net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX]哈希表来指向。

tb_id用于标识表的ID号,对于路由出去的表id是254,本地IP表则id是255。

tb_data[0]零长数组,这个数组用于指向struct trie结构体,这个结构体就是对

struct rt_trie_node结构体的封装,该结构体只有parent和key两个成员,红色框已经表明它们的所属关系。

struct tnode结构体的前两项也是parent 和key,这就意味着可以使用强制类型转换方法和struct rt_tire_node互换使用。内核经常使用这个技巧。tnode自身是trie node的意思,就是字典树的节点的意义,对应图12.1.1的蓝色节点,tnode的最后一个成员是*child[0],这是又是一个零长数组,图中画出了0和1两个成员,实际上可能还有2…,full_children与empty_children之和等于零长数组成员的个数。

Pos:比较起始位置

Bits:比较的位数。

这两成员算是trie路由算法的核心成员,pos指定了一个32位ipv4地址比较的起始位置,bits指定了比较的比特数,因为IPV4的地址是32位的,pos和bits是0~31之间的数值。

由于10.12.39.0路由项先于192.168.0.10路由项存在这张路由表中,当插入192.168.0.10时,它会和根节点开始查找,192和10的二进制比较结果就是,它们的第一个bit不同,192的第一个bit是1,而10的四位二进制的第一个bit是0,23bit的IPv4地址使用0~31标记,这就是tnode中pos等于0,bits等于1的由来。

leaf对应图12.2.3中的黄色节点,用于标记叶子节点。


图12.2.3 具有10.12.39.0和192.168.0.10两项的路由表

在插入192.168.0.100时,很明显,首先遍历tnode,找到pos是0,bits1是1的一个节点(实际上只有这么一个节点),tnode的children,发现有相同的前缀192.168.0项,即192.168.0.100和192.168.0.10从第25个bit不同,并且不同的那个bit的值是1,这里会创建一个tnode,并将children赋值成适当的叶子。这一过程参看图12.1.4,和图12.1.3对比可以使这一过程更加明晰。当这棵树添加完成了以后,最后会去判断这棵树是否需要rebalance。


图12.2.4 10.12.39.0/192.168.0.10/192.168.0.100三项路由表项

对路由项的核心算法以及路由表项的组织有了一个了解以后,下面就正式进入源码及的剖析,由于代码的分支情况及其繁多,所以会以一个主线分析代码,即使主线不包括的代码也会注释它们的功能。

最后为了加深对pos和bits的奥妙了理解,这里给出3个IP地址分别是10.12.39.0,192.168.0.10和192.168.0.100这三项,这三项和图12.1.4是对应的。插入的顺序是10.12.39.0、192.168.0.10和192.169.0.100,插入192.168.0.10时,其发现和10.12.39.0的第0个比特不同,所以这时会将先前的10.12.39.0作为一个tnode,tnode的pos设置为0,bits设置成1,黄色的那里只有一列,在插入192.168.0.100时,其和192.168.0.10有相同的前缀,所以192.168.0.10赋值到一个tnode,其pos设置为25,bits设置为1。查找时先遍历tnode,在遍历leaf,这个过程会在后面结合具体代码来看。图中虚线右边是rebalance的一个示例。


图12.2.5 10.12.39.0/192.168.0.10/192.168.0.100三项路由表的pos和bits

 12.3 ifconfig下trie路由跟新

12.3.1 路由下路由信息

在/proc/net/目录下fib_trie和fib_triestat这两个文件,这两文件包含了一些trie路由的信息。fib_trie用于显示路由表的树状图,fib_ritestat是trie树的一些统计信息。

没有分配IP地址时的路由信息:

# cat /proc/net/fib_trie 
# cat /proc/net/fib_triestat 
Basic info: size of leaf: 20 bytes, size of tnode: 28 bytes. 
Local: 
	Aver depth:     0.00
	Max depth:      0
	Leaves:         0
	Prefixes:       0
	Internal nodes: 0
	
	Pointers: 0
Null ptrs: 0
Total size: 0  kB
Main: 
	Aver depth:     0.00
	Max depth:      0
	Leaves:         0
	Prefixes:       0
	Internal nodes: 0
	
	Pointers: 0
Null ptrs: 0
Total size: 0  kB

没有分配IP地址时的路由信息都是空的,这很合逻辑, 在使用ifconfig eth0 10.12.39.221 netmask255.255.255.0配置本机IP时,路由信息的内容非常的多。在fib_trie中可以看到有Local和Main这两个字段,这两个字段分别对应于两张表,Local用于表示自己的IP地址,而Main用于路由出去的IP地址。

在配置10.12.39.221时,LOCAL表中包含了主机号全零和主机号全一的IP地址,这都是协议上规定的特殊地址。

第3行10.12.39.0/24,这里的24对应于12.2节中trie字段的pos,这里10.12.39.0和10.12.192的第一个不同的比特就是第24个(从0位置计)比特。同样第6行中的26也是根据10.12.39.221和10.12.39.255之间的差别。有“+”的行,表明其是一个tnode(节点),“|”则表明了其是一个leaf(叶子)。每个叶子下面“/”的标记了该叶子的属性,host、LOCAL以及BROADCAST是表的类型,每对应这么一项,意味着插入了一次,比如这里的叶子10.12.39.0既属于link类型又属于BROADCAST类型,这两种类型的表项会对应于两次插入路由表的动作。

fib_triestat统计了树的一些统计信息,在12.2节中所述的rebalance操作就是依赖这些统计信息对树进行均衡的。

1# cat /proc/net/fib_trie 
2Local: 
3  +-- 10.12.39.0/24 1 0 0
4    |-- 10.12.39.0
5        /32 link BROADCAST
6     +-- 10.12.39.192/26 1 0 0
7        |-- 10.12.39.221
8           /32 host LOCAL
9        |-- 10.12.39.255
10           /32 link BROADCAST
11Main: 
12  |-- 10.12.39.0
13     /24 link UNICAST

15# cat /proc/net/fib_triestat 
16Basic info: size of leaf: 20 bytes, size of tnode: 28 bytes. 
17Local: 
18	Aver depth:     1.66
19	Max depth:      2
20	Leaves:         3
21	Prefixes:       3
22	Internal nodes: 2
23	  1: 2
24	Pointers: 4
25Null ptrs: 0
26Total size: 1  kB
27Main: 
28	Aver depth:     0.00
29	Max depth:      0
30	Leaves:         1
31	Prefixes:       1
32	Internal nodes: 0
33	
34	Pointers: 0
35Null ptrs: 0
36Total size: 1  kB

12.3.2  路由通知链函数的注册

前面已经见到过ip_fib_init函数,这个函数在net/ipv4/fib_frontend.c文件中,并且在初始化时会被调用。

1075 static struct notifier_block fib_inetaddr_notifier = {
1076     .notifier_call = fib_inetaddr_event, 
1077 };
1168 void __init ip_fib_init(void) 
1169 {
1170     rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL, NULL); 
1171     rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL, NULL); 
1172     rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib, NULL); 
1173 
1174     register_pernet_subsys(&fib_net_ops); 
1175     register_netdevice_notifier(&fib_netdev_notifier); 
1176     register_inetaddr_notifier(&fib_inetaddr_notifier); 
1177 
1178     fib_trie_init();
1179 }

其1176行注册的结构体在1075行,这节就以注册的fib_inetaddr_event函数开始,当使用ifconfig eth0 10.12.39.221 netmask255.255.255.0 设置设备IP地址时,这个函数就会被触发调用。1175行的fib_netdev_notifier函数也会在该设备获得地址的时候出发调用,进而会配置12.3.1节中那些未指定IP地址。

12.3.3  ifconfig调用流程

ifconfig配置IP地址的函数调用,第一个内核里被调用到的函数是inet_ioctl(),经过devinet_ioctl函数会调用fib_inetaddr_event():

net/ipv4/af_inet.c inet_ioctl()

net/ipv4/devinet.c devinet_ioctl()

fib_inetaddr_event()这个函数是在上一节注册的通知链函数,从这个函数的命名(fib forward information base)就可以看出其和路由是有直接关系关系的,这节就从这个函数开始。

好了从现在开始就要正式接触Linux内核网络路由代码细节了。由12.3.1节可知,一个ifconfig命令会在Local表中配置三项路由项,在Main表中配置一项路由项,本节所述内容是ifconfig在Local表创建路由项所经历的代码片段,当然在操作Main表时用的都是同一套代码。

图12.3.1给出是的ifconfig在创建一个本地IPv4地址时所调用的函数,在后面继续创建路由项时同样调用这些函数,只是执行的逻辑分支会不一样,为了让脉络更加清晰,图12.3.1是添加10.12.39.221这一项路由项的函数调用流程。

首先在fib_inetaddr_event检测导师是NETDEV_UP事件发生,其会调用fib_add_ifaddr函数处理设备UP事件,fib_add_ifaddr的源码如下:

net/ipv4/fib_frontend.c

 734 void fib_add_ifaddr(struct in_ifaddr *ifa)
 735 {
 736     struct in_device *in_dev = ifa->ifa_dev;
 737     struct net_device *dev = in_dev->dev;
 738     struct in_ifaddr *prim = ifa;
 739     __be32 mask = ifa->ifa_mask;
 740     __be32 addr = ifa->ifa_local;
 741     __be32 prefix = ifa->ifa_address & mask;
 742 
 743     if (ifa->ifa_flags & IFA_F_SECONDARY) {
 744         prim = inet_ifa_byprefix(in_dev, prefix, mask);
 745         if (prim == NULL) {
 746             pr_warn("%s: bug: prim == NULL\n", __func__);
 747             return;
 748         }
 749     }
 750 
 751     fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim);
 752 
 753     if (!(dev->flags & IFF_UP))
 754         return;
 755 
 756     /* Add broadcast address, if it is explicitly assigned. */
 757     if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF))
 758         fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32, prim);
 759 
 760     if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags & IFA_F_SECONDARY) &&
 761         (prefix != addr || ifa->ifa_prefixlen < 32)) {
 762         fib_magic(RTM_NEWROUTE,
 763               dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,
 764               prefix, ifa->ifa_prefixlen, prim);
 765 
 766         /* Add network specific broadcasts, when it takes a sense */
 767         if (ifa->ifa_prefixlen < 31) {
 768             fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32, prim);
 769             fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix | ~mask,
 770                   32, prim);
 771         }
 772     }
 773 }

743~749 如果flag参数指定了配置IP对象为从属设备或者临时设备,但是根据索引找不到该设备,返回错误。

751行,在ID等于255的表中,插入IP地址类型为2的IP地址dd270c0a。ID等于255的表示是LOCAL表,即该表中的所有IP项的目的地址均是本机。类型见IP地址类型。

760~770处理广播包的路由项。主机号全为1和主机号全为0,实际的过程比较复杂,这里就不展开了,这里以751行添加的过程来说明路由表项是如何添加的。

IP地址的类型如下

include/uapi/linux/ rtnetlink.h

191 enum {
192     RTN_UNSPEC,//未定义
193     RTN_UNICAST, //单播,网关或者直接路由       
194     RTN_LOCAL, //host地址,目的地址是本机
195     RTN_BROADCAST,    广播地址,广播收广播发
197     RTN_ANYCAST,   //广播接收数据,单播发送数据,IPV6协议规定的地址类型
199     RTN_MULTICAST,   多播
200     RTN_BLACKHOLE,   丢弃
201     RTN_UNREACHABLE,  目的不可达
202     RTN_PROHIBIT,   管理员禁止IP地址
203     RTN_THROW,     /* Not in this table        */
204     RTN_NAT,      需要进行网络地址转换
205     RTN_XRESOLVE,    外部解析
206     __RTN_MAX
207 };

图12.3.1 ifconfig 配置IP地址调用流程

路由项的管理集中在fib_frontend.c、fib_semantics.c和fib_trie.c这三个文件,后面的代码也集中在这三个文件中。接着fib_magic函数的调用。这个函数的三个参数:

Cmd:ioctl传递的命令参数,这里是RTM_NEWROUTE,对应添加路由项,此外前面提到的netfilter也是在这个magic函数里处理的。

Type:这个参数是路由项的类型,前面已经介绍过了,这里传递的参数是RTN_LOCAL,配置的IP地址的目的端就是本机。

Dst:是一个32bit的IP地址,是要添加到路由表项,ifa是一些附属配置信息。

net/ipv4/fib_frontend.c

 696 static void fib_magic(int cmd, int type, __be32 dst, int dst_len, struct in_ifaddr *ifa)
 697 {
 698     struct net *net = dev_net(ifa->ifa_dev->dev);
 699     struct fib_table *tb;
 700     struct fib_config cfg = {
 701         .fc_protocol = RTPROT_KERNEL,
 702         .fc_type = type,
 703         .fc_dst = dst,
 704         .fc_dst_len = dst_len,
 705         .fc_prefsrc = ifa->ifa_local,
 706         .fc_oif = ifa->ifa_dev->dev->ifindex,
 707         .fc_nlflags = NLM_F_CREATE | NLM_F_APPEND,
 708         .fc_nlinfo = {
 709             .nl_net = net,
 710         },
 711     };
 712 
 713     if (type == RTN_UNICAST)
 714         tb = fib_new_table(net, RT_TABLE_MAIN);
 715     else
 716         tb = fib_new_table(net, RT_TABLE_LOCAL);
 717 
 718     if (tb == NULL)
 719         return;
 720 
 721     cfg.fc_table = tb->tb_id;
 722 
 723     if (type != RTN_LOCAL)
 724         cfg.fc_scope = RT_SCOPE_LINK;
 725     else
 726         cfg.fc_scope = RT_SCOPE_HOST;
 727 
 728     if (cmd == RTM_NEWROUTE)
 729         fib_table_insert(tb, &cfg);
 730     else
 731         fib_table_delete(tb, &cfg);
 732 }

700~710行,将用户传递的配置参数使用内核下的数据结构保护起来。

713~716通过类型,索引要添加到的表。

include/net/ip_fib.h

204 staticinline struct fib_table *fib_get_table(struct net *net, u32 id)
205 {
206     struct hlist_head *ptr; 
207
208     ptr = id == RT_TABLE_LOCAL ? 
209        &net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX] : 
210        &net->ipv4.fib_table_hash[TABLE_MAIN_INDEX]; 
211     return hlist_entry(ptr->first, structfib_table, tb_hlist); 
212 }
213
214 staticinline struct fib_table *fib_new_table(struct net *net, u32 id) 
215 {
216     return fib_get_table(net, id); 
217 }

这个函数的第一个参数是一个net,代表了一个网络,其附属于一个网络命名空间,第二个参数是716行的RT_TABLE_LOCAL。索引返回的类型是fib_table,在路由表核心数据结构时,介绍过fib_table代表的是路由表的大类,这些路由表可以多打256张,这么多张的表使用哈希法索引。

fib_get_table的209行就是返回ipv4的路由项,&net->ipv4.fib_table_hash[TABLE_LOCAL_INDEX]。

718~719判断是否得到了该表项,依据就是这个表的地址非NULL,实际上在初始化时这个表已经得到了存储表项的空间。

721~726将参数保存在cfg结构体内。

728~729将cfg中保存的配置路由项的信息,插入到由fib_get_table获得的表中。

路由项的插入工作就是在fib_table_insert这个函数中完成的,这个函数大体分为四个部分:

1、路由信息获取,每一个路由项都会对应一个路由信息,并且一个路由信息可以对应多个路由项。获取含义是先查找,如果找不到路由信息则根据传递的参数创建路由信息。

2、Trie树节点tnode。存在多个路由项,并且他们有共同的前缀时,共同的前缀会被设定为tnode。

3、Trie树叶子leaf,如果一个路由项不在路由表中,但是其和其它已经在路由表中的项具有相同的前缀,则会创建对应的叶子,在rebalance操作时,tnode和leaf因深度和广度原因都可能被调整。

4、插入操作,就是将新创建的叶子插入到trie树上去,也就对应创建了一个路由项。

net/ipv4/fib_frontend.c
1172 int fib_table_insert(struct fib_table*tb, struct fib_config *cfg) 
1173 {
1174    struct trie *t = (struct trie *) tb->tb_data; 
1175    struct fib_alias *fa, *new_fa; 
1176    struct list_head *fa_head = NULL;
1177    struct fib_info *fi;
1178    int plen = cfg->fc_dst_len;
1179    u8 tos = cfg->fc_tos;
1180    u32 key, mask;
1181    int err;
1182    struct leaf *l;
//图12.1.2中plen值等于32。下面的判断条件满足。
1184    if (plen > 32) 
1185        return -EINVAL;
//图12.3.1中,fc_dst是0a0c27dd,即10.12.39.221,用户空间配置的路由地址
1187    key = ntohl(cfg->fc_dst);
//mask 等于~0
1191    mask = ntohl(inet_make_mask(plen));
//验证key(目的地址)和目的地址掩码的正确性。
1193    if (key & ~mask)
1194        return -EINVAL;
//经过掩码匹配,得到真正的键值key等于10.12.39.221
1196    key = key & mask;
//对应于四个部分中的第一个部分,路由信息获取,见后文
1198    fi = fib_create_info(cfg);
1199    if (IS_ERR(fi)) {
1200        err = PTR_ERR(fi);
1201        goto err;
1202    }
//对应于四个部分的第二、三两个部分,遍历tnode获取,获取叶子leaf,见后文。
1204    l = fib_find_node(t, key);
1205    fa = NULL;
//在使用ifconfig向LOCAL表插入10.12.39.221前,路由表时空的,所以这里不可能找到对应的fib_table 和leaf信息。所以这里fi返回值是新创建的路由信息记录项,I==NULL,fa==NULL,直接跳至1293行,对于如果有相同前缀的路由IP项,fib_find_node返回的是一个tnode。
1207    if (l) {
//get_fa_head根据leaf信息,获得fib_alias链表的头指针。
1208        fa_head = get_fa_head(l, plen);
//遍历上述链表,找到合适的服务类型和优先级,合适的意义是优先级要高于这里传递的参数
1209        fa = fib_find_alias(fa_head, tos, fi->fib_priority);
1210    }
//fa在首次配置IP地址时为空,第一遍先跳过下面对这个if语句的注释吧,第二遍再来看。 
1223    if (fa && fa->fa_tos == tos &&
1224        fa->fa_info->fib_priority == fi->fib_priority) {
1225        struct fib_alias *fa_first, *fa_match;
1226
1227        err = -EEXIST;
1228        if (cfg->fc_nlflags & NLM_F_EXCL)
1229             goto out;
1230
1231        /* We have 2 goals:
1232         * 1. Find exact match for type, scope, fib_info to avoid
1233         * duplicate routes
1234         * 2. Find next 'fa' (or head), NLM_F_APPEND inserts before it
1235         */
1236        fa_match = NULL;
1237        fa_first = fa;
1238        fa = list_entry(fa->fa_list.prev, struct fib_alias, fa_list);
1239        list_for_each_entry_continue(fa, fa_head, fa_list) {
1240             if (fa->fa_tos != tos)
1241                 break;
1242             if(fa->fa_info->fib_priority != fi->fib_priority)
1243                 break;
1244             if (fa->fa_type ==cfg->fc_type &&
1245                 fa->fa_info == fi) {
1246                 fa_match = fa;
1247                 break;
1248             }
1249        }
1250
1251        if (cfg->fc_nlflags & NLM_F_REPLACE) {
1252             struct fib_info *fi_drop;
1253             u8 state;
1254
1255             fa = fa_first;
1256             if (fa_match) {
1257                 if (fa == fa_match)
1258                     err = 0;
1259                goto out;
1260             }
1261             err = -ENOBUFS;
//上面的代码已然没有找到fib alias,所以这里创建新的fib alias。并在1266~1273行对其成员进行初始化。
1262             new_fa =kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
1263             if (new_fa == NULL)
1264                 goto out;
1265
1266             fi_drop = fa->fa_info;
1267             new_fa->fa_tos = fa->fa_tos;
1268             new_fa->fa_info = fi;
1269             new_fa->fa_type =cfg->fc_type;
1270             state = fa->fa_state;
1271             new_fa->fa_state = state &~FA_S_ACCESSED;
1272
1273             list_replace_rcu(&fa->fa_list,&new_fa->fa_list);
1274             alias_free_mem_rcu(fa);
1275
1276             fib_release_info(fi_drop);
1277             if (state & FA_S_ACCESSED)
1278                rt_cache_flush(cfg->fc_nlinfo.nl_net);
1279            rtmsg_fib(RTM_NEWROUTE,htonl(key), new_fa, plen,
1280                 tb->tb_id,&cfg->fc_nlinfo, NLM_F_REPLACE);
1281
1282             goto succeeded;
1283        }
1284        /* Error if we find a perfect match which
1285         * uses the same scope, type, and nexthop
1286         * information.
1287         */
1288        if (fa_match)
1289             goto out;
1290
1291        if (!(cfg->fc_nlflags & NLM_F_APPEND))
1292             fa = fa_first;
1293    }
1294    err = -ENOENT;
//由图12.3.1可以知道fc_nlflags的值等于1024,就是0x400,正好等于这里的(NLM_F_CREATE),所以还要接着1297行看。
1295    if (!(cfg->fc_nlflags & NLM_F_CREATE))
 1296         goto out;
1297
1298    err = -ENOBUFS;
//从fn_alias_kmem上分配一块cache,这个cache用于flash别名系统
1299    new_fa = kmem_cache_alloc(fn_alias_kmem, GFP_KERNEL);
1300    if (new_fa == NULL)
1301        goto out;
//向fib_alias中插入相关的路由信息,并将其成员fa_info指向前面的fib_ info结构体。
1303    new_fa->fa_info = fi;
1304    new_fa->fa_tos = tos;
1305    new_fa->fa_type = cfg->fc_type;
1306    new_fa->fa_state = 0;
1307    /*
1308     * Insert new entry to the list.
1309     */
//fa_head在1208行没有得到复制,这里fa_head == NULL, fib_insert_node对应于第四个部分,插入操作,见后文
1311    if (!fa_head) {
1312        fa_head = fib_insert_node(t, key, plen);
1313        if (unlikely(!fa_head)) {
1314             err = -ENOMEM;
1315             goto out_free_new_fa;
1316        }
1317    }
1318
//跟新tb_num_default字段
1319    if (!plen)
1320        tb->tb_num_default++;
//处理fa_list链表
1322    list_add_tail_rcu(&new_fa->fa_list,
1323               (fa ? &fa->fa_list :fa_head));
1324
1325    rt_cache_flush(cfg->fc_nlinfo.nl_net);
发送netlink消息
1326    rtmsg_fib(RTM_NEWROUTE, htonl(key), new_fa, plen, tb->tb_id,
1327           &cfg->fc_nlinfo, 0);
1328 succeeded:
1329    return 0;
//差错处理
1331 out_free_new_fa:
1332    kmem_cache_free(fn_alias_kmem, new_fa);
1333 out:
1334    fib_release_info(fi);
1335 err:
1336    return err;
1337 }

1198行fib_create_info获得一个路由信息记录结构体,当要获得的对象不存在时,会创建一个新的路由信息结构体fib_info。

net/ipv4/fib_semantics.c

773 struct fib_info*fib_create_info(structfib_config *cfg)
 774 {
 775    interr;
 776    structfib_info *fi = NULL;
 777    structfib_info *ofi;
 778    int nhs= 1;
 779    structnet *net = cfg->fc_nlinfo.nl_net;
//用户空间传递进来的tc_type值是2,所以这里的检查类型的有效性通过。
 781    if(cfg->fc_type > RTN_MAX)
 782        gotoerr_inval;
//本地地址LOCAL的值是RT_SCOPE_HOST,254,用户传递进来的fc_scope等于254(图13.3.1),这里检查也通过。
 784    /* Fastcheck to catch the most weird cases */
 785    if(fib_props[cfg->fc_type].scope > cfg->fc_scope)
 786        gotoerr_inval;
796    err =-ENOBUFS;
// 对于由于前表项为空,所以统计路由信息技术变量fib_info_cnt的值等于0,索引路由信息的哈希数组大小变量//fib_info_hash_size值等于0.
 797    if(fib_info_cnt >= fib_info_hash_size) {
 798        unsignedint new_size = fib_info_hash_size << 1;
 799        structhlist_head *new_info_hash;
 800        structhlist_head *new_laddrhash;
 801        unsignedint bytes;
//由于fib_info_hash_size确实为0,所以这里的new_size将被赋值成16。
 803        if(!new_size)
 804            new_size= 16;
 805        bytes= new_size * sizeof(struct hlist_head *);
//为哈希信息和哈希地址存储分配内存,如果小于一个页使用kzalloc,大于一个页使用__get_free_pages。
 806        new_info_hash= fib_info_hash_alloc(bytes);
 807        new_laddrhash= fib_info_hash_alloc(bytes);
//分配失败的处理,如果没有失败,调用fib_info_hash_move进行处理。
 808        if (!new_info_hash || !new_laddrhash){
 809            fib_info_hash_free(new_info_hash,bytes);
 810            fib_info_hash_free(new_laddrhash,bytes);
 811        }else
//根据先前申请到的内存,设置fib_info_hash和fib_info_laddrhash这两个fib_semantics.c文件内的全局变量。这两变量用于链接所有的fib_info结构体。
 812            fib_info_hash_move(new_info_hash,new_laddrhash, new_size);
 813
 814        if(!fib_info_hash_size)
 815            gotofailure;
 816    }
//申请路由信息空间,注意零长数组指向了下一跳fib_nh(nh—next hop)。
818    fi =kzalloc(sizeof(*fi)+nhs*sizeof(struct fib_nh), GFP_KERNEL);
 819    if (fi== NULL)
 820        gotofailure;
/*用户空间没有配置metric,metirc用于配置下一跳的个数,不指定的情况下赋值如下。
*const u32 dst_default_metrics[RTAX_MAX+ 1]= {
*           [RTAX_MAX]= 0xdeadbeef,
*};
*/
 821    if(cfg->fc_mx) {
 822        fi->fib_metrics= kzalloc(sizeof(u32) * RTAX_MAX, GFP_KERNEL);
 823        if(!fi->fib_metrics)
 824            gotofailure;
 825    } else
 826        fi->fib_metrics= (u32 *) dst_default_metrics;
//fib_info_cnt用于计数有意义的fib_info 个数。
 827    fib_info_cnt++;
//下面的赋值,见图12.3.2,fib_info结构体赋值,值来源于用户配置。
 829    fi->fib_net= hold_net(net);
 830    fi->fib_protocol= cfg->fc_protocol;
 831    fi->fib_scope= cfg->fc_scope;
 832    fi->fib_flags= cfg->fc_flags;
 833    fi->fib_priority= cfg->fc_priority;
 834    fi->fib_prefsrc= cfg->fc_prefsrc;
 835    fi->fib_type= cfg->fc_type;
//初始化下一跳的成员,对于没有配置等价路由的,这个循环只会执行一次,对于等价路由,有几个等价的路由项就会执行几
//次。将下一跳的nh_parent指向fib_info字段。分配一个percpu变量,这会为每个核创建一个对应的rtable,rtable是路由缓//存部分的,其将加速路由项的查找,路由缓存参考11.5节。
 837    fi->fib_nhs= nhs;
 838    change_nexthops(fi){
 839        nexthop_nh->nh_parent= fi;
 840        nexthop_nh->nh_pcpu_rth_output= alloc_percpu(struct rtable __rcu *);
 841        if(!nexthop_nh->nh_pcpu_rth_output)
 842            gotofailure;
 843    }endfor_nexthops(fi)
//用户空间没有传递metric,跳过。
 845    if(cfg->fc_mx) {
 846        structnlattr *nla;
 847        intremaining;
 848
 849        nla_for_each_attr(nla,cfg->fc_mx, cfg->fc_mx_len, remaining) {
 850            inttype = nla_type(nla);
 851
 852            if(type) {
 853                u32 val;
 854
 855                if (type > RTAX_MAX)
 856                    goto err_inval;
 857                val = nla_get_u32(nla);
 858                if (type == RTAX_ADVMSS&& val > 65535 - 40)
 859                    val = 65535 - 40;
 860                if (type == RTAX_MTU&& val > 65535 - 15)
 861                    val = 65535 - 15;
 862                fi->fib_metrics[type - 1] =val;
863            }
 864        }
 865    }
//用户空间也没有配置等价路由,执行else语句。
 867    if(cfg->fc_mp) {
 868#ifdefCONFIG_IP_ROUTE_MULTIPATH
 869        err= fib_get_nhs(fi, cfg->fc_mp, cfg->fc_mp_len, cfg);
 870        if(err != 0)
 871            gotofailure;
 872        if(cfg->fc_oif && fi->fib_nh->nh_oif !=cfg->fc_oif)
 873            gotoerr_inval;
 874        if(cfg->fc_gw && fi->fib_nh->nh_gw != cfg->fc_gw)
 875            gotoerr_inval;
 876#ifdef CONFIG_IP_ROUTE_CLASSID
 877        if(cfg->fc_flow && fi->fib_nh->nh_tclassid !=cfg->fc_flow)
 878            gotoerr_inval;
 879#endif
 880#else
 881        gotoerr_inval;
 882#endif
 883     } else{
//对下一跳的赋值,见图12.3.2。赋值的来源同样是用户空间。
 884        structfib_nh *nh = fi->fib_nh;
 885
 886        nh->nh_oif= cfg->fc_oif;
 887        nh->nh_gw= cfg->fc_gw;
 888        nh->nh_flags= cfg->fc_flags;
 889#ifdef CONFIG_IP_ROUTE_CLASSID
 890        nh->nh_tclassid= cfg->fc_flow;
 891        if(nh->nh_tclassid)
 892            fi->fib_net->ipv4.fib_num_tclassid_users++;
 893#endif
 894#ifdefCONFIG_IP_ROUTE_MULTIPATH
 895        nh->nh_weight= 1;
 896#endif
 897    }
//fc_type类型是2,即单播,该类型的error成员值是0,执行else语句。else语句判断type字段是否合法。
 899    if(fib_props[cfg->fc_type].error) {
 900        if(cfg->fc_gw || cfg->fc_oif || cfg->fc_mp)
 901            gotoerr_inval;
 902        gotolink_it;
 903    } else{
 904        switch(cfg->fc_type) {
 905        caseRTN_UNICAST:
 906        case RTN_LOCAL:
 907        caseRTN_BROADCAST:
 908        case RTN_ANYCAST:
 909        caseRTN_MULTICAST:
 910            break;
 911        default:
 912            gotoerr_inval;
 913        }
 914    }
//范围大于RT_SCOPE_HOST(253)就出错了。用于限定路由的范围。
 916    if(cfg->fc_scope > RT_SCOPE_HOST)
 917        gotoerr_inval;
//fc_scope的值等于254,执行if语句里的内容,当使用route命令配置一个(RT_SCOPE_LINK)范围地址,执行else语句
 919    if(cfg->fc_scope == RT_SCOPE_HOST) {
//fi是在818行创建的指向fib_info的结构体,该结构体的部分程员在821~843被赋值,这里是处理其nh(next hop)字段。
 920        structfib_nh *nh = fi->fib_nh;
 921
 922        /*Local address is added. */
 923        if(nhs != 1 || nh->nh_gw)
 924            gotoerr_inval;
//目的地址是本机,所以scope赋值成RT_SCOPE_NOWHERE,该scope范围不会向外发送数据包。
 925        nh->nh_scope= RT_SCOPE_NOWHERE;
//根据索引号获得net_device结构体,net_device结构体用于标记网卡,其实就是“eth0”结构体,该网卡用来发送数据。
 926        nh->nh_dev= dev_get_by_index(net, fi->fib_nh->nh_oif);
 927        err= -ENODEV;
 928        if(nh->nh_dev == NULL)
 929            gotofailure;
 930    } else{
 931        change_nexthops(fi){
//fib_check_nh检查下一跳语义的正确性,
 932            err= fib_check_nh(cfg, fi, nexthop_nh);
 933            if(err != 0)
 934                goto failure;
 935        }endfor_nexthops(fi)
 936    }
// prefsrc是dd270c0a,并且检查也通过。
 938     if(fi->fib_prefsrc) {
 939        if(cfg->fc_type != RTN_LOCAL || !cfg->fc_dst ||
 940            fi->fib_prefsrc!= cfg->fc_dst)
 941            if(inet_addr_type(net, fi->fib_prefsrc) != RTN_LOCAL)
 942                goto err_inval;
 943    }
//获得对应接口的源地址信息,将这些信息存放在下一跳nexthop_nh中。
 945    change_nexthops(fi){
 946        fib_info_update_nh_saddr(net,nexthop_nh);
 947    }endfor_nexthops(fi)
//在fib_info_hash所标示的哈希表中,见图12.3.2左上角,紫色部分;看看要插入的路由项是否已经存在了,如果存在,则//955行直接返回,由于用户空间的配置路由项原本内核路由表中没有,所以接着958行继续。
 949link_it:
 950    ofi = fib_find_info(fi);
 951    if(ofi) {
 952        fi->fib_dead= 1;
953        free_fib_info(fi);
 954        ofi->fib_treeref++;
 955        returnofi;
 956    }
//路由树引用计数++
 958    fi->fib_treeref++;
 959    atomic_inc(&fi->fib_clntref);
//获得保护该路由信息记录项的锁。这个fib_info_hash表是局部全局的。以安全将上面创建的fi添加到fib_info_hash链表上。
 960    spin_lock_bh(&fib_info_lock);
//将这个新的fib_info,添加到管理这个结构的全局哈希表上。
 961    hlist_add_head(&fi->fib_hash,
 962               &fib_info_hash[fib_info_hashfn(fi)]);
969    change_nexthops(fi){
 970        structhlist_head *head;
 971        unsignedint hash;
 972
 973        if(!nexthop_nh->nh_dev)
 974            continue;
//ifindex的值等于2,对应于eth0。
 975        hash= fib_devindex_hashfn(nexthop_nh->nh_dev->ifindex);
 976        head= &fib_info_devhash[hash];
// fib_info_devhash是存储设备的哈希表,将nexthop指向设备的哈希元素添加到fib_info_devhash链表上。
 977        hlist_add_head(&nexthop_nh->nh_hash,head);
 978    }endfor_nexthops(fi)
 979    spin_unlock_bh(&fib_info_lock);
//最后这里返回已经添加到相应管理链表上的fib_info的结构体信息fi。
 980    returnfi;
992 }


图12.3.2 ifconfig使用到的数据结构

fib_find_info参数值是前面创建的fib_info结构体,这个函数就是在管理fib_info的哈希链表fib_info_hash查找先前创建的fib_info信息是否已经存在这张链表了,如果存在,那么说明fib_info没有必要在创建了,直接重复利用就好了。

298 static struct fib_info *fib_find_info(const struct fib_info *nfi)
 299 {
 300     struct hlist_head *head;
 301     struct fib_info *fi;
 302     unsigned int hash;
//计算fib_info的哈希索引值,该哈希值依赖于fib_protocol、fib_scope、fib_prefsrc、fib_priority以及设备index。
 304     hash = fib_info_hashfn(nfi);
//直接得到传递进来了fib_info所对应的哈希表头,如果在这个表中能够找到就直接返回,如果找不到返回NULL,返回去以后
//的函数查看返回值,是NULL的话,说明这项并不存在,就会将这个fib_info添加到fib_info_hash链表上。
 305     head = &fib_info_hash[hash];
//遍历哈希表,依次对比哈希表的每一个成员的下列字段和传递进来要找的fib_info是否相同。 
 307     hlist_for_each_entry(fi, head, fib_hash) {
 308         if (!net_eq(fi->fib_net, nfi->fib_net))
 309             continue;
 310         if (fi->fib_nhs != nfi->fib_nhs)
 311             continue;
 312         if (nfi->fib_protocol == fi->fib_protocol &&
 313             nfi->fib_scope == fi->fib_scope &&
 314             nfi->fib_prefsrc == fi->fib_prefsrc &&
 315             nfi->fib_priority == fi->fib_priority &&
 316             nfi->fib_type == fi->fib_type &&
 317             memcmp(nfi->fib_metrics, fi->fib_metrics,
 318                sizeof(u32) * RTAX_MAX) == 0 &&
 319             ((nfi->fib_flags ^ fi->fib_flags) & ~RTNH_F_DEAD) == 0 &&
 320             (nfi->fib_nhs == 0 || nh_comp(fi, nfi) == 0))
 321             return fi;
 322     }
 323 
 324     return NULL;
 325 }

继续回到fib_table_insert 的1204行fib_find_node,该函数用于查找叶子节点。页叶子节点和tnode都是用来表示字典树节点,它们处在这棵树的位置不一样,两者并不完全相同,但是前两个成员是一样的,两者之间的互相转换的技巧前面已经遇到过了。这个函数的参数,key就是要添加的192.168.0.10 IP地址,第一个参数见图12.3.2路由表拓扑的底部。这个函数查找的过程是这样的:首先遍历tnode,然后是遍历leaf,这个过程还是很容易理解的。

955 static struct leaf *
 956fib_find_node(struct trie *t, u32 key)
 957 {
 958    int pos;
 959    struct tnode *tn;
 960    struct rt_trie_node *n;
 961
 962    pos = 0;
//将传递进来的trie节点转换成tnode类型。
 963    n = rcu_dereference_rtnl(t->trie);
/*************************************************************************************************
//在插入唯一一项10.12.39.0时,这一项就是一个leaf。
//在添加192.168.0.10时,由于n指向10.12.39.0的rt_trie_node类结构体,并不是NULL。
// n->parent:00000001, n->key:0a0c2700。
//而在添加192.168.0.10时,
// n->parent:00000000, n->key:0a0c2700
n->parent用于存放其父节点的地址,这个父节点是tnode,如果不存在则是NULL(0000000*)。该成员的最低bit用于标识这个节点是tnode还是leaf。如果是tnode则最低一个bit是0,如果是leaf则最低一个bit是1。
//#define T_TNODE 0
//#define T_LEAF  1
在插入192.168.0.10时,由于10.12.39.0是个leaf,所以这个循环被跳过了。而插入192.168.0.100时,由于有tnode存在,所以这里while循环就不会被跳过了,这个tnode是图12.3.2的上面两行产生了,至于pos为25的则是192.168.0.100插入之后才会存在的tnode。
***************************************************************************************************/
 965    while (n != NULL && NODE_TYPE(n) == T_TNODE) {
 966        tn = (struct tnode *) n;
 967
 968        check_tnode(tn);
//970~977非常经典的几行代码,就这么短短的几行代码能够处理更正情况下与要查找的key最接近的tnode。
 970        if (tkey_sub_equals(tn->key, pos, tn->pos-pos, key)) {
 971            pos = tn->pos + tn->bits;
 972            n = tnode_get_child_rcu(tn,
 973                         tkey_extract_bits(key,
 974                                   tn->pos,
 975                                  tn->bits));
 976        } else
 977            break;
 978    }

//如果这个if语句满足,说明我们要插入的项已经存在这个路由表了,这是一次重复插入操作。
 981    if (n != NULL && IS_LEAF(n) && tkey_equals(key,n->key))
 982        return (struct leaf *)n;
 983
 984    return NULL;
 985 }

为了说明遍历tnode的过程,看图12.3.2,如果根据该图张图不能想象出trie的拓扑结构,参考图12.2.4,它们是一样的拓扑结构。在这个基础上,这里在次插入192.168.0.11这个项。

图12.3.2 路由项查找实例

Key值就是192.168.0.11,t参数指向10.12.39.0这个tnode。

遍历过程如下:

         1、遍历第一个tnode,if语句因tn->pos-pos的等于零成立。遍历trie树第一个tnode。

tkey_extract_bits提取key(192.168.0.11)的pos位置开始的bits位的值,这里就是1。tnode_get_child_rcu转变成tnode_get_child_rcu(tn,1),这个函数获得tnode的child成员,这里就是child[1],

         2、再一次遍历tnode的第二个节点。这个tn->pos是25,tn->bits是1。971行的pos将变成26,这就意味着从第26个bit开始比较。然后比较的bits数是1。这时得到是child[0],参考图12.2.4,该节点挂载的是192.168.0.10,。

         3、该函数返回以后会,外部函数会将这个child指向的leaf节点,变成指向一个tnode节点,tnode的孩子节点分别指向192.168.0.10和192.168.0.11。

1312行是核心的插入操作,为了使这一过程更具代表性,这里结合图12.2.4以插入192.168.0.100为例进行源码分析,在插入192.168.0.100之前的状态见图12.2.3,只存在key等于10.12.39.0的tnode,和child[0]指向key等于10.12.39.0的leaf和child[1]指向key值等于192.168.0.10的leaf节点。在插入192.168.0.100时,fib_inset_node d的参数意义如下:

1、t指向key等于10.12.39.0的tnode;

2、key是需要插入的目的IP 192.168.0.100

3、plen是前缀长度,这里是32。

1023 static struct list_head *fib_insert_node(struct trie *t, u32 key, int plen)
1024 {
1025     int pos, newpos;
1026     struct tnode *tp = NULL, *tn = NULL;
1027     struct rt_trie_node *n;
1028     struct leaf *l;
1029     int missbit;
1030     struct list_head *fa_head = NULL;
1031     struct leaf_info *li;
1032     t_key cindex;
1033 
1034     pos = 0;
//rcu类型变量引用。
1035     n = rtnl_dereference(t->trie);
//由于t->trie是key等于10.12.39.0的tnode,所以这个while的语句判断第一次是满足的。
1055     while (n != NULL &&  NODE_TYPE(n) == T_TNODE) {
//从类型rt_trie_node到tnode强制类型转换,内核常用技巧
1056         tn = (struct tnode *) n;
//tnode验证,tnode != NULL,并且该tnode的pos和bits成员之和需要小于32。
1058         check_tnode(tn);
//下面这段代码和fib_find_node一样。
1060         if (tkey_sub_equals(tn->key, pos, tn->pos-pos, key)) {
1061             tp = tn; //图12.2.4中的copy来自这行代码。
1062             pos = tn->pos + tn->bits; //获得比较的起始位置
//返回tnode下合适的孩子,这个孩子可能是tnode也可能是leaf,对于这里情况返回192.168.0.10 leaf。
1063             n = tnode_get_child(tn, 
1064                         tkey_extract_bits(key, //根据传递进来的key,查tnode处不同的bits。
1065                                   tn->pos,
1066                                   tn->bits));
1067 
1068             BUG_ON(n && node_parent(n) != tn);
1069         } else
1070             break;
1071     }
1072 
//这里两个条件满足,但是第三个条件不满足,实际上如果第三个条件满足,这就说明该路由项已经在该路由表中了,没有必
//要再次添加该路由项。
1083     if (n != NULL && IS_LEAF(n) && tkey_equals(key, n->key)) {
1084         l = (struct leaf *) n;
1085         li = leaf_info_new(plen);
1086 
1087         if (!li)
1088             return NULL;
//将leaf串接成一个链表,leaf_info结构体的falh成员完成这一工作。
1090         fa_head = &li->falh; 
1091         insert_leaf_info(&l->list, li);
1092         goto done;
1093     }
//如果1083行if语句不满足,说明路由表中不存在这么一项,创建一个新的leaf,该leaf用于插入项的索引。
1094     l = leaf_new();
//申请内存失败处理
1096     if (!l)
1097         return NULL;
//这里将192,168.0.100作为key值传递给leaf,并根据前缀长度创建一个记录leaf信息的li(leaf_info结构体)
1099     l->key = key;
1100     li = leaf_info_new(plen);
//记录leaf信息的结构体内存申请失败的处理。
1102     if (!li) {
1103         free_leaf(l);
1104         return NULL;
1105     }
//将leaf_info和leaf建立关系,即将leaf_info的falh成员指向leaf的list。
1107     fa_head = &li->falh;
1108     insert_leaf_info(&l->list, li);
//上面已经看到n显然不等于NULL。
1110     if (t->trie && n == NULL) {
1111         /* Case 2: n is NULL, and will just insert a new leaf */
1112 
1113         node_set_parent((struct rt_trie_node *)l, tp);
1114 
1115         cindex = tkey_extract_bits(key, tp->pos, tp->bits);
1116         put_child(tp, cindex, (struct rt_trie_node *)l);
1117     } else {
//tp在前面得到赋值(copy),tp->pos+tp->bits将等于1,这就意味着key等于192.168.0.100的插入项从第一个比特开始比较。
1124         if (tp)
1125             pos = tp->pos+tp->bits;
1126         else
1127             pos = 0;
1128 
1129         if (n) {
/***************************************************************************************************************
265 static inline int tkey_mismatch(t_key a, int offset, t_key b)
 266 {
 267     t_key diff = a ^ b;
 268     int i = offset;
 269 
 270     if (!diff)
 271         return 0;
 272     while ((diff << i) >> (KEYLENGTH-1) == 0)
 273         i++;
 274     return i;
 275 }
///key:c0a80064,pos:1,n->key:0a0c2700
将这三个参数带入上面的函数,KEYLENGTH值是32,可得25,源于192.168.0.100和192.168.0.10二进制不同的起始比特
**************************************************************************************************************/
1130             newpos = tkey_mismatch(key, pos, n->key);
//根据n->key和newpos创建一个tnode,这对应于图12.2.4中的key等于192.168.0.10的tnode。
1131             tn = tnode_new(n->key, newpos, 1);
1132         } else {
//首次需要创建tnode的情况,这是一个特殊情况,一个路由表,只会调用一次这里的函数。
1133             newpos = 0;
1134             tn = tnode_new(key, newpos, 1); /* First tnode */
1135         }
//tn创建失败的处理
1137         if (!tn) {
1138             free_leaf_info(li);
1139             free_leaf(l);
1140             return NULL;
1141         }
//这个函数就是设置这里创建的tn(tree node)的父节点。注意看图12.2.4中的ADD开始字符串,这里的赋值使用就是ADD
//冒号后字符串,实际上是地址,注意图中它们的赋值关系。
1143         node_set_parent((struct rt_trie_node *)tn, tp);
//从第25个比特比起,它们不同的第一个比特是1,
1145         missbit = tkey_extract_bits(key, newpos, 1);
//在tnode的child[1],插入key值等于192.168.0.100的leaf。见12.3.4小节。
//在tnode的child[0],插入key值等于192.168.0.10的leaf。
1146         put_child(tn, missbit, (struct rt_trie_node *)l);
1147         put_child(tn, 1-missbit, n);
//1061行,如果有copy,那么需要跟新原来tnode的child,其child[1]变成了一个具有两个leaf的tnode。
1149         if (tp) {
//这里找到tnode插入的起始位置。
1150             cindex = tkey_extract_bits(key, tp->pos, tp->bits);
//put_child为插入操作。
1151             put_child(tp, cindex, (struct rt_trie_node *)tn);
1152         } else {
1153             rcu_assign_pointer(t->trie, (struct rt_trie_node *)tn);
1154             tp = tn;
1155         }
1156     }
//设置后的参数合理性检查,温和的使用了warn。
1158     if (tp && tp->pos + tp->bits > 32)
1159         pr_warn("fib_trie tp=%p pos=%d, bits=%d, key=%0x plen=%d\n",
1160             tp, tp->pos, tp->bits, key, plen);
1161 
1162     /* Rebalance the trie */
//提到过好多次的rebalance操作。图12.3.2中黄色的列,bits值在前面的操作都是一个比特,在rebalance中可能会变成多个
//比特。
1164     trie_rebalance(t, tp);
1165 done:
1166     return fa_head;
1167 }

后面是发送一个消息,网卡接收到消息后,执行函数fib_netdev_event()函数,这个函数会再次调用fib_inetaddr_event,这个函数这次会按顺序添加子网号全1、全0以及主机号为全1(10.12.39.255广播地址,10.12.39.221也是要接收发往这个地址的数据的)。其过程和上面的类似,插入的节点hash值也是相同的(图中表ifconfig的数组索引)。

图12.3.3由ifconfig引发的前三次路由表更新过程

12.3.4 put_child

12.3.3小节可以知道核心的插入函数时put_child,所以这里单独一个小节来看一下插入的过程是如何完成的。这节的情况还是继续12.3.3小节。但是只打算分析1164行,其它的读者自行完成。

475 static inline int tnode_full(const struct tnode *tn, const struct rt_trie_node *n)
 476 {
//对于NULL和leaf情况,返回值一定是0,用于更新full_children计数器。
 477     if (n == NULL || IS_LEAF(n))
 478         return 0;
//对于tnode情况的判断方法。
480     return ((struct tnode *) n)->pos == tn->pos + tn->bits;
 481 }
483 static inline void put_child(struct tnode *tn, int i,
 484                  struct rt_trie_node *n)
 485 {
 486     tnode_put_child_reorg(tn, i, n, -1);
 487 }
 488 
 489  /*
 490   * Add a child at position i overwriting the old value.
 491   * Update the value of full_children and empty_children.
 492   */
 493 
 494 static void tnode_put_child_reorg(struct tnode *tn, int i, struct rt_trie_node *n,
 495                   int wasfull)
 496 {
 497     struct rt_trie_node *chi = rtnl_dereference(tn->child[i]);
 498     int isfull;
 499 
//传递进来的n非空,chi这是还没有获得有效值,所以会执行505行的else语句。对于插入操作,基本都是对
//empty_children减一操作
 502     /* update emptyChildren */
 503     if (n == NULL && chi != NULL)
 504         tn->empty_children++;
 505     else if (n != NULL && chi == NULL)
 506         tn->empty_children--;
 507 
//对于添加路由项,传递进来的值都是-1
 509     if (wasfull == -1)
 510         wasfull = tnode_full(tn, chi);
 511 
 512     isfull = tnode_full(tn, n);
 513     if (wasfull && !isfull)
 514         tn->full_children--;
 515     else if (!wasfull && isfull)
 516         tn->full_children++;
 517 
/*******************************************************************************************************
最低一个bit设置node的类型,要么tnode,要么leaf,高位存放地址(ADD)
208 static inline void node_set_parent(struct rt_trie_node *node, struct tnode *ptr)
 209 {
 210     smp_wmb();
 211     node->parent = (unsigned long)ptr | NODE_TYPE(node);
 212 }
********************************************************************************************************/
 518     if (n)
 519         node_set_parent(n, tn);
 520 
//添加的操作实际上非常的简单,就是一个rcu变量的赋值,将tnode的child成员赋值成适当的值就好了。
 521     rcu_assign_pointer(tn->child[i], n);
522 }

483参数:

tn是key值为0a0c2700的tnode,

i值是1

l是key等于192.168.0.100的leaf,(先前已经创建并初始化好了)。

该函数调用了,486 tnode_put_child_reorg,完成添加工作。

479行原来tnode的child[1]是指向key值等于192.168.0.10的leaf。

         502~506跟新孩子计数器。

         509行,判断chi是否“满”,由于这里的chi是键值为192.168.0.10的leaf,所以其返回值是0,

         512行,判断插入leaf,192.168.0.100是否满,显然返回值是0。

12.4 route添加路由项

路由这章的介绍和前面的章节分开,从工具说起,在Linux下经常使用route工具配置路由项和网关。下面的这段代码来自配置网关的用户空间程序的代码片段,从这个函数可以看出,创建一个套接字,并调用ioctl方法完成网关的设置。

memcpy(ifr.ifr_name, interface_name, sizeof(ifr.ifr_name)); 
	(void)ioctl(sockfd, SIOGIFINDEX, &ifr); 
	v4_rt.rtmsg_ifindex = ifr.ifr_ifindex; 
	
	memcpy(&v4_rt.rtmsg_gateway, gateway, sizeof(struct in_addr)); 
	/*添加路由*/
	if (ioctl(sockfd, SIOCADDRT, &v4_rt) < 0) 
	{
		SAFE_CLOSE(sockfd); 
		BASEFUN_DBG(RT_ERROR, "add route ioctl error and errno=%d\n", errno); 
		return -1; 
	}	
	SAFE_CLOSE(sockfd);

这个ioctl函数对应到内核下最终的路由配置函数是ip_rt_ioctl,该函数位于net/ipv4/fib_front.c文件。Linux源码中路由的表示结构体是fib*,FIB(forward information base),所以后面会出现好多以fib开始的程序片段。

480 int ip_rt_ioctl(struct net *net, unsigned int cmd, void __user *arg)
 481 {
 482     struct fib_config cfg;
 483     struct rtentry rt;
 484     int err;
 485 
 486     switch (cmd) {
 487     case SIOCADDRT:     /* Add a route */
 488     case SIOCDELRT:     /* Delete a route */
 489         if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
 490             return -EPERM;
 491 
 492         if (copy_from_user(&rt, arg, sizeof(rt)))
 493             return -EFAULT;
 494 
 495         rtnl_lock();
 496         err = rtentry_to_fib_config(net, cmd, &rt, &cfg);
 497         if (err == 0) {
 498             struct fib_table *tb;
 499 
 500             if (cmd == SIOCDELRT) {
 501                 tb = fib_get_table(net, cfg.fc_table);
 502                 if (tb)
 503                     err = fib_table_delete(tb, &cfg);
 504                 else
 505                     err = -ESRCH;
 506             } else {
 507                 tb = fib_new_table(net, cfg.fc_table);
 508                 if (tb)
 509                     err = fib_table_insert(tb, &cfg);
 510                 else
 511                     err = -ENOBUFS;
 512             }
 513 
 514             /* allocated by rtentry_to_fib_config() */
 515             kfree(cfg.fc_mx);
 516         }
 517         rtnl_unlock();
 518         return err;
 519     }
 520     return -EINVAL;
 521 }

根据cmd参数SIOCADDRT可以跟踪到switch内执行的程序代码段,496行rtentry_to_fib_config用于将用户空间的路由配置信息转变到内核记录配置信息的结构体cfg中,在图12.4.2中等号右边的信息就是来自用户空间的配置信息。

图12.4.1是使用route命令添加了一个路由项、一个网关和导出路由项,内核下保存配置信息的成员cfg是struct fib_config类型的一个结构体,该结构体的成员值在图12.4.1中列了出来。

include/net/ip_fib.h

26 struct fib_config {
 27     u8          fc_dst_len;
 28     u8          fc_tos;
 29     u8          fc_protocol;
 30     u8          fc_scope;
 31     u8          fc_type;
 32     /* 3 bytes unused */
 33     u32         fc_table;
 34     __be32          fc_dst;
 35     __be32          fc_gw;
 36     int         fc_oif;
 37     u32         fc_flags;
 38     u32         fc_priority;
 39     __be32          fc_prefsrc;
 40     struct nlattr       *fc_mx;
 41     struct rtnexthop    *fc_mp;
 42     int         fc_mx_len;
 43     int         fc_mp_len;
 44     u32         fc_flow;
 45     u32         fc_nlflags;
 46     struct nl_info      fc_nlinfo;
 47  };

图12.4.1 route命令配置路由信息

根据上面信息,将各项内容表述在如下图形中:

图12.4.2 cfg信息

由于应用空间传递的命令是SIOCADDRT(代码片段一),所以执行507~511行的代码。fib_new_table用于获得一个和用户匹配类型的路由表,如果在现有的路由表中没有要找的类型,则会创建这个类型的路由表。

 75 struct fib_table *fib_new_table(struct net *net, u32 id)
  76 {
  77     struct fib_table *tb;
  78     unsigned int h;
  79 
  80     if (id == 0)
  81         id = RT_TABLE_MAIN;
  82     tb = fib_get_table(net, id);
  83     if (tb)
  84         return tb;
  85 
  86     tb = fib_trie_table(id);
  87     if (!tb)
  88         return NULL;
  89 
  90     switch (id) {
  91     case RT_TABLE_LOCAL:
  92         net->ipv4.fib_local = tb;
  93         break;
  94 
  95     case RT_TABLE_MAIN:
  96         net->ipv4.fib_main = tb;
  97         break;
  98 
  99     case RT_TABLE_DEFAULT:
 100         net->ipv4.fib_default = tb;
 101         break;
 102 
 103     default:
 104         break;
 105     }
 106 
 107     h = id & (FIB_TABLE_HASHSZ - 1);
 108     hlist_add_head_rcu(&tb->tb_hlist, &net->ipv4.fib_table_hash[h]);
 109     return tb;
 110 }
112 struct fib_table *fib_get_table(struct net *net, u32 id)
 113 {
 114     struct fib_table *tb;
 115     struct hlist_head *head;
 116     unsigned int h;
 117 
 118     if (id == 0)
 119         id = RT_TABLE_MAIN;
 120     h = id & (FIB_TABLE_HASHSZ - 1);
 121 
 122     rcu_read_lock();
 123     head = &net->ipv4.fib_table_hash[h];
 124     hlist_for_each_entry_rcu(tb, head, tb_hlist) {
 125         if (tb->tb_id == id) {
 126             rcu_read_unlock();
 127             return tb;
 128         }
 129     }
 130     rcu_read_unlock();
 131     return NULL;
 132 }

82行fib_get_table用户查找现有的路由表,路由表的类型定义在include/uapi/linux/rtnetlink.h文件中:

enum rt_class_t {
	RT_TABLE_UNSPEC=0,
/* User defined values */
	RT_TABLE_COMPAT=252,
	RT_TABLE_DEFAULT=253,
	RT_TABLE_MAIN=254,
	RT_TABLE_LOCAL=255,
	RT_TABLE_MAX=0xFFFFFFFF
};

虽然这里最大路由项的定义值是RT_TABLE_MAX,但是其实在不启动等价路由【Equal-cost multi-path routing(ECMP)(CONFIG_IP_ROUTE_MULTIPATH)】时,配置CONFIG_IP_MULTIPLE_TABLES时有256张路由表。启用等价路由时只会有两张路由表,在存在多条网络路径的网络节点会配置使能。

图12.4.3的路由表拓扑结构中, net代表了一个网络命名空间,其指针成员ipv4指向inet协议字段,ipv4的fib_table_hash是一个数组,其指向是哈希链表,该表表映射的就是具体路由表的类型,路由表的类型从0开始一直到255,图中第一行的RT_TABLE_DEFAULT就等于253依次类推,所以在查找路由表时,只需要记得你找的是MAIN表还是LOCAL,而不需要关心是254还是255之类的数值了,这很类似于域名概念。每一个路由表由struct fib_table类型表示。命名空间中的哈希字段指向具体路由表的tb_list字段,并且处在同一个类型的路由表之间也是通过这个字段联系在一起的。

图12.4.3路由表拓扑

fc_table项指示的选择哪一张表插入用户配置的路由项,fib_new_table的82行fib_get_table根据路由表的id(根据图12.1.2其传递进来的值等于0)字段查询指定类型的路由表。81行将id赋值为RT_TABLE_MAIN。

120行得到h等于 RT_TABLE_MAIN。

将123行结合图12.1.3就可以看出其找到main表。

124~128根据路由表的tb_list字段遍历路由表,寻找路由表的tb_id等于RT_TABLE_MAIN的表。找到就返回该表,没找到就返回NULL,让其创建该表项。

83~84,如果fib_get_table找到了指定的表,则就是向这个表插入一个路由项,如果没有找到,说明需要创建一个这个表,接着向下86行即用于创建一个表。创建路由表的函数位于fib_trie.c文件,在2.6.38及以前Linux默认使用的是hash方法管理路由表和路由项,到Linux3.10,内核默认使用LC-trie方法,中文里常把这个算法称为字典或者单词查找树,路由使用的关于单词查找树的代码都在fib_trie.c。这个算法在分析代码时遇到了再说。

net/ipv4/fib_trie.c

1970 struct fib_table *fib_trie_table(u32 id)
1971 {
1972     struct fib_table *tb;
1973     struct trie *t;
1974 
1975     tb = kmalloc(sizeof(struct fib_table) + sizeof(struct trie),
1976              GFP_KERNEL);
1977     if (tb == NULL)
1978         return NULL;
1979 
1980     tb->tb_id = id;
1981     tb->tb_default = -1;
1982     tb->tb_num_default = 0;
1983 
1984     t = (struct trie *) tb->tb_data;
1985     memset(t, 0, sizeof(*t));
1986 
1987     return tb;
1988 }

1975行创建一个路由表,路由表的定义如下,这里的内存申请将零长数组tb_data初始化为struct trie成员。1980~1985初始化该路由表的各个字段。

struct fib_table {
	struct hlist_node	tb_hlist;
	u32			tb_id;
	int			tb_default;
	int			tb_num_default;
	unsigned long		tb_data[0];
};

fib_new_table的90~105行将网络命名空间的fib_main字段指向这里创建的MAIN表。108行再将MAIN表放到数组fib_table_hash里,以便后续的索引。

再回退到ip_rt_ioctl的509行,fib_table_insert用于向路由表中插入一个路由项了。可以知道插入操作是基于trie方法的。其插入文件位于net/ipv4/fib_trie.c文件。这个函数有点长,但是还是要一行一行的过。

其参数是上面找到的或者创建的路由表,配置项就是前面用户空间配置的信息,该信息见图12.4.2。

12.6 路由查找

在12.2和12.3节,主要关注的是trie树的构建和管理,并不涉及路由查找的内容,由于管理和维护一个trie树本身是一件复杂的事,所以内核没有将查找路由表需要的信息也放在trie树中存储,而是使用了其它的一些数据结构来辅助查找过程,这节先引入这些数据结构,然后剖析查找过程,查找的总体思想是首先检查参数的合法性,查找trie(tnode ,leaf)树,trie树本身存储的是key值,没有其它 过多的信息,这些信息包括tos,也即流控,所以需要查找一些辅助数据结构,查找到了以后,如果对应的路由项没有缓存,会创建一个路由缓存。

12.6.1 相关数据结构

 

本节所述的数据结构指的是辅助数据结构,并没有将使用到的trie树相关数据结构罗列出来。关于这几个数据结构间的关系见图12.3.3。

fib_result

fib_result用于存放路由查找的结果

include/net/ip_fib.h

struct fib_result {
unsigned char	prefixlen; //前缀长度
unsigned char	nh_sel; //nexthop 索引
unsigned char	type; //type和scope是类型和范围
unsigned char	scope;
u32		tclassid; 
struct fib_info *fi; //路由项对应的信息
struct fib_table *table; //该结果源于的表项
struct list_head *fa_head; //fib alias链表指针。
};
fib_info
struct fib_info {
	struct hlist_node	fib_hash; //fib_info信息索引,局部全局数组fib_info_hash使用
	struct hlist_node	fib_lhash;//同样是局部全局数组fib_info_laddrhash索引
	struct net		*fib_net;//对应设备所在网络命名空间中的一些信息。
	int			fib_treeref;//trie树引用计数
	atomic_t		fib_clntref;
	unsigned int		fib_flags;
//标记该fib_info是否可用,当值是1时,标志该路由项失效,查找时将不会参考该值。
	unsigned char		fib_dead;	
unsigned char		fib_protocol; //支持的协议
	unsigned char		fib_scope; //范围
	unsigned char		fib_type;//类型
	__be32			fib_prefsrc; //前缀
	u32			fib_priority; //优先级
	u32			*fib_metrics; //metrics相关内容
#define fib_mtu fib_metrics[RTAX_MTU-1]
#define fib_window fib_metrics[RTAX_WINDOW-1]
#define fib_rtt fib_metrics[RTAX_RTT-1]
#define fib_advmss fib_metrics[RTAX_ADVMSS-1]
	int			fib_nhs; //记录fib_nh[0]零长数组具有的下一跳的个数。
#ifdef CONFIG_IP_ROUTE_MULTIPATH
	int			fib_power;
#endif
	struct rcu_head		rcu;
	struct fib_nh		fib_nh[0];
#define fib_dev		fib_nh[0].nh_dev
};

该数据结构元素的初始化实例可以参考图12.3.3,图中的fi、fi-2、fi-3分别对应于三种路由项情况。

fib_alias

虽然套接字数据包路由项key可能不同,例如路由到192.168.0.10和路由到192.168.0.100的两项,但是不同套接字数据包的type或者tos是相同的,对于一个大规模的路由而言,的类型不同套接字数据包的type和tos相同的概率还是比较大的,为了节约空间和时间,就将这些字段抽象出来了,tos和type组合的类型有限,这样可以使这个有限的组合应对无限多路由项。fa_list用于串接所有的fib_alias类型的结构体,以便于管理fa_alias结构体。

struct fib_alias {
	struct list_head	fa_list;
	struct fib_info		*fa_info; //见上
	u8			fa_tos;
	u8			fa_type;// UNICAST、LOCAL、BROADCAST
	u8			fa_state;
	struct rcu_head		rcu;
};
fib_flowi4

该数据结构在图12.3.3中并未显示出来,路由查找过程中会使用到该数据结构,该数据结构根据输入输出网络设备、ip层和tcp层一些字段对流量进行分类。flowi4(flow inet 4)是Internet 4协议的流控之意。对应的还有flowi6的流控,其实该结构体就是对flowi_common的封装,这么做的好处是不言而喻的,提高代码复用率的同时增加了代码维护的灵活性。

struct flowi4 {
	struct flowi_common	__fl_common;
#define flowi4_oif		__fl_common.flowic_oif //output Interface 
#define flowi4_iif		__fl_common.flowic_iif //input interface
#define flowi4_mark		__fl_common.flowic_mark
#define flowi4_tos		__fl_common.flowic_tos
#define flowi4_scope		__fl_common.flowic_scope
#define flowi4_proto		__fl_common.flowic_proto
#define flowi4_flags		__fl_common.flowic_flags
#define flowi4_secid		__fl_common.flowic_secid

	/* (saddr,daddr) must be grouped, same order as in IP header */
	__be32			saddr; //源地址
	__be32			daddr;//目的地址

	union flowi_uli		uli;
#define fl4_sport		uli.ports.sport //tcp层源端口号
#define fl4_dport		uli.ports.dport//tcp层目的端口号
#define fl4_icmp_type		uli.icmpt.type
#define fl4_icmp_code		uli.icmpt.code
#define fl4_ipsec_spi		uli.spi
#define fl4_mh_type		uli.mht.type
#define fl4_gre_key		uli.gre_key
} __attribute__((__aligned__(BITS_PER_LONG/8)));
fib_ common
struct flowi_common {
	int	flowic_oif;
	int	flowic_iif;
	__u32	flowic_mark;
	__u8	flowic_tos;
	__u8	flowic_scope;
	__u8	flowic_proto;
	__u8	flowic_flags;
#define FLOWI_FLAG_ANYSRC		0x01
#define FLOWI_FLAG_CAN_SLEEP		0x02
#define FLOWI_FLAG_KNOWN_NH		0x04
	__u32	flowic_secid;
};
rtable

套接字将会绑定该结构体。

struct rtable {
	struct dst_entry	dst;

	int			rt_genid;
	unsigned int		rt_flags;
	__u16			rt_type;
	__u8			rt_is_input;
	__u8			rt_uses_gateway;

	int			rt_iif;

	/* Info on neighbour */
	__be32			rt_gateway;

	/* Miscellaneous cached information */
	u32			rt_pmtu;

	struct list_head	rt_uncached;
};
dst_entry
struct dst_entry {
	struct rcu_head		rcu_head;
	struct dst_entry	*child;
	struct net_device       *dev;
	struct  dst_ops	        *ops;
	unsigned long		_metrics;
	unsigned long           expires;
	struct dst_entry	*path;
	struct dst_entry	*from;
#ifdef CONFIG_XFRM
	struct xfrm_state	*xfrm;
#else
	void			*__pad1;
#endif
	int			(*input)(struct sk_buff *);
	int			(*output)(struct sk_buff *);

	unsigned short		flags;
#define DST_HOST		0x0001
#define DST_NOXFRM		0x0002
#define DST_NOPOLICY		0x0004
#define DST_NOHASH		0x0008
#define DST_NOCACHE		0x0010
#define DST_NOCOUNT		0x0020
#define DST_NOPEER		0x0040
#define DST_FAKE_RTABLE		0x0080
#define DST_XFRM_TUNNEL		0x0100
#define DST_XFRM_QUEUE		0x0200

	unsigned short		pending_confirm;

	short			error;

	/* A non-zero value of dst->obsolete forces by-hand validation
	 * of the route entry.  Positive values are set by the generic
	 * dst layer to indicate that the entry has been forcefully
	 * destroyed.
	 *
	 * Negative values are used by the implementation layer code to
	 * force invocation of the dst_ops->check() method.
	 */
	short			obsolete;
#define DST_OBSOLETE_NONE	0
#define DST_OBSOLETE_DEAD	2
#define DST_OBSOLETE_FORCE_CHK	-1
#define DST_OBSOLETE_KILL	-2
	unsigned short		header_len;	/* more space at head required */
	unsigned short		trailer_len;	/* space to reserve at tail */
#ifdef CONFIG_IP_ROUTE_CLASSID
	__u32			tclassid;
#else
	__u32			__pad2;
#endif

	/*
	 * Align __refcnt to a 64 bytes alignment
	 * (L1_CACHE_SIZE would be too much)
	 */
#ifdef CONFIG_64BIT
	long			__pad_to_align_refcnt[2];
#endif
	/*
	 * __refcnt wants to be on a different cache line from
	 * input/output/ops or performance tanks badly
	 */
	atomic_t		__refcnt;	/* client references	*/
	int			__use;
	unsigned long		lastuse;
	union {
		struct dst_entry	*next;
		struct rtable __rcu	*rt_next;
		struct rt6_info		*rt6_next;
		struct dn_route __rcu	*dn_next;
	};
};

12.6.2 接收包路由项查找

在ip_input.c文件中,ip_rcv_finish()函数用于处理接收到的数据包,这里将重心倾向于路由这块。 skb中没有找到路由项,即缓存中寻找路由项失败,需要调用ip_route_input_noref到路由表中查找,如果是回环包,则skb的路由缓存有路由项,即路由cache命中。

*多播地址寻找路由项函数:ip_route_input_mc

*单播地址寻找路由项函数:ip_route_input_slow

314 static int ip_rcv_finish(struct sk_buff *skb)
315 {
//根据套接字获得ip头
316         const struct iphdr *iph = ip_hdr(skb);
317         struct rtable *rt;
/ sysctl_ip_early_demux 是二进制值,该值用于对发往本地数据包的优化。当前仅对建立连接的套接字起作用。
319         if (sysctl_ip_early_demux && !skb_dst(skb)) {
320                 const struct net_protocol *ipprot;
321                 int protocol = iph->protocol;
322 
323                 ipprot = rcu_dereference(inet_protos[protocol]);
324                 if (ipprot && ipprot->early_demux) {
325                         ipprot->early_demux(skb);
326                         /* must reload iph, skb->head might have changed */
327                         iph = ip_hdr(skb);
328                 }
329         }
330 
//如果套接字的dst字段没有指向一个路由项,如果没有则调用ip_route_input_noref进行查找。
335         if (!skb_dst(skb)) {
336                 int err = ip_route_input_noref(skb, iph->daddr, iph->saddr,
337                                                iph->tos, skb->dev);
338                 if (unlikely(err)) {
339                         if (err == -EXDEV)
//更新基于tcp/ip因特网的MIB(management information base)信息,RFC1213
340                                 NET_INC_STATS_BH(dev_net(skb->dev),
341                                                  LINUX_MIB_IPRPFILTER);
342                         goto drop;
343                 }
344         }
345 
//对套接字可选字段的处理。ip_rcv_options(skb)会调用ip_options_rcv_srr(skb)
357         if (iph->ihl > 5 && ip_rcv_options(skb))
358                 goto drop;
//获得路由表
360         rt = skb_rtable(skb);
//多播和广播时的信息传递。
361         if (rt->rt_type == RTN_MULTICAST) {
362                 IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INMCAST,
363                                 skb->len);
364         } else if (rt->rt_type == RTN_BROADCAST)
365                 IP_UPD_PO_STATS_BH(dev_net(rt->dst.dev), IPSTATS_MIB_INBCAST,
366                                 skb->len);
/*向tcp层传递packet*/
368         return dst_input(skb);
373 }

336行ip_route_input_noref对于tcp/ip接收数据包进行路由寻址。

net/ipv4/route.c
/*参数的意义
skb:传递进来的skb_buff,
dst:目的地址
src: 源地址
tos:type of service,ip头中的服务类型
devin:网卡设备
*/
int ip_route_input_noref(struct sk_buff *skb, __be32 daddr, __be32 saddr,
1763                          u8 tos, struct net_device *dev)
1764 {
1765         int res;
1766 
1767         rcu_read_lock();
1768 
//多播的处理
1780         if (ipv4_is_multicast(daddr)) {
1781                 struct in_device *in_dev = __in_dev_get_rcu(dev);
1782 
1783                 if (in_dev) {
1784                         int our = ip_check_mc_rcu(in_dev, daddr, saddr,
1785                                                   ip_hdr(skb)->protocol);
1786                         if (our
1792                            ) {
1793                                 int res = ip_route_input_mc(skb, daddr, saddr,
1794                                                             tos, dev, our);
1795                                 rcu_read_unlock();
1796                                 return res;
1797                         }
1798                 }
1799                 rcu_read_unlock();
1800                 return -EINVAL;
1801         }
//除多播以外情况的处理。
1802         res = ip_route_input_slow(skb, daddr, saddr, tos, dev);
1803         rcu_read_unlock();
1804         return res;
1805 } 

1793行是多播地址寻找路由项函数ip_route_input_mc。

1373 static int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr,
1374                                 u8 tos, struct net_device *dev, int our)
1375 {
/申请并初始化dst_entry,由于rtable的第一个成员就是dst_entry,多以这里直接进行赋值,没有使用策略路由
1403         rth = rt_dst_alloc(dev_net(dev)->loopback_dev,
1404                            IN_DEV_CONF_GET(in_dev, NOPOLICY), false, false);
1405         if (!rth)
1406                 goto e_nobufs;
1407 
//初始化rtable字段的其它项。
1411         rth->dst.output = ip_rt_bug;
1412 
1413         rth->rt_genid   = rt_genid(dev_net(dev));
1414         rth->rt_flags   = RTCF_MULTICAST;
1415         rth->rt_type    = RTN_MULTICAST;
1416         rth->rt_is_input= 1;
1417         rth->rt_iif     = 0;
1418         rth->rt_pmtu    = 0;
1419         rth->rt_gateway = 0;
1420         rth->rt_uses_gateway = 0;
1421         INIT_LIST_HEAD(&rth->rt_uncached);
1422         if (our) {
1423                 rth->dst.input= ip_local_deliver;
1424                 rth->rt_flags |= RTCF_LOCAL;
1425         }
//将rtable的dst成员地址赋值给skb。
1433         skb_dst_set(skb, &rth->dst);
1434         return 0;
1442 }

ip_route_input_noref根据传递进来的目的地址判断是多播还是单播,多播使用ip_route_input_mc(skb, daddr,saddr, tos, dev, our)处理,单播使用 ip_route_input_slow(skb, daddr, saddr,tos, dev)。为了使函数的脉络看起来更为清晰,这里省去函数变量的定义、路由地址的合法性检查以及一些错误处理代码,只保留了正常情况下路由处理相关代码。

1585 static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr,
1586                                u8 tos, struct net_device *dev)
1587 {
//上面之所以要检查源和目的地址,是因为路由会使用该信息。
//流量分类信息初始化,也是流控
1637         fl4.flowi4_oif = 0;
1638         fl4.flowi4_iif = dev->ifindex;
1639         fl4.flowi4_mark = skb->mark;
1640         fl4.flowi4_tos = tos;
1641         fl4.flowi4_scope = RT_SCOPE_UNIVERSE;
1642         fl4.daddr = daddr;
1643         fl4.saddr = saddr;
//路由查找路由查找的结果存放在res(results)里。
1644         err = fib_lookup(net, &fl4, &res);
1645         if (err != 0)
1646                 goto no_route;
//根据路由查找结果,创建一个路由缓存项。 
1667         err = ip_mkroute_input(skb, &res, &fl4, in_dev, daddr, saddr, tos);
1668 out:    return err;
1669 
1760 }

1644行路由查找函数

include/net/ip_fib.h
219 static inline int fib_lookup(struct net *net, const struct flowi4 *flp,
220                              struct fib_result *res)
221 {
222         struct fib_table *table;
//获得id等于LOCAL的路由,见12.3节。并查找其中的路由项, 查找过程见后面。
224         table = fib_get_table(net, RT_TABLE_LOCAL);
225         if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
226                 return 0;
//获得id等于MAIN的路由,见12.3节。并查找其中的路由项, 查找过程见后面。
228         table = fib_get_table(net, RT_TABLE_MAIN);
229         if (!fib_table_lookup(table, flp, res, FIB_LOOKUP_NOREF))
230                 return 0;
231         return -ENETUNREACH;
232 }

225行和229行的函数和12.3节提到的大多数函数一样,也位于fib_trie.c函数里。它是路由查找的核心函数,由于这个函数有些部分直接或者间接在12.3节有过叙述,该函数看起来也稍微容易些。这个函数先查找tnode然后查找leaf,查询的结果

1405 int fib_table_lookup(struct fib_table *tb, const struct flowi4 *flp,
1406                      struct fib_result *res, int fib_flags)
1407 {
1408         struct trie *t = (struct trie *) tb->tb_data;
1409         int ret;
1410         struct rt_trie_node *n;
1411         struct tnode *pn;
1412         unsigned int pos, bits;
1413         t_key key = ntohl(flp->daddr);
1414         unsigned int chopped_off;
1415         t_key cindex = 0;
1416         unsigned int current_prefix_length = KEYLENGTH;
1417         struct tnode *cn;
1418         t_key pref_mismatch;
1419 
1420         rcu_read_lock();
1421 
1422         n = rcu_dereference(t->trie);
1423         if (!n)
1424                 goto failed;
//首先查看fib_table指向的是否仅仅是leaf,而没有tnode,对于fib_table只有一个leaf的情况下,直接调用check_leaf进行
//验证
1430         /* Just a leaf? */
1431         if (IS_LEAF(n)) {
1432                 ret = check_leaf(tb, t, (struct leaf *)n, key, flp, res, fib_flags);
1433                 goto found;
1434         }
//对于是tnode的情况的处理
1436         pn = (struct tnode *) n;
//该变量用于记录已经匹配到的比特数。
1437         chopped_off = 0;
1438 
1439         while (pn) {
1440                 pos = pn->pos;
1441                 bits = pn->bits;
1443                 if (!chopped_off)
//找到不同的bit,这是为了获得孩子节点。
1444                         cindex = tkey_extract_bits(mask_pfx(key, current_prefix_length),
1445                                                    pos, bits);
//获得孩子节点,这个孩子节点可能是tnode也可能是leaf
1447                 n = tnode_get_child_rcu(pn, cindex);
//
1449                 if (n == NULL) {
1453                         goto backtrace;
1454                 }
//如果孩子节点是一个leaf节点,则调用check_leaf检查是否是需要的路由项,并将结果存放在res中。
1456                 if (IS_LEAF(n)) {
1457                         ret = check_leaf(tb, t, (struct leaf *)n, key, flp, res, fib_flags);
1458                         if (ret > 0)
1459                                 goto backtrace;
1460                         goto found;
1461                 }
//如果孩子节点是一个tnode,则需要进行迭代到其孩子进行上述查找过程。
1463                 cn = (struct tnode *)n;
//第一次进入该函数这里的current_prefix_length的长度是不会小于pos+bits的。
1494                 if (current_prefix_length < pos+bits) {
1495                         if (tkey_extract_bits(cn->key, current_prefix_length,
1496                                                 cn->pos - current_prefix_length)
1497                             || !(cn->child[0]))
1498                                 goto backtrace;
1499                 }
1532 
1533                 pref_mismatch = mask_pfx(cn->key ^ key, cn->pos);
//当根据pos和bits值没有找到搜索的key的话,进入前缀匹配模式。
/************************************************************************************************
***该模式存在意义如下:
***对于ipv4路由表有如下两项:
***192.168.20.16/28
***192.168.0.0/16
***如果需要查找192.168.20.19则上面两个都是匹配的,但是取哪个好呢?内核使用最常匹配原则,即认为192.168.20.16
***(子网掩码长度是28)这一项是匹配的,通常default 项的前缀是最短的,其作用是在其他路由项均不能匹配时会使用***default项*[摘自维基百科,longest prefix match],这个函数的chopped_off就是忽略prefix的长度,这样匹配成功的概率会***变大。对于图12.2.4的情况的trie树,查找192.168.0.100路由项的情况是不会进入backtrace标号开始的语句的。
**********************************************************************************************/
1540                 if (pref_mismatch) {
1541                         /* fls(x) = __fls(x) + 1 */
1542                         int mp = KEYLENGTH - __fls(pref_mismatch) - 1;
1543 
1544                         if (tkey_extract_bits(cn->key, mp, cn->pos - mp) != 0)
1545                                 goto backtrace;
1546 
1547                         if (current_prefix_length >= cn->pos)
1548                                 current_prefix_length = mp;
1549                 }
1550 
1551                 pn = (struct tnode *)n; /* Descend */
1552                 chopped_off = 0;
1553                 continue;
1554 
1555 backtrace:
1556                 chopped_off++;
1557 
1558                 /* As zero don't change the child key (cindex) */
1559                 while ((chopped_off <= pn->bits)
1560                        && !(cindex & (1<<(chopped_off-1))))
1561                         chopped_off++;
1562 
1563                 /* Decrease current_... with bits chopped off */
1564                 if (current_prefix_length > pn->pos + pn->bits - chopped_off)
1565                         current_prefix_length = pn->pos + pn->bits
1566                                 - chopped_off;
1567 
1568                 /*
1569                  * Either we do the actual chop off according or if we have
1570                  * chopped off all bits in this tnode walk up to our parent.
1571                  */
1572 
1573                 if (chopped_off <= pn->bits) {
1574                         cindex &= ~(1 << (chopped_off-1));
1575                 } else {
1576                         struct tnode *parent = node_parent_rcu((struct rt_trie_node *) pn);
1577                         if (!parent)
1578                                 goto failed;
1579 
1580                         /* Get Child's index */
1581                         cindex = tkey_extract_bits(pn->key, parent->pos, parent->bits);
1582                         pn = parent;
1583                         chopped_off = 0;
1593 found:
1594         rcu_read_unlock();
1595         return ret;
1596 }

回到ip_route_input_slow的1667行,在没有使用多路路由技术的情况下,只是对__mkroute_input()函数的封装。该函数用于为接收到的套接字数据创建路由项缓存。该函数的第二个参数res是前面查找的结果。

net/ipv4/route.c

1471 static int __mkroute_input(struct sk_buff *skb,
1472                            const struct fib_result *res,
1473                            struct in_device *in_dev,
1474                            __be32 daddr, __be32 saddr, u32 tos)
1475 {
1476         struct rtable *rth;
1477         int err;
1478         struct in_device *out_dev;
1479         unsigned int flags = 0;
1480         bool do_cache;
1481         u32 itag;
1482 
//该函数给了数据包的源地址、输入Interface以及目的地址、oif、tos;检查源地址的正确性,例如不能是广播地址和local地
//址,	
1490         err = fib_validate_source(skb, saddr, daddr, tos, FIB_RES_OIF(*res),
1491                                   in_dev->dev, in_dev, &itag);
1492         if (err < 0) {
1493                 ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr,
1494                                          saddr);
1495 
1496                 goto cleanup;
1497         }
1498 
//创建一个dst_entry入口项,并将其赋值给rth,rtable的第一个字段就是指向dst_entry。该函数还对dst_entry进行了初始化。
1530         rth = rt_dst_alloc(out_dev->dev,
1531                            IN_DEV_CONF_GET(in_dev, NOPOLICY),
1532                            IN_DEV_CONF_GET(out_dev, NOXFRM), do_cache);
1533         if (!rth) {
1534                 err = -ENOBUFS;
1535                 goto cleanup;
1536         }
//rtable相关字段初始化
1538         rth->rt_genid = rt_genid(dev_net(rth->dst.dev));
1539         rth->rt_flags = flags;
1540         rth->rt_type = res->type;
1541         rth->rt_is_input = 1;
1542         rth->rt_iif     = 0;
1543         rth->rt_pmtu    = 0;
1544         rth->rt_gateway = 0;
1545         rth->rt_uses_gateway = 0;
1546         INIT_LIST_HEAD(&rth->rt_uncached);
1547 
1548         rth->dst.input = ip_forward;
1549         rth->dst.output = ip_output;
//rtable的最后一项是nexthop,这里设置rth的nexthop项,并设置路由缓存。
1551         rt_set_nexthop(rth, daddr, res, NULL, res->fi, res->type, itag);
//将rtable的dst_entry入口项设置成skb的路由项。4.2节的路由内容至此结束。
1552         skb_dst_set(skb, &rth->dst);
1553 out:
1554         err = 0;
1555  cleanup:
1556         return err;
1557 }
  • 11
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shichaog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值