linux内核路由转发表的组成,Linux内核网络协议栈TCP/IP分析:ip route路由表

一 概述

1.1 概念

(1)路由:跨越从源主机到目标主机的一个互联网络来转发数据包的过程;

(2)路由器:能够将数据包转发到正确的目的地,并在转发过程中选择最佳路径的设备;

(3)路由表:在路由器中维护的路由条目,路由器根据路由表做路径选择;

(4)静态路由:是由管理员手工配置的,是单向的;

(5)默认路由:当路由器在路由表中找不到目标网络的路由条目时,路由器把请求转发到默认路由接口 。

1.2 路由表

Linux系统可以同时存在256(0-255)个路由表,而且每个路由表都各自独立,互不相关。数据包在传输时是根据RPDB(路由策略数据库)内的策略决定数据包应该用哪个路由表传输的。

在默认情况下,系统有三个路由表,这三个路由表的功能如下:

(1)local:路由表local包含本机路由及广播信息。例如,在本机上执行ssh 127.0.0.1时,就会参考这份路由表的内容,在正常情况下,只要配置好网卡的网络设置,就会自动生成local路由表的内容,我们应该也不必修改其内容。

(2)main:使用传统命令route -n所看到的路由表就是main的内容。Linux系统在默认情况下使用这份路由表的内容来传输数据包,因此,其内容极为重要,在正常情况下,只要配置好网卡的网络设置,就会自动生成main路由表的内容。

(3)default:最后是default路由表,这个路由表在默认情况下内容为空;除非有特别的要求,否则保持其内容为空即可。

1.3 路由类型

(1)主机路由

主机路由是路由选择表中指向单个IP地址或主机名的路由记录。主机路由的Flags字段为H。

(2)网络路由

网络路由是代表主机可以到达的网络。网络路由的Flags字段为N。

(3)默认路由

当主机不能在路由表中查找到目标主机的IP地址或网络路由时,数据包就被发送到默认路由(默认网关)上。默认路由的Flags字段为G。

二 Linux路由表

在Linux内核中存在路由表fib_table和路由缓存表rt_hash。

路由缓存表主要是为了加速路由的查找,每次路由查询都会先查找路由缓存,再查找路由表。

在Linux 2.4.9内核中默认的路由查找算法使用的是Hash查找。

Linux默认有三种策略路由:本地路由,主路由和默认路由,那么与之对应的就是三张路由表:本地路由表,主路由表和默认路由表。

2.1 相关结构体

(1)路由表是由fib_table结构来描述的,该结构是通过函数fib_hash_table()来赋值的,fib_table结构中的tb_data,是一个零长数组,该地址指向fn_hash结构体;

struct fib_table

{

unsigned chartb_id; // 标识符(例如:本地路由,主路由,默认路由);

unsignedtb_stamp; // 时间戳

int(*tb_lookup)(struct fib_table *tb, const struct rt_key *key, struct fib_result *res);// 查找函数

int(*tb_insert)(struct fib_table *table, struct rtmsg *r,

struct kern_rta *rta, struct nlmsghdr *n,

struct netlink_skb_parms *req);// 插入函数

int(*tb_delete)(struct fib_table *table, struct rtmsg *r,

struct kern_rta *rta, struct nlmsghdr *n,

struct netlink_skb_parms *req);// 删除路由函数

int(*tb_dump)(struct fib_table *table, struct sk_buff *skb,

struct netlink_callback *cb); // 用于路由转发

int(*tb_flush)(struct fib_table *table);// 移除路由信息结构

int(*tb_get_info)(struct fib_table *table, char *buf,

int first, int count);

void(*tb_select_default)(struct fib_table *table,

const struct rt_key *key, struct fib_result *res);// 设置默认路由

unsigned chartb_data[0];// 注意这个特殊字段,标识结构的结尾,分配fib_table同时分配fn_hash结构;

// 也就是fib_table之后就是fn_hash结构

};

(2)fn_hash包含fn_zone[33]和fn_zone_list,其中fn_zone[33]是由33个fn_zone结构指针构成的向量,与fn_zone_list构成了循环单链表;fz_hash是长度为fz_divisor的HASH表,HASH表中存放的是不同子网的fib_node节点。

// 路由区结构体的数组( 包含所有的额路由区的情况 )

struct fn_hash {

struct fn_zone*fn_zones[33]; // 路由区分成33份,子网掩码长度是1~32,0长度掩码代表网关,那么加起来就是33,即:fn_zone[0]的掩码是0.0.0.0,fn_zone[1]是10000000.00000000.00000000.0000000这一类 等等;

struct fn_zone*fn_zone_list; // 指向第一个活动的路由区

};

(3)fn_zone代表同一掩码长度表项的集合,路由区:fn_zone,子网掩码长度相同的认为是相同的路由区;

// 路由区结构体(所有的子网长度相等的被分在同一个路由区)

struct fn_zone

