一 概述
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 char tb_id; // 标识符(例如:本地路由,主路由,默认路由);
unsigned tb_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 char tb_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长度);*/
int fz_nent; /* Number of entries,包含的路由节总数 */
int fz_divisor; /* Hash divisor,hash头数量(上面说了) */
u32 fz_hashmask; /* (1<<fz_divisor) - 1,确定hash头的掩码 */
#define FZ_HASHMASK(fz) ((fz)->fz_hashmask)
int fz_order; /* Zone order,子网掩码位数 */
u32 fz_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;
int fib_treeref; // 路由信息结构使用计数器
atomic_t fib_clntref; // 释放路由信息结构(fib)计数器
int fib_dead; // 标志路由被删除了
unsigned fib_flags; // 标识位
int fib_protocol; // 安装路由协议
u32 fib_prefsrc; // 指定源IP,源地址和目的地址组成一个路由
u32 fib_priority; // 路由优先级
unsigned fib_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值(对外公开的)
int fib_nhs; // 倒数第二个字段即:跳转结构的数组个数
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int fib_power; // 支持多路径时候使用
#endif
struct fib_nh fib_nh[0]; // 跳转结构(就是该怎么路由)
#define fib_dev fib_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。
流程说明:(1)rt_hash_table是在inet_init()模块初始化的时候调用ip_rt_init创建的路由缓存,之后通过rt_intern_hash函数插入条目rt,以供路由查找的时候进行查找;
2.2 路由表
流程说明:(1)在ip_fib_init()函数初始化的时候,为路由表分配空间;并且声明路由的插入、删除、转发函数;
(2)向路由表中插入一条新的路由是通过fn_hash_insert函数实现的,事实上,调用此函数的还有处理路由的尾部追加(append)、首部追加(prepend)、改变(change)和替换(replace)操作,这些不同的操作是由传入的NLM_F_XXX标识参数来区分的。路由的删除由fn_hash_delete完成,删除路由要比添加路由简单,因为删除路由只有一种操作;