内核路由表设置
一、内核中路由表
在ip_rt_init函数中,初始化了一个缓冲结构:
if (!rt_hash_table)
panic("Failed to allocate IP route cache hash table\n");
注意:这个并不是内核中我们创建的静态路由表,这个静态路由表是我们通过 ip route add添加的,这个添加的内容是添加到fib_init中创建的fib表,这个表是一个静态的概念,也就是用户设置或者某些可靠途径设置的路由表类型。这些路由表是正规军,也就是这些路由表是查找路由的框架性通配符,拿makefile中的例子,这些是一些通配符规则,但是某些精确的内容则是特定规则。
二、网卡设置对路由的影响
void __init ip_fib_init(void)
{
#ifdef CONFIG_PROC_FS
proc_net_create("route",0,fib_get_procinfo);
#endif /* CONFIG_PROC_FS */
#ifndef CONFIG_IP_MULTIPLE_TABLES
local_table = fib_hash_init(RT_TABLE_LOCAL);
main_table = fib_hash_init(RT_TABLE_MAIN);
#else
fib_rules_init();
#endif
register_netdevice_notifier(&fib_netdev_notifier);
register_inetaddr_notifier(&fib_inetaddr_notifier);
}
注意这里创建的路由表并不是rt_hash_table,而是local_table和main_table,后两者是真正的内核静态和backbone路由表,通过cat /prot/net/route文件可以看到系统中的静态路由表配置信息。
这里还有两个重要的通知链接口,其中就是在添加或者删除一个网卡的时候,此时将会添加一个路由,这个路由就是我们最为常见的 <网卡地址 + 网卡掩码> 的一个路由表,这个是一个默认路由表,也是内核中一个比较可靠、自动的路由表。
三、rt_cache的由来
当我们发送或者接受一个报文的时候都可以在其中添加新的路由表类型。当我们向一个特定的IP发送了报文的时候,就可以将这个查找的过程省略。也就是我们这里cache了路由的查找过程。也就是对于特定的确定目的IP,它对应的网口信息是确定的,也就是我们可以确定这个报文应该走哪个网口。
另一方面,当我们从某个网口接受到一个报文的时候,我们也可以从这个接受的报文中获得一个有用的信息,者也就是一个学习的过程。这是一个我们容易忽略的问题,就是从接受的报文中学习到路由信息,这个路由包含了源IP地址以及其MAC地址。
通过 cat /proc/net/rt_cache可以看到,其中缓冲的大部分都是确定地址的一个发送规则,并且这个变化比较快。包括我们没有发送成功的报文,在其中都有缓冲,这样也是有好处的,因为这样可以说明某个网络地址是不可达的,并且及时我们再次查询一下静态路由表,这个查找的结果应该还是不会变化的。
四、从IP层到MAC层
ip_queue_xmit--->>>ip_queue_xmit2--->>> return skb->dst->output(skb);
在这个函数中完成一次输出
ip_finish_output2--->>>dst->neighbour->output(skb)
一个neighbour的分配在arp_bind_neighbour函数中完成,
rt_intern_hash-->>
其中的这个rt_inern_hash就是我们刚才说的在学习到一个mac报文或者是在从静态路由表中找到一个目的地址的路由之后,在这个接口完成内部的一些hash,同时也分配了一个neighbour接口,通过这个接口可以摘掉系统中的所有的邻接系统。
在arp_constructor函数中,对于这个neighbour的output的设置为:
if (neigh->nud_state&NUD_VALID)
neigh->output = neigh->ops->connected_output;
else
neigh->output = neigh->ops->output;
由于generic的设置为
static struct neigh_ops arp_generic_ops = {
family: AF_INET,
solicit: arp_solicit,
error_report: arp_error_report,
output: neigh_resolve_output,
connected_output: neigh_connected_output,
hh_output: dev_queue_xmit,
queue_xmit: dev_queue_xmit,
};
所以这里就是执行neighbour_resolve_output函数完成的真正的输出。
五、路由表的查询
正如作者所有,这里的ip_route_output_slow是主要的路由表操作,正是这个函数完成了路由表的查询
/*
* Major route resolver routine.
*/
int ip_route_output_slow(struct rtable **rp, const struct rt_key *oldkey)
在该函数中,使用的主要操作就是在fib_lookup
if (fib_lookup(&key, &res)) {
对于简单的查找,一般是通过
fn_hash_lookup(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
函数完成路由的查找,这个函数其实也比较简单,就是按照路由中最有的配置来完成。举例来说,当我们路由表配置的是
192.254.0.0 255.255.0.0. eth0
192.254.100,0 255.255.255.0 eth1
这样的话,当我们遇到一个
192.254.100.200
的时候,明显地,我们应该选择eth1,。或者简单的说,我们应该寻则掩码最多的哪一个路由表,从而匹配度更加的高。
在fib_hash.c的实现中,其中的确有一个zone的概念,总共分了32个zone。所有的路由按照掩码来进行划分,然后在lookup的时候从其中进行匹配。
在这个结构中完成了fz_next和fn_next两个链表,其中的fz链表是一个以zone为单位的链表,而接下来的fn_next则是以node为单位进行的节点连接。
这个表为什么没有按照循环查找,我想是一个简单的优化。因为可能很多的zone可能都是空的,所以循环是通过
fn_hash_lookup(struct fib_table *tb, const struct rt_key *key, struct fib_result *res)
{
int err;
struct fn_zone *fz;
struct fn_hash *t = (struct fn_hash*)tb->tb_data;
read_lock(&fib_hash_lock);
for (fz = t->fn_zone_list; fz; fz = fz->fz_next) {
struct fib_node *f;
fn_key_t k = fz_key(key->dst, fz);
来完成的。
另一方面,由于key(也就是目的地址)是一个32bits的整数,而zone的长度是32,或者说zone的下表是该路由表项中掩码的长度。
其中一个表格中,这一点在fn_hash_insert函数中可以看到:
fz = table->fn_zones[z];
其中同一个fn_zone中的路由表项具有相同的路由掩码长度。
在
static struct fn_zone *
fn_new_zone(struct fn_hash *table, int z)
函数中,新添加的zone之间同样是按照掩码从长到短的顺序通过fz_next连接在一起的,因为下面的for循环是从z+1开始,然后将新的表项插入到该项的后面。
/* Find the first not empty zone with more specific mask */
for (i=z+1; i<=32; i++)
if (table->fn_zones[i])
break;
write_lock_bh(&fib_hash_lock);
if (i>32) {
/* No more specific masks, we are the first. */
fz->fz_next = table->fn_zone_list;
table->fn_zone_list = fz;
} else {
fz->fz_next = table->fn_zones[i]->fz_next;
table->fn_zones[i]->fz_next = fz;
}
table->fn_zones[z] = fz;