{

struct fn_zone*fz_next;/* Next not empty zone,指向下一个不为空的路由区结构,那么所有的路由区就能链接起来*/

struct fib_node**fz_hash;/* Hash table pointer,有一个hash数组,用来hash得到一个hlist_head,是很多的fib_node通过自己的字段连接在这个队列中,那么通过这个fz_hahs字段可以找到fib_node所在的队列的头hlist_head,进而找到对应的fib_node ( 注意:上面说的hash数组的长度是fz_divisor长度);*/

intfz_nent;/* Number of entries,包含的路由节总数*/

intfz_divisor;/* Hash divisor,hash头数量(上面说了)*/

u32fz_hashmask;/* (1<

#define FZ_HASHMASK(fz)((fz)->fz_hashmask)

intfz_order;/* Zone order,子网掩码位数*/

u32fz_mask; /*子网掩码*/

#define FZ_MASK(fz)((fz)->fz_mask) /*获取子网掩码的宏定义*/

};

(4)fib_info存储真正重要路由信息,即如何到达目的地。

/*

* This structure contains data shared by many of routes.

*/

// 具体怎么路由这个数据包的信息

struct fib_info

{

struct fib_info*fib_next;

struct fib_info*fib_prev;

intfib_treeref; // 路由信息结构使用计数器

atomic_tfib_clntref; // 释放路由信息结构(fib)计数器

intfib_dead; // 标志路由被删除了

unsignedfib_flags; // 标识位

intfib_protocol; // 安装路由协议

u32fib_prefsrc; // 指定源IP,源地址和目的地址组成一个路由

u32fib_priority; // 路由优先级

unsignedfib_metrics[RTAX_MAX]; // 保存负载值(例如MTU,MSS)

#define fib_mtu fib_metrics[RTAX_MTU-1] // MTU值

#define fib_window fib_metrics[RTAX_WINDOW-1] // 窗口值

#define fib_rtt fib_metrics[RTAX_RTT-1] // RTT值

#define fib_advmss fib_metrics[RTAX_ADVMSS-1] // MSS值(对外公开的)

intfib_nhs; // 倒数第二个字段即:跳转结构的数组个数

#ifdef CONFIG_IP_ROUTE_MULTIPATH

intfib_power; // 支持多路径时候使用

#endif

struct fib_nhfib_nh[0]; // 跳转结构(就是该怎么路由)

#define fib_devfib_nh[0].nh_dev //fib_nh[0],这样的操作手法在内核中也是常见的。代表会有这个字段的存在,但是具体是几个并不知道,因为可能是动态的,所以需要一个计数表示,也就是fib_power;

};

三 路由查找

进入Linux内核数据包的路由是通过函数ip_route_input来处理的:

/*

*函数名:ip_route_input

*函数功能:在处理从网络上进来的IP包时调用的路由函数,

*它的结果主要有两个:即如果是本地包则传给上层协议层,

*如果不是则选则一个出端口再发送出去。

*函数参数:skb--表示ip包的缓冲区;

*dst--目的地址;

*src--源地址;

*tos--IP包服务类型;

*dev--入端口。

*函数返回值:函数返回值指示错误,如果成功查到路由,

*函数返回后,skb->dst会被赋值。

*

*/

int ip_route_input(struct sk_buff *skb, u32 daddr, u32 saddr,

u8 tos, struct net_device *dev);

(1)首先这个函数需要查路由缓存(cache),如果找到了那么它给skb->dst赋值并返回,如是没找到,它会调用ip_route_input_slow去查询路由数据库。

(2)路由查找就是最终找到这个路由条目,得到目的地址(下一跳),然后赋值给skb->dst,然后通过skb->dst->input(skb)就可以进行操作。第三需要注意,这里的操作分成两类:第一类是投到本地,即数据是发到本机的,那么调用ip_local_deliver将数据包发送给上一层进行处理;第二类是转发,调用ip_forward函数进行处理,转发出去。最后注意:当路由缓冲找不到所需要的路由项,那么最终需要再次到fib中去查找,也就是完整的一个查找过程。

2.1 路由缓存

路由缓存用于加速路由的查找,当收到报文或发送报文时,首先会查询路由缓存,在内核中被组织成hash表,就是rt_hash_table。

d121b44c74e7c259ebbc872038264140.png

流程说明:(1)rt_hash_table是在inet_init()模块初始化的时候调用ip_rt_init创建的路由缓存,之后通过rt_intern_hash函数插入条目rt,以供路由查找的时候进行查找;

2.2 路由表

255cea39d0829944be86fbd684bb0555.png

流程说明:(1)在ip_fib_init()函数初始化的时候,为路由表分配空间;并且声明路由的插入、删除、转发函数;

(2)向路由表中插入一条新的路由是通过fn_hash_insert函数实现的,事实上,调用此函数的还有处理路由的尾部追加(append)、首部追加(prepend)、改变(change)和替换(replace)操作,这些不同的操作是由传入的NLM_F_XXX标识参数来区分的。路由的删除由fn_hash_delete完成,删除路由要比添加路由简单,因为删除路由只有一种操作;