数据结构
路由函数操作表
struct fib_table {
struct hlist_node tb_hlist; //用来将各个路由表连接成一个双向链表
u32 tb_id; //路由标识,最多可以有256个路由表(静态路由、策略路由等等表项)
unsigned tb_stamp;
int tb_default; //路由信息结构的队列序号
int (*tb_lookup)(struct fib_table *tb, const struct flowi *flp, struct fib_result *res); //搜索路由表项
int (*tb_insert)(struct fib_table *, struct fib_config *); //插入给定的路由表项
int (*tb_delete)(struct fib_table *, struct fib_config *); //删除给定的路由表项
int (*tb_dump)(struct fib_table *table, struct sk_buff *skb, //dump出路由表的内容
struct netlink_callback *cb);
int (*tb_flush)(struct fib_table *table); //刷新路由表项,并删除带有RTNH_F_DEAD标志的fib_node
void (*tb_select_default)(struct fib_table *table, //选择一条默认的路由
const struct flowi *flp, struct fib_result *res);
unsigned char tb_data[0]; //路由表项的散列表的起始地址,指向fn_hash
};
struct fn_hash {
struct fn_zone *fn_zones[33];
struct fn_zone *fn_zone_list; //fn_zone链表
};
路由区
struct fn_zone {
struct fn_zone *fz_next; /* Next not empty zone 将不为空的路由表项fn_zone链接在一起,该链表头存储在fn_hash的fn_zone_list中 */
struct hlist_head *fz_hash; /* Hash table pointer 指向存储路由表项fib_node的散列表 */
int fz_nent; /* Number of entries 在zone的散列表中的fib_node的数目,用于判断是否需要改变散列表的容量 */
int fz_divisor; /* Hash divisor 散列表fz_hash的容量,即散列表桶的数目每次扩大2倍,最大1024 */
u32 fz_hashmask; /* (fz_divisor - 1) */
#define FZ_HASHMASK(fz) ((fz)->fz_hashmask)
int fz_order; /* Zone order 掩码fz_mask的长度 */
__be32 fz_mask; //利用fz_order构造得到的网络掩码
#define FZ_MASK(fz) ((fz)->fz_mask)
};
路由节点
struct fib_node {
struct hlist_node fn_hash; //用于散列表中同一桶内的所有fib_node链接成一个双向链表
struct list_head fn_alias; //指向多个fib_alias结构组成的链表
__be32 fn_key; //由IP和路由项的netmask与操作后得到,被用作查找路由表的搜索条件
struct fib_alias fn_embedded_alias; //内嵌的fib_alias结构,一般指向最后一个fib_alias
};
路由别名
struct fib_alias {
struct list_head fa_list; //将所有fib_alias组成的链表
struct fib_info *fa_info; //指向fib_info,储存如何处理路由信息
u8 fa_tos; //路由的服务类型比特位字段
u8 fa_type; //路由表项的类型,间接定义了当路由查找匹配时,应采取的动作
u8 fa_scope; //路由表项的作用范围
u8 fa_state; //一些标志位,目前只有FA_S_ACCESSED。表示该表项已经被访问过。
#ifdef CONFIG_IP_FIB_TRIE
struct rcu_head rcu;
#endif
};
路由信息结构
struct fib_info {
struct hlist_node fib_hash; //所有fib_info组成的散列表,该表为全局散列表fib_info_hash
struct hlist_node fib_lhash; //当存在首源地址时,才会将fib_info插入该散列表,该表为全局散列表fib_info_laddrhash
struct net *fib_net;
int fib_treeref; //使用该fib_info结构的fib_node的数目
atomic_t fib_clntref; //引用计数,路由查找成功而被持有的引用计数
int fib_dead; //标记路由表项正在被删除的标志,当该标志被设置为1时,警告该数据结构将被删除而不能再使用
unsigned fib_flags; //当前使用的唯一标志是RTNH_F_DEAD,表示下一跳已无效
int fib_protocol; //设置路由的协议
__be32 fib_prefsrc; //首选源IP地址
u32 fib_priority; //路由优先级,默认为0,值越小优先级越高
u32 fib_metrics[RTAX_MAX]; //与路由相关的度量值
#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; //可用的下一跳数量,通常为1.只有支持多路径路由时,才大于1
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int fib_power;
#endif
struct fib_nh fib_nh[0]; //表示路由的下一跳
#define fib_dev fib_nh[0].nh_dev
};
路由跳转结构
struct fib_nh {
struct net_device *nh_dev; //该路由表项输出网络设备
struct hlist_node nh_hash; //fib_nh组成的散列表
struct fib_info *nh_parent; //指向所属fib_info结构体
unsigned nh_flags;
unsigned char nh_scope;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int nh_weight;
int nh_power;
#endif
#ifdef CONFIG_NET_CLS_ROUTE
__u32 nh_tclassid;
#endif
int nh_oif; //输出网络设备索引
__be32 nh_gw; //网关地址
};
路由函数表的初始化过程
介绍路由函数表初始化与从中找出路由函数表的过程,我们看inet_init()
static int __init inet_init(void)
{
struct sk_buff *dummy_skb;
struct inet_protosw *q;
struct list_head *r;
int rc = -EINVAL;
...
(void)sock_register(&inet_family_ops);
...
for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
inet_register_protosw(q);
/*
* Set the ARP module up
*/
arp_init();
/*
* Set the IP module up
*/
ip_init();
...
}
我们关心其内部调用的ip_init()函数
void __init ip_init(void)
{
ip_rt_init();
inet_initpeers();
#if defined(CONFIG_IP_MULTICAST) && defined(CONFIG_PROC_FS)
igmp_mc_proc_init();
#endif
}
进一步调用了ip_rt_init(),此函数实现了路由函数表初始化功能。
int __init ip_rt_init(void)
{
int rc = 0;
atomic_set(&rt_genid, (int) ((num_physpages ^ (num_physpages>>8)) ^
(jiffies ^ (jiffies >> 7)))); //设置路由随机数
#ifdef CONFIG_NET_CLS_ROUTE
ip_rt_acct = __alloc_percpu(256 * sizeof(struct ip_rt_acct));
if (!ip_rt_acct)
panic("IP: failed to allocate ip_rt_acct\n");
#endif
ipv4_dst_ops.kmem_cachep =
kmem_cache_create("ip_dst_cache", sizeof(struct rtable), 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); //创建路由项的高速缓存,对象长度为路由表的长度 rtable
ipv4_dst_blackhole_ops.kmem_cachep = ipv4_dst_ops.kmem_cachep;
rt_hash_table = (struct rt_hash_bucket *)
alloc_large_system_hash("IP route cache",
sizeof(struct rt_hash_bucket),
rhash_entries,
(num_physpages >= 128 * 1024) ?
15 : 17,
0,
&rt_hash_log,
&rt_hash_mask,
0); //创建路由哈希桶缓存
memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket));
rt_hash_lock_init(); //初始化路由哈希队列
ipv4_dst_ops.gc_thresh = (rt_hash_mask + 1); //记录回收阈值
ip_rt_max_size = (rt_hash_mask + 1) * 16; //哈希桶的最大长度
devinet_init();
ip_fib_init();
rt_secret_timer.function = rt_secret_rebuild;
rt_secret_timer.data = 0;
init_timer_deferrable(&rt_secret_timer);
/* All the timers, started at system startup tend
to synchronize. Perturb it a bit.
*/
schedule_delayed_work(&expires_work,
net_random() % ip_rt_gc_interval + ip_rt_gc_interval);
rt_secret_timer.expires = jiffies + net_random() % ip_rt_secret_interval +
ip_rt_secret_interval;
add_timer(&rt_secret_timer);
if (ip_rt_proc_init())
printk(KERN_ERR "Unable to create route proc files\n");
#ifdef CONFIG_XFRM
xfrm_init();
xfrm4_init();
#endif
rtnl_register(PF_INET, RTM_GETROUTE, inet_rtm_getroute, NULL);
return rc;
}
首先通过宏atomic_set以原子操作方式设置原子变量rt_genid为随机数,该随机数在路由缓存生成hash关键字时做为一个参数使用,目的是为了防止DDOS攻击,该随机值后期在每次缓存刷新时也会重新生成。__alloc_percpu函数也是同步操作,这里申请了连续的256个ip_rt_acct空间
struct ip_rt_acct //用于路由参数统计
{
__u32 o_bytes; //发出的字节
__u32 o_packets; //发出的包数
__u32 i_bytes; //收到的字节
__u32 i_packets; //收到的包数
};
然后创建 ipv4_dst_ops 的slab高速缓存,ipv4_dst_ops是一个全局dst_opt结构变量,创建的高速缓存赋值到kmem_cache成员上,名为ip_dst_cache(用于路由表rtable结构)。
static struct dst_ops ipv4_dst_ops = {
.family = AF_INET,
.protocol = __constant_htons(ETH_P_IP),
.gc = rt_garbage_collect,
.check = ipv4_dst_check,
.destroy = ipv4_dst_destroy,
.ifdown = ipv4_dst_ifdown,
.negative_advice = ipv4_negative_advice,
.link_failure = ipv4_link_failure,
.update_pmtu = ip_rt_update_pmtu,
.local_out = __ip_local_out,
.entry_size = sizeof(struct rtable),
.entries = ATOMIC_INIT(0),
};
关于struct dst_ops结构其实属于协议无关的缓存间的接口,也指定了一些事件的协议通知(如链接出错情况等),这里IPV4的dst_ops实现为ipv4_dst_ops。
然后将ipv4_dst_blackhole_ops(路由项“黑洞”的处理函数表)的kmem_cachep也指向ipv4_dst_ops 的路由表slab高速缓存[TODO 什么作用?]。
然后对rt_hash_table进行初始化
static struct rt_hash_bucket *rt_hash_table __read_mostly;
struct rt_hash_bucket { //路由哈希队列
struct rtable *chain;//路由表结构队列
};
struct rtable
{
union
{
struct dst_entry dst;
} u;
/* Cache lookup keys */
struct flowi fl;
struct in_device *idev;
int rt_genid;
unsigned rt_flags;
__u16 rt_type;
__be32 rt_dst; /* Path destination */
__be32 rt_src; /* Path source */
int rt_iif;
/* Info on neighbour */
__be32 rt_gateway;
/* Miscellaneous cached information */
__be32 rt_spec_dst; /* RFC1122 specific destination */
struct inet_peer *peer; /* long-living peer info */
};
struct dst_entry //路由项的定义
{
......
union {
struct dst_entry *next;
struct rtable *rt_next;
struct rt6_info *rt6_next;
struct dn_route *dn_next;
};
};
rt_hash_table专门用于路由表队列,内部包含结构队列指针 chain。rtable是路由表结构体,其开始处有个联合体,内部声明了一个路由项结构变量dst,dst_entry结构的最后也有一个联合体,其内部通过next指针将路由项结构链成了队列,rtable则通过rt_next链成路由表队列。
[TODO rtable结构图]
这里解释一下 fib_table 与rtable 区别,rtable是数据包使用到的结构,fib_table及相关的结构都是搭建路由表rtable的基础,他们为路由表提供具体的路由信息。
继续ip_rt_init函数,通过alloc_large_system_hash函数分配一个rt_hash_bucket的路由表队列空间,rt_hash_table指向其。
alloc_large_system_hash函数从 bootmem 空间分配大量的路由表空间,bootmem空间是内核启动时使用的内存管理策略,主要指内核使用的空间,其意图是从这部分内存中按页进行分配,这部分的页面是不会被linux交换或者回收的,一旦分配就不再改变了。然后通过memset(rt_hash_table, 0, (rt_hash_mask + 1) * sizeof(struct rt_hash_bucket))初始化。其中rt_hash_mask代表分配的路由哈希桶的数量减1。
然后hash锁初始化,再使ipv4_dst_ops记录下可以回收的碎片数gc_thresh(进行强制路由缓存垃圾回收的阀值,为路由缓存hash桶的个数),ip_rt_max_size记录下允许最大的路由缓存条目数,为路由缓存hash桶的16倍。
然后通过devinet_init()函数完成一些注册工作
void __init devinet_init(void)
{
register_pernet_subsys(&devinet_ops);
register_gifconf(PF_INET, inet_gifconf); //注册IO配置程序
register_netdevice_notifier(&ip_netdev_notifier); //注册inet_dev_even通知节点
/* 注册处理路由地址的 netlink */
rtnl_register(PF_INET, RTM_NEWADDR, inet_rtm_newaddr, NULL);
rtnl_register(PF_INET, RTM_DELADDR, inet_rtm_deladdr, NULL);
rtnl_register(PF_INET, RTM_GETADDR, NULL, inet_dump_ifaddr);
}
可以看到通过register_pernet_subsys函数向内核注册了一个pernet_operations结构,即网络空间操作表,这里登记的是devinet_ops
static __net_initdata struct pernet_operations devinet_ops = {
.init = devinet_init_net,
.exit = devinet_exit_net,
};
提供了初始化网络空间、释放网络空间的钩子函数。
int register_pernet_subsys(struct pernet_operations *ops)
{
int error;
mutex_lock(&net_mutex);
error = register_pernet_operations(first_device, ops);
mutex_unlock(&net_mutex);
return error;
}
#ifdef CONFIG_NET_NS //在内核中CONFIG_NET_NS配置选项是为了让用户自定义自己的网络空间结构
static int register_pernet_operations(struct list_head *list,
struct pernet_operations *ops)
{
struct net *net, *undo_net;
int error;
list_add_tail(&ops->list, list);
if (ops->init) {
for_each_net(net) {
error = ops->init(net);
if (error)
goto out_undo;
}
}
return 0;
out_undo:
/* If I have an error cleanup all namespaces I initialized */
list_del(&ops->list);
if (ops->exit) {
for_each_net(undo_net) {
if (undo_net == net)
goto undone;
ops->exit(undo_net);
}
}
undone:
return error;
}
#else
static int register_pernet_operations(struct list_head *list,
struct pernet_operations *ops)
{
if (ops->init == NULL)
return 0;
return ops->init(&init_net);
}
#endif
CONFIG_NET_NS
register_gifconf()函数向内核注册了一个SIOCGIF处理程序(socket IO Config Interface),gif指generous interface configure(通用接口配置),这里注册了inet_gifconf
int register_gifconf(unsigned int family, gifconf_func_t * gifconf)
{
if (family >= NPROTO)
return -EINVAL;
gifconf_list[family] = gifconf;
return 0;
}
register_netdevice_