下载地址《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 }
现在内核采用的是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
在/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
前面已经见到过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地址。
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.c1172 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.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。
路由这章的介绍和前面的章节分开,从工具说起,在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.2和12.3节,主要关注的是trie树的构建和管理,并不涉及路由查找的内容,由于管理和维护一个trie树本身是一件复杂的事,所以内核没有将查找路由表需要的信息也放在trie树中存储,而是使用了其它的一些数据结构来辅助查找过程,这节先引入这些数据结构,然后剖析查找过程,查找的总体思想是首先检查参数的合法性,查找trie(tnode ,leaf)树,trie树本身存储的是key值,没有其它 过多的信息,这些信息包括tos,也即流控,所以需要查找一些辅助数据结构,查找到了以后,如果对应的路由项没有缓存,会创建一个路由缓存。
本节所述的数据结构指的是辅助数据结构,并没有将使用到的trie树相关数据结构罗列出来。关于这几个数据结构间的关系见图12.3.3。
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链表指针。
};
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分别对应于三种路由项情况。
虽然套接字数据包路由项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;
};
该数据结构在图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)));
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;
};
套接字将会绑定该结构体。
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;
};
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;
};
};
在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 }