以下代码取自 kernel 2.6..
[数据结构]
该结构被基于路由表的classifier使用,用于跟踪与一个标签(tag)相关联的路由流量的统计信息,该统计信息中包含字节数和报文数两类信息。
这个结构包含一个counters数组,每个处理器有256个元素。*大小为256是因为路由标签的取值范围为0到255。IPv4中是由ip_rt_init接
口为该向量分配空间,IPv6中没有为该向量分配空间。ip_rt_acct结构中的四个字段是在ip_rcv_finish接口中更新。
struct ip_rt_acct {
}; 路由表hash项
struct rt_hash_bucket {
struct rtable *chain;
};
对每个路由表实例创建一个fib_table结构,这个结构主要由一个路由表标识和管理该路由表的一组函数指针组成:
struct fib_table {
struct hlist_node tb_hlist;
u32 tb_id; //路由表标识
unsigned tb_stamp; //未被使用
//函数被fib_lookup函数调用
int (*tb_lookup)(struct fib_table *tb, const struct flowi *flp, struct fib_result *res);
//tb_insert被inet_rtm_newroute和ip_rt_ioctl调用,处理用户空间的ip route add/change/replace/prepend/append/test 命令和 route add 命令。
//类似地,tb_delete被inet_rtm_delroute(对ip route del ... 命令作出的响应)和ip_rt_ioctl(对route del ... 命令作出的响应)调用,
//用于从路由表中删除一条路由。这两个函数指针也被fib_magic调用。
int (*tb_insert)(struct fib_table *, struct fib_config *);
int (*tb_delete)(struct fib_table *, struct fib_config *);
//Dump出路由表的内容。在处理诸如“ip route get...”等用户命令时被激活。
int (*tb_dump)(struct fib_table *table, struct sk_buff *skb, struct netlink_callback *cb);
//将设置有RTNH_F_DEAD标志的fib_info结构删除
int (*tb_flush)(struct fib_table *table);
//选择一条缺省路由
void (*tb_select_default)(struct fib_table *table, const struct flowi *flp, struct fib_result *res);
//指向该结构的尾部
unsigned char tb_data[];
};
一个zone是一组有着相同网络掩码长度的路由项。路由表中的路由项按照zone来组织.
struct fn_zone {
//将活动zones(active zones)(即至少有一条路由项的zones)链接在一起的指针。
//该链表的头部由fn_zone_list来跟踪,fn_zone_list是fn_hash数据结构的一个字段。
struct fn_zone *fz_next; /* Next not empty zone */
//指向存储该zone中路由项的哈希表
struct hlist_head *fz_hash; /* Hash table pointer */
//在该zone中路由项的数目(即在该zone的哈希表中fib_node实例的数目)。
//这个值可以用于检查是否需要改变该哈希表的容量
int fz_nent; /* Number of entries */
//哈希表fz_hash的容量(桶的数目)
int fz_divisor; /* Hash divisor */
u32 fz_hashmask; /* (fz_divisor - 1) */
#define FZ_HASHMASK(fz) ((fz)->fz_hashmask)
//在网络掩码fz_mask中(所有连续)的比特数目,在代码中也用prefixlen来表示。
//例如,网络掩码255.255.255.0所对应的fz_order为24。
int fz_order; /* Zone order */
//用fz_order构造的网络掩码。例如设fz_order值取3,则生成的fz_mask的二进制表示
//为11100000.00000000.00000000.00000000,其十进制表示为224.0.0.0。
__be32 fz_mask;
#define FZ_MASK(fz) ((fz)->fz_mask)
};
内核路由项中每一个唯一的目的网络对应一个fib_node实例。目的网络相同但其它配置参数不同的路由项共享同一个fib_node实例。
struct fib_node {
//fib_node元素是用哈希表来组织的。这个指针用于将分布在一张哈希表中的一个桶内所有的fib_node元素链接在一起。
struct hlist_node fn_hash;
//每个fib_node结构与包含一个或多个fib_alias结构的链表相关联。fn_alias指针指向该链表的头部
struct list_head fn_alias;
//这是路由项的前缀(或网络地址,用路由项的netmask字段来表示)。该字段被用作查找路由表时的搜索key
__be32 fn_key;
};
fib_alias实例是用来区分目的网络相同但其它配置参数(除了目的地址以外)不同的路由项
struct fib_alias {
//将与同一个fib_node结构相关联的所有fib_alias实例链接在一起
struct list_head fa_list;
struct rcu_head rcu;
struct fib_info *fa_info; //该指针指向一个fib_info实例,该实例存储着如何处理与该路由相匹配报文的信息
//路由的服务类型(TOS)比特位字段(bitfield)。当该值为零时表示还没有配置TOS,所以在路由查找时任何值都可以匹配。
//不要将fa_tos字段与fib_rule结构中的r_tos字段相混淆。
//fa_tos字段是用户对每一条路由表项配置的TOS,而fib_rule结构中的r_tos字段是用户对策略规则配置的TOS。
u8 fa_tos;
u8 fa_type;
u8 fa_scope; //路由表项的作用范围
u8 fa_state; //一些标志的比特位图,迄今只定义了下面这一个标志:
//FA_S_ACCESSED
//当查找时访问到fib_alias实例,就设置该标志来标记。当一个fib_node数据结构改变时这个标志很有用:
//它用于决定是否应当flush路由缓存。如果fib_node已经被访问,那么就可能意味着:
//在该路由发生变化时需要清理(clear)路由缓存内的表项,从而触发一次flush。
};
下一跳网关等重要的路由信息则存储在一个fib_info结构
struct fib_info {
struct hlist_node fib_hash;
struct hlist_node fib_lhash;
//引用计数。fib_treeref是持有该fib_info实例引用的fib_node数据结构的数目,
//fib_clntref是由于路由查找成功而被持有的引用计数
int fib_treeref;
atomic_t fib_clntref;
//标记路由项正在被删除的标志。当该标志被设置为1时,警告该数据结构将被删除而不能再使用
int fib_dead;
//当前使用的唯一标志是RTNH_F_DEAD。
//当与一条多路径路由项相关联的所有fib_nh结构都设置了RTNH_F_DEAD标志时,设置该标志
unsigned fib_flags;
int fib_protocol; //设置路由的协议。使用该字段的一个例子是使路由守护进程(Daemon)在与内核通信时,
//操作只能局限于它们自己生成的路由项。
__be32 fib_prefsrc; //首选源IP地址
u32 fib_priority;//路由优先级。值越小则优先级越高。当没有明确设定时,内核将它的值初始化为缺省值0。
//当配置路由时,ip route命令还可以指定一组metrics。fib_metrics是存储这一组metrics的一个向量。
//没有明确设定的Metrics在初始化时被设置为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;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
int fib_power; //当内核编译支持多路径时,该字段才是fib_info数据结构的一部分
#endif
//fib_nh是结构类型为fib_nh的一个可变长向量,fib_nhs是该向量的size。
//只有当内核支持多路径特性时,fib_nhs才可能大于1。
struct fib_nh fib_nh[];
#define fib_dev fib_nh[0].nh_dev
};
对每一个下一跳,内核需要跟踪的信息要比IP地址信息更为丰富
struct fib_nh {
//这是与设备标识nh_oif相关联的net_device数据结构。因为设备标识和指向net_device结构的指针
//都需要利用(在不同的上下文内),所以这两项都存在于fib_nh结构中,虽然利用其中任何一项就可以得到另一项
struct net_device *nh_dev;
struct hlist_node nh_hash;
struct fib_info *nh_parent; //该指针指向包含该fib_nh实例的fib_info结构
unsigned nh_flags;
//用于获取下一跳的路由scope。在大多数情况下为RT_SCOPE_LINK。该字段由fib_check_nh来初始化。
unsigned char nh_scope;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
//nh_power是由内核初始化,nh_weight是由用户利用关键字weight来设置。
int nh_weight;
int nh_power;
#endif
#ifdef CONFIG_NET_CLS_ROUTE
__u32 nh_tclassid; //它的值是利用realms关键字来设置
#endif
int nh_oif; //egress设备标识
__be32 nh_gw; //下一跳网关的IP地址,它是利用关键字via来设置的。
//注意在NAT情况下,它表示NAT路由器向外部世界通告的地址,
//以及在NAT路由器向内部网中的主机发送回应之前而向外部世界回应的地址。
//例如,命令ip route add nat 10.1.1.253/32 via 151.41.196.1将设置nh_gw为151.41.196.1。
//注意在2.6内核中路由代码已经不再支持NAT,即原来众所周知的FastNAT。
};
策略被存储在fib_rule结构内
struct fib_rule
{
struct list_head list; //将这些fib_rule结构链接到一个包含所有fib_rule实例的全局链表内
//引用计数。该引用计数的递增是在fib_lookup函数(只在策略路由版的函数中)中进行的,
//这解释了为什么在每次路由查找成功后总是需要调用fib_res_put(递减该引用计数)。
atomic_t refcnt;
int ifindex; //内核可以得到相关的net_device实例,将该实例的ifindex字段拷贝到ifindex中。ifindex值取-1表示禁止该规则.
char ifname[IFNAMSIZ]; //策略应用的设备的名称
//当内核编译支持“使用Netfilter MARK值作为路由key“特性时,可以根据防火墙标签来定义规则。
//该字段是管理员定义一条策略规则时利用mark关键字指定的标签。
u32 mark;
u32 mark_mask;
//路由规则的优先级。当管理员利用IPROUTE2软件包添加一个策略时,可以使用关键字priority,preference和order来配置。
//优先级0,0x7FFE和0x7FFF是预留给由内核添加的特定规则使用.
u32 pref;
u32 flags;
//路由表标识,范围从0到255。当用户没有指定路由表标识时,IPROUTE2按照以下方法来选择路由表:
//当用户命令是添加一条规则时使用RT_TABLE_MAIN,其它情况使用RT_TABLE_UNSPEC(例如删除一条规则)。
u32 table;
u8 action; //路由动作类型
u32 target;
struct fib_rule * ctarget;
struct rcu_head rcu;
};
fib_result结构被fib_semantic_match初始化为路由查找结果
struct fib_result {
unsigned char prefixlen; //匹配路由的前缀长度
unsigned char nh_sel; //标识已经被选中的下一跳
//这两个字段被初始化为相匹配的fib_alias实例的fa_type和fa_scope字段的取
unsigned char type;
unsigned char scope;
struct fib_info *fi; //与匹配的fib_alias实例相关联的fib_info实例
#ifdef CONFIG_IP_MULTIPLE_TABLES
struct fib_rule *r; //字段由fib_lookup来初始化
#endif
};
用rtable数据结构来存储缓存内的路由表项
struct rtable
{
union //这个联合用来将一个dst_entry结构嵌入到rtable结构中
{
struct dst_entry dst;
} u; /* Cache lookup keys */
struct flowi fl; //用于缓存查找的搜索key
//该指针指向egress设备的IP配置块。注意对送往本地的ingress报文的路由,设置的egress设备为loopback设备
struct in_device *idev; unsigned rt_flags;
__u16 rt_type; //路由类型 __be32 rt_dst; /* Path destination */
__be32 rt_src; /* Path source */
//Ingress设备标识。这个值是从ingress设备的net_device数据结构中得到。
//对本地生成的流量(因此不是从任何接口上接收到的),该字段被设置为出设备的ifindex字段。
int rt_iif; //当目的主机为直连时(即在同一链路上),rt_gateway表示目的地址。
//当需要通过一个网关到达目的地时,rt_gateway被设置为由路由项中的下一跳网关。
__be32 rt_gateway; /* Miscellaneous cached information */
__be32 rt_spec_dst; //RFC 1122中指定的目的地址
//与本地主机在最近一段时间通信的每个远端IP地址都有一个inet_peer结
struct inet_peer *peer; /* long-living peer info */
};
数据结构dst_entry被用于存储缓存路由项中独立于协议的信息
struct dst_entry
{
struct rcu_head rcu_head; //用于将分布在同一个哈希桶内的dst_entry实例链接在一起
struct dst_entry *child; //这字段被IPsec代码使用
struct net_device *dev; //Egress设备(即将报文送达目的地的发送设备)
//当fib_lookup API(只被IPv4使用)失败时,错误值被保存在error(用一个正值)中,
//在后面的ip_error中使用该值来决定如何处理本次路由查找失败(即决定生成哪一类ICMP消息)。
short error;
//用于定义该dst_entry实例的可用状态:0(缺省值)表示该结构有效而且可以被使用,
//2表示该结构将被删除因而不能被使用,-1被IPsec和IPv6使用但不被IPv4使用。
short obsolete;
//标志集合(Set of flags)。DST_HOST被TCP使用,表示主机路由(即它不是到网络或到一个广播/多播地址的路由)。
//DST_NOXFRM,DST_NOPOLICY和DST_NOHASH只用于IPsec。
int flags;
#define DST_HOST 1
#define DST_NOXFRM 2
#define DST_NOPOLICY 4
#define DST_NOHASH 8 unsigned long expires; //表示该表项将过期的时间戳
//这些字段被IPsec代码使用
unsigned short header_len; /* more space at head required */
unsigned short nfheader_len; /* more non-fragment space at head required */
unsigned short trailer_len; /* space to reserve at tail */
//主要被TCP使用。该向量是用fib_info->fib_metrics向量的一份拷贝来初始化(如果fib_metrics向量被定义),当需要时使用缺省值。
u32 metrics[RTAX_MAX];
struct dst_entry *path; //这字段被IPsec代码使用
//这两个字段被用于对两种类型的ICMP消息限速
unsigned long rate_last; /* rate limiting for ICMP */
unsigned long rate_tokens;
//neighbour是包含下一跳三层地址到二层地址映射的结构,hh是缓存的二层头
struct neighbour *neighbour;
struct hh_cache *hh;
struct xfrm_state *xfrm; //这字段被IPsec代码使用
//分别表示处理ingress报文和处理egress报文的函数
int (*input)(struct sk_buff*);
int (*output)(struct sk_buff*);
#ifdef CONFIG_NET_CLS_ROUTE __u32 tclassid;//基于路由表的classifier的标签
#endif
//该结构内的虚函数表(VFT)用于处理dst_entry结构。 struct dst_ops *ops;
//用于记录该表项上次被使用的时间戳。当缓存查找成功时更新该时间戳,
//垃圾回收程序使用该时间戳来选择最合适的应当被释放的结构。
unsigned long lastuse;
atomic_t __refcnt; //引用计数 /* client references */
int __use; //该表项已经被使用的次数(即缓存查找返回该表项的次数)
union { struct dst_entry *next;
struct rtable *rt_next;
struct rt6_info *rt6_next;
struct dn_route *dn_next;
};
char info[];
};
利用flowi数据结构,就可以根据诸如ingress设备和egress设备、L3和L4协议报头中的参数等字段的组合对流量进行分类。
它通常被用作路由查找的搜索key,IPsec策略的流量选择器以及其它高级用途。
struct flowi {
Egress设备ID和ingress设备ID
int oif;
int iif;
__u32 mark; union { //该联合的各个字段是可用于指定L3参数取值的结构。目前支持的协议为IPv4,IPv6和DECnet。 struct {
__be32 daddr;
__be32 saddr;
__u8 tos;
__u8 scope; } ip4_u; struct {
struct in6_addr daddr;
struct in6_addr saddr;
__be32 flowlabel; } ip6_u; struct {
__le16 daddr;
__le16 saddr;
__u8 scope; } dn_u;
} nl_u;
#define fld_dst nl_u.dn_u.daddr
#define fld_src nl_u.dn_u.saddr
#define fld_scope nl_u.dn_u.scope
#define fl6_dst nl_u.ip6_u.daddr
#define fl6_src nl_u.ip6_u.saddr
#define fl6_flowlabel nl_u.ip6_u.flowlabel
#define fl4_dst nl_u.ip4_u.daddr
#define fl4_src nl_u.ip4_u.saddr
#define fl4_tos nl_u.ip4_u.tos
#define fl4_scope nl_u.ip4_u.scope __u8 proto; //L4协议
__u8 flags; //该变量只定义了一个标志,FLOWI_FLAG_MULTIPATHOLDROUTE,它最初用于多路径代码,但已不再被使用
#define FLOWI_FLAG_MULTIPATHOLDROUTE 0x01 union { //该联合的各个字段是可用于指定L4参数取值的主要结构。目前支持的协议为TCP, UDP,ICMP,DECnet和IPsec协议套件(suite)。 struct {
__be16 sport;
__be16 dport;
} ports; struct {
__u8 type;
__u8 code;
} icmpt; struct {
__le16 sport;
__le16 dport;
} dnports; __be32 spi; struct {
__u8 type; } mht;
} uli_u;
#define fl_ip_sport uli_u.ports.sport
#define fl_ip_dport uli_u.ports.dport
#define fl_icmp_type uli_u.icmpt.type
#define fl_icmp_code uli_u.icmpt.code
#define fl_ipsec_spi uli_u.spi
#define fl_mh_type uli_u.mht.type __u32 secid; /* used by xfrm; see secid.txt */
} __attribute__((__aligned__(BITS_PER_LONG/))); #define LOOPBACK(x) (((x) & htonl(0xff000000)) == htonl(0x7f000000))
IP地址高8位为127的为回送地址类
#define MULTICAST(x) (((x) & htonl(0xf0000000)) == htonl(0xe0000000))
IP地址高4位为14的为多目地址类
#define BADCLASS(x) (((x) & htonl(0xf0000000)) == htonl(0xf0000000))
IP地址高4位为15的为非法地址类
#define ZERONET(x) (((x) & htonl(0xff000000)) == htonl(0x00000000))
IP地址高8位为0的为零网地址类
#define LOCAL_MCAST(x) (((x) & htonl(0xFFFFFF00)) == htonl(0xE0000000))
IP地址高24位为0xE00000的为局域组播地址类
[/数据结构]
[初始化]
fs_initcall(inet_init);
static int __init inet_init(void) //net/ipv4/af_inet.c
{
......
ip_init(); //ip路由相关部分初始化
......
}
void __init ip_init(void)
{
ip_rt_init(); //路由初始化
inet_initpeers(); //初始化一个 AVL 树,用于追踪IP对端的信息,远程主机到这主机最新交换的数据包. #if defined(CONFIG_IP_MULTICAST) && defined(CONFIG_PROC_FS)
igmp_mc_proc_init();
#endif
}
初始化高速路由缓存和FIB(Forwarding Information Base)
int __init ip_rt_init(void)
{
int rc = ;
//hash随机因子初始化
rt_hash_rnd = (int) ((num_physpages ^ (num_physpages>>)) ^ (jiffies ^ (jiffies >> )));
#ifdef CONFIG_NET_CLS_ROUTE
{
int order;
//确定需要多少内存页
for (order = ; (PAGE_SIZE << order) < * sizeof(struct ip_rt_acct) * NR_CPUS; order++)
/* NOTHING */;
//路由参数统计,即进出的包数和字节数,每个cpu有256个 ip_rt_acct = (struct ip_rt_acct *)__get_free_pages(GFP_KERNEL, order);
if (!ip_rt_acct)
panic("IP: failed to allocate ip_rt_acct\n"); memset(ip_rt_acct, , PAGE_SIZE << order);
}
#endif
//路由操作高速缓存分配初始化,参看下面路由缓存操作实现
ipv4_dst_ops.kmem_cachep = kmem_cache_create("ip_dst_cache", sizeof(struct rtable), ,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); 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, //元素的个数,默认0,由系统来确定,即使你给了值,函数也会把它变为最接近的2的幂
(num_physpages >= * ) ? : ,
,//可取HASH_EARLY或0,分配内存的地方根据这个有不同
&rt_hash_log,//用于返回元素个数的以2为底的对数,也就是表示元素个数这个数值所用的比特数
&rt_hash_mask,//用于返回上面那个比特数所能表示的最大数 -1
);//哈希表表元数上限,不是分配内存的总尺寸,不要弄混了。
//如果给个0值,那么系统使用1/16内存所能容纳的元素数作为哈希表表元数.
memset(rt_hash_table, , (rt_hash_mask + ) * sizeof(struct rt_hash_bucket)); //清零 rt_hash_lock_init();//锁初始化 //路由高速缓存垃圾回收阀值
ipv4_dst_ops.gc_thresh = (rt_hash_mask + );
ip_rt_max_size = (rt_hash_mask + ) * ; //每个hash项最多16个元素 devinet_init();
ip_fib_init(); //fib初始化 init_timer(&rt_flush_timer); //flush路由缓存时的定时器
rt_flush_timer.function = rt_run_flush;
init_timer(&rt_secret_timer); //垃圾回收时要使用的定时器
rt_secret_timer.function = rt_secret_rebuild; /* 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);
#ifdef CONFIG_PROC_FS //proc初始化
{
struct proc_dir_entry *rtstat_pde = NULL; /* keep gcc happy */
if (!proc_net_fops_create(&init_net, "rt_cache", S_IRUGO, &rt_cache_seq_fops) ||
!(rtstat_pde = create_proc_entry("rt_cache", S_IRUGO, init_net.proc_net_stat))) {
return -ENOMEM;
}
rtstat_pde->proc_fops = &rt_cpu_seq_fops;
}
#ifdef CONFIG_NET_CLS_ROUTE
create_proc_read_entry("rt_acct", , init_net.proc_net, ip_rt_acct_read, NULL);
#endif
#endif #ifdef CONFIG_XFRM //IPSEC初始化,参考IPSEC实现文章 ??为什么放在这
xfrm_init();
xfrm4_init();
#endif
rtnl_register(PF_INET, RTM_GETROUTE, inet_rtm_getroute, NULL);
return rc;
}
看上面参数说明
void *__init alloc_large_system_hash(const char *tablename, unsigned long bucketsize, unsigned long numentries,
int scale, int flags, unsigned int *_hash_shift, unsigned int *_hash_mask, unsigned long limit)
{
unsigned long long max = limit;
unsigned long log2qty, size;
void *table = NULL; /* allow the kernel cmdline to have a say */
if (!numentries) { //决定元素个数
/* round applicable memory size up to nearest megabyte */
numentries = nr_kernel_pages;
numentries += (1UL << ( - PAGE_SHIFT)) - ; //加上一个255 (i386)
//清除低8位,变为256倍数
numentries >>= - PAGE_SHIFT;
numentries <<= - PAGE_SHIFT; /* limit to 1 bucket per 2^scale bytes of low memory */
if (scale > PAGE_SHIFT)
numentries >>= (scale - PAGE_SHIFT);
else
numentries <<= (PAGE_SHIFT - scale); /* Make sure we've got at least a 0-order allocation.. */
if (unlikely((numentries * bucketsize) < PAGE_SIZE))
numentries = PAGE_SIZE / bucketsize;
}
numentries = roundup_pow_of_two(numentries); //向上到2的幂 /* limit allocation size to 1/16 total memory by default */
if (max == ) { //使用 1/16 的内存
max = ((unsigned long long)nr_all_pages << PAGE_SHIFT) >> ;
do_div(max, bucketsize); //max = max / bucketsize
}
if (numentries > max) //不能超过这个数量
numentries = max; log2qty = ilog2(numentries);//元素个数这个数值所用的比特数
do {
size = bucketsize << log2qty; //确定内存使用大小(字节) bucket * 2 ^ log2qty
if (flags & HASH_EARLY)
table = alloc_bootmem(size); else if (hashdist) //默认0,启动时可以添加参数修改
table = __vmalloc(size, GFP_ATOMIC, PAGE_KERNEL);
else {
unsigned long order;
for (order = ; ((1UL << order) << PAGE_SHIFT) < size; order++)//确定需要使用的页数
; table = (void*) __get_free_pages(GFP_ATOMIC, order);
/* If bucketsize is not a power-of-two, we may free some pages at the end of hash table. */
if (table) {
unsigned long alloc_end = (unsigned long)table + (PAGE_SIZE << order); //内存结束位置
unsigned long used = (unsigned long)table + PAGE_ALIGN(size); //使用结尾位置,以页对齐
split_page(virt_to_page(table), order); //页是连续的,把它打散成一页一页的 //因为在确定需要使用的页数的时候页的数量是以2的幂增长的.
while (used < alloc_end) { //释放一些不使用的页
free_page(used);
used += PAGE_SIZE;
//size -= PAGE_SIZE; 应该加上,否则下面统计信息有一些偏差
}
} }
} while (!table && size > PAGE_SIZE && --log2qty); //内存分配失败,减少比特位数也就是除2 if (!table)
panic("Failed to allocate %s hash table\n", tablename); printk(KERN_INFO "%s hash table entries: %d (order: %d, %lu bytes)\n",
tablename,
(1U << log2qty), //表项个数
ilog2(size) - PAGE_SHIFT, //占2的几次方页
size); //内存使用 if (_hash_shift)
*_hash_shift = log2qty;
if (_hash_mask)
*_hash_mask = ( << log2qty) - ;
return table;
}
为通知链netdev_chain注册另一个处理钩子,注册Netlink套接字上地址和路由命令(即ip addr ... 与ip route ..命令)的处理钩子函数,
并创建/proc/sys/net/conf和/proc/sys/net/conf/default目录.
void __init devinet_init(void)
{
register_gifconf(PF_INET, inet_gifconf);
register_netdevice_notifier(&ip_netdev_notifier);
//rtnetlink和用户交互使用
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);
#ifdef CONFIG_SYSCTL
devinet_sysctl.sysctl_header = register_sysctl_table(devinet_sysctl.devinet_root_dir); //创建/proc/sys/net/ipv4/conf/all/*
devinet_sysctl_register(NULL, &ipv4_devconf_dflt); //创建/proc/sys/net/ipv4/conf/default/*
#endif
}
FIB (Forwarding Information Base)初始化
void __init ip_fib_init(void)
{
unsigned int i; for (i = ; i < FIB_TABLE_HASHSZ; i++)
INIT_HLIST_HEAD(&fib_table_hash[i]); fib4_rules_init(); register_netdevice_notifier(&fib_netdev_notifier); //网络设备状态的变化
register_inetaddr_notifier(&fib_inetaddr_notifier); //网络设备上IP配置的变化
nl_fib_lookup_init(); //netlink fib 查询初始化 使用 ?? rtnl_register(PF_INET, RTM_NEWROUTE, inet_rtm_newroute, NULL);
rtnl_register(PF_INET, RTM_DELROUTE, inet_rtm_delroute, NULL);
rtnl_register(PF_INET, RTM_GETROUTE, NULL, inet_dump_fib);
}
当内核配置了策略路由 CONFIG_IP_MULTIPLE_TABLES,系统可使用多达256张转发表,
fib_lookup()通过规则表(fib_fule)匹配相应的转发表,再进一步匹配目标地址的路由.我们浏览使用策略路由的代码.
void __init fib4_rules_init(void)
{
BUG_ON(fib_default_rules_init());
fib_rules_register(&fib4_rules_ops);//注册这个路由规则操作,就是添加到 static LIST_HEAD(rules_ops); 连表中
}
默认规则初始化,初始化三个路由规则,参看下面路由规则操作实现
这三条规则以链表的形式组织在一起,系统管理员可以通过系统命令操作向这个链表中再添加其它优先级的路由规则。
三条内置规则对数据包的源和目的地址,以及服务类型都没有作任何特殊限制,所以任何一个数据包都可以按顺序查找这三个路由表,直到找到相应的路由为止。
static int __init fib_default_rules_init(void)
{
int err;
//RT_TABLE_LOCAL优先级最高,为0,RT_TABLE_MAIN为0x7FFE, RT_TABLE_DEFAULT为0x7FFF
err = fib_default_rule_add(&fib4_rules_ops, , RT_TABLE_LOCAL, FIB_RULE_PERMANENT);
if (err < )
return err; err = fib_default_rule_add(&fib4_rules_ops, 0x7FFE, RT_TABLE_MAIN, );
if (err < )
return err; err = fib_default_rule_add(&fib4_rules_ops, 0x7FFF, RT_TABLE_DEFAULT, );
if (err < )
return err;
return ;
}
int fib_default_rule_add(struct fib_rules_ops *ops, u32 pref, u32 table, u32 flags)
{
struct fib_rule *r; r = kzalloc(ops->rule_size, GFP_KERNEL);
if (r == NULL)
return -ENOMEM; atomic_set(&r->refcnt, );
r->action = FR_ACT_TO_TBL;
r->pref = pref; //优先级
r->table = table; //表类型
r->flags = flags; /* The lock is not required here, the list in unreacheable at the moment this function is called */
list_add_tail(&r->list, &ops->rules_list); //添加到规则连表中
return ;
}
[/初始化]
[概念解释]
看代码之前,我们先解释一些概念。
scope
路由和IP地址都有scope(作用范围或作用域),这告诉内核它们在哪些情况下是有意义的,是可以被使用的。
在Linux中,路由的scope表示到目的网络的距离。IP地址的scope表示该IP地址距离本地主机有多远,某种程度上也告诉你该地址的owner距离本地主机有多远。
路由的scope被保存在fib_alias数据结构内的fa_scope字段。下面按照scope递减顺序给出IPv4路由代码中使用的主要scope,值越小表示的范围越大:
RT_SCOPE_NOWHERE //
它被代码视为非法scope。它的字面含义是路由项不通往任何地方,这基本上就意味着没有到达目的地的路由。
RT_SCOPE_HOST //
scope为RT_SCOPE_HOST的路由项的例子是,为本地接口配置IP地址时自动创建的路由表项。
RT_SCOPE_LINK //
为本地接口配置地址时,派生的目的地为本地网络地址(由网络掩码定义)和子网广播地址的路由表项的scope就是RT_SCOPE_LINK。
RT_SCOPE_UNIVERSE
该scope被用于所有的通往远程非直连目的地的路由表项(也就是需要一个下一跳网关的路由项)。 地址的scope被保存在in_ifaddr结构内的ifa_scope字段。对设备上配置的每一个IP地址对应一个in_ifaddr实例。
当一个地址只用于主机自身内部通信时scope为host,该地址在主机以外不知道并且不能被使用。例如回环地址127.0.0.。
当一个地址只在一个局域网(即每一台计算机通过链路层互联的一个网络)内有意义且只在局域网内使用时,该地址的scope为link。
例如子网的广播地址。子网内一台主机发送到子网广播地址的报文被送给同一子网内的其它主机。
当一个地址可以在任何地方使用时scope为universe,这是大多数地址的缺省scope。 路由表项中的下一跳网关是另一个具有scope的对象类型。每一条路由项可以有零个、一个或多个下一跳,每个下一跳是由一个fib_nh结构表示。
fib_nh结构中有nh_gw和nh_scope两个字段:nh_gw是下一跳网关的IP地址,nh_scope是该地址的scope
(这两个字段表示了从本地主机到这个下一跳网关的路由表项的scope)。
[/概念解释]
[输入流程]
IP接收函数ip_rcv->ip_rcv_finish->ip_route_input对一个输入的包进行路由查找,完毕后调用dst_input->skb->dst->input(skb);
int ip_route_input(struct sk_buff *skb, __be32 daddr, __be32 saddr, u8 tos, struct net_device *dev)
{
struct rtable * rth;
unsigned hash;
int iif = dev->ifindex; //设备索引 tos &= IPTOS_RT_MASK;
hash = rt_hash(daddr, saddr, iif); //源地址,目的地址,设备索引 rcu_read_lock();
for (rth = rcu_dereference(rt_hash_table[hash].chain); rth; rth = rcu_dereference(rth->u.dst.rt_next)) { if (rth->fl.fl4_dst == daddr && rth->fl.fl4_src == saddr && rth->fl.iif == iif &&
rth->fl.oif == && rth->fl.mark == skb->mark && rth->fl.fl4_tos == tos) {
//在路由高速缓存中找到了匹配
dst_use(&rth->u.dst, jiffies);
RT_CACHE_STAT_INC(in_hit);
rcu_read_unlock();
skb->dst = (struct dst_entry*)rth;
return ;
}
RT_CACHE_STAT_INC(in_hlist_search);
}
rcu_read_unlock(); if (MULTICAST(daddr)) { //目的地址是多播地址
struct in_device *in_dev;
rcu_read_lock();
if ((in_dev = __in_dev_get_rcu(dev)) != NULL) {
int our = ip_check_mc(in_dev, daddr, saddr, ip_hdr(skb)->protocol);//检查目的地址是本地配置的多播地址
if (our
#ifdef CONFIG_IP_MROUTE
|| (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev)) //内核编译时支持多播路由,且转发开启
#endif
) {
rcu_read_unlock();
return ip_route_input_mc(skb, daddr, saddr, tos, dev, our); //参考下面多播路由处理
} }
rcu_read_unlock();
return -EINVAL;
}
return ip_route_input_slow(skb, daddr, saddr, tos, dev);
}
非多播路由处理
static int ip_route_input_slow(struct sk_buff *skb, __be32 daddr, __be32 saddr, u8 tos, struct net_device *dev)
{
struct fib_result res;
struct in_device *in_dev = in_dev_get(dev); //dev->ip_ptr指向这个结构
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = daddr,
.saddr = saddr,
.tos = tos,
.scope = RT_SCOPE_UNIVERSE,
} },
.mark = skb->mark,
.iif = dev->ifindex };
unsigned flags = ;
u32 itag = ;
struct rtable * rth;
unsigned hash;
__be32 spec_dst;
int err = -EINVAL;
int free_res = ; /* IP on this device is disabled. */
if (!in_dev)
goto out; /* Check for the most weird martians, which can be not detected by fib_lookup. */
//源地址是多播,非法或回环
if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr))
goto martian_source;
//检测地址是否错误
if (daddr == htonl(0xFFFFFFFF) || (saddr == && daddr == ))
goto brd_input;
/* Accept zero addresses only to limited broadcast;
* I even do not know to fix it or not. Waiting for complains :-)
*/
if (ZERONET(saddr)) //源地址为0
goto martian_source; //目的地址为非法,零类或回环地址
if (BADCLASS(daddr) || ZERONET(daddr) || LOOPBACK(daddr))
goto martian_destination;
/* Now we are ready to route packet. */
//在fib中查询路由信息,将路由查询结果保存在fib_result结构的res中
if ((err = fib_lookup(&fl, &res)) != ) {
if (!IN_DEV_FORWARD(in_dev))//没有找到而且设备不能转发
goto e_hostunreach; goto no_route; //没找到但可以转发
}
free_res = ; //已经找到
RT_CACHE_STAT_INC(in_slow_tot); if (res.type == RTN_BROADCAST) //路由类型为广播
goto brd_input;
if (res.type == RTN_LOCAL) {//路由类型为本地,也就是到本地的包
int result;
//验证源地址有效
result = fib_validate_source(saddr, daddr, tos, init_net.loopback_dev->ifindex, dev, &spec_dst, &itag);
if (result < )
goto martian_source; if (result)
flags |= RTCF_DIRECTSRC; spec_dst = daddr;
goto local_input;
}
//需要转发的包,查看是否允许转发
if (!IN_DEV_FORWARD(in_dev))
goto e_hostunreach; if (res.type != RTN_UNICAST) //路由类型不是单播
goto martian_destination;
//需要转发的包,创建路由项
err = ip_mkroute_input(skb, &res, &fl, in_dev, daddr, saddr, tos);
done:
in_dev_put(in_dev);
if (free_res)
fib_res_put(&res);
out:
return err;
brd_input: //目的地址为广播地址或源和目的地址为0或路由结果显示为广播,会跳到这 if (skb->protocol != htons(ETH_P_IP)) //非ip协议
goto e_inval; if (ZERONET(saddr)) //源地址为0,选择一个地址
spec_dst = inet_select_addr(dev, , RT_SCOPE_LINK);
else { //验证源地址
err = fib_validate_source(saddr, , tos, , dev, &spec_dst, &itag);
if (err < ) //源地址不正确
goto martian_source; if (err)
flags |= RTCF_DIRECTSRC; //告诉ICMP代码,不应当对地址掩码请求消息作出回应.
}
flags |= RTCF_BROADCAST; //路由的目的地址是一个广播地址
res.type = RTN_BROADCAST; //目的地址是一个广播地址。匹配的ingress报文以广播方式送往本地,匹配的egress报文以广播方式发送出去.
RT_CACHE_STAT_INC(in_brd);
local_input:
rth = dst_alloc(&ipv4_dst_ops); //分配路由缓冲项
if (!rth)
goto e_nobufs; rth->u.dst.output= ip_rt_bug; //到本地的包,应该没有发送调用 atomic_set(&rth->u.dst.__refcnt, );
//DST_HOST被TCP使用,表示主机路由(即它不是到网络或到一个广播/多播地址的路由)
rth->u.dst.flags= DST_HOST; if (IN_DEV_CONF_GET(in_dev, NOPOLICY))
rth->u.dst.flags |= DST_NOPOLICY; //IPSEC使用 rth->fl.fl4_dst = daddr;
rth->rt_dst = daddr;
rth->fl.fl4_tos = tos;
rth->fl.mark = skb->mark;
rth->fl.fl4_src = saddr;
rth->rt_src = saddr;
#ifdef CONFIG_NET_CLS_ROUTE
rth->u.dst.tclassid = itag;
#endif
rth->rt_iif =
rth->fl.iif = dev->ifindex;
rth->u.dst.dev = init_net.loopback_dev; //指向回环设备
dev_hold(rth->u.dst.dev);
rth->idev = in_dev_get(rth->u.dst.dev);
rth->rt_gateway = daddr;
rth->rt_spec_dst= spec_dst;
rth->u.dst.input= ip_local_deliver;
rth->rt_flags = flags|RTCF_LOCAL; //路由目的地址为本地地址 if (res.type == RTN_UNREACHABLE) { //路由类型为不可达
rth->u.dst.input= ip_error;
rth->u.dst.error= -err; //保存错误类型,ip_error会根据这个类型发送相应的icmp包
rth->rt_flags &= ~RTCF_LOCAL;
}
rth->rt_type = res.type;
hash = rt_hash(daddr, saddr, fl.iif);
err = rt_intern_hash(hash, rth, (struct rtable**)&skb->dst); //添加到hash表中
goto done;
no_route: //路由信息库查找失败时
RT_CACHE_STAT_INC(in_no_route);
spec_dst = inet_select_addr(dev, , RT_SCOPE_UNIVERSE);
res.type = RTN_UNREACHABLE; //结果路由类型为不可达
if (err == -ESRCH)
err = -ENETUNREACH;
goto local_input; /* Do not cache martian addresses: they should be logged (RFC1812)*/
martian_destination: //目的地址出错
RT_CACHE_STAT_INC(in_martian_dst);
#ifdef CONFIG_IP_ROUTE_VERBOSE
if (IN_DEV_LOG_MARTIANS(in_dev) && net_ratelimit())
printk(KERN_WARNING "martian destination %u.%u.%u.%u from %u.%u.%u.%u, dev %s\n",
NIPQUAD(daddr), NIPQUAD(saddr), dev->name);
#endif
e_hostunreach:
err = -EHOSTUNREACH; //函数返回后,根据这个值记录统计信息,然后丢弃这个包
goto done;
e_inval:
err = -EINVAL; //返回后直接丢弃
goto done;
e_nobufs:
err = -ENOBUFS;//返回后直接丢弃
goto done;
martian_source: //验证源地址,源地址出错 //处理出错源地址,除了RT_CACHE_STAT_INC(in_martian_src); 如果开启CONFIG_IP_ROUTE_VERBOSE打印一些信息,其他什么也不做.
ip_handle_martian_source(dev, in_dev, skb, daddr, saddr); //处理出错源地址,RT_CACHE_STAT_INC(in_martian_src);
goto e_inval;
}
fib查询函数
int fib_lookup(struct flowi *flp, struct fib_result *res)
{
struct fib_lookup_arg arg = {
.result = res,
};
int err;
err = fib_rules_lookup(&fib4_rules_ops, flp, , &arg);
res->r = arg.rule; //找到的具体路由规则 return err;
}
实际查找函数
int fib_rules_lookup(struct fib_rules_ops *ops, struct flowi *fl, int flags, struct fib_lookup_arg *arg)
{
struct fib_rule *rule;
int err; rcu_read_lock();
list_for_each_entry_rcu(rule, &ops->rules_list, list) { //连表中搜寻所有规则
jumped:
if (!fib_rule_match(rule, ops, fl, flags)) //查看规则是否匹配
continue; if (rule->action == FR_ACT_GOTO) { //活动类型为跳转到其他规则
struct fib_rule *target;
target = rcu_dereference(rule->ctarget);
if (target == NULL) {
continue;
} else {
rule = target;
goto jumped;
}
} else if (rule->action == FR_ACT_NOP)
continue;
else //根据规则查找路由表中内容是否匹配,参考下面路由规则操作实现
err = ops->action(rule, fl, flags, arg);
if (err != -EAGAIN) {
fib_rule_get(rule);
arg->rule = rule;
goto out;
}
}
err = -ESRCH;
out:
rcu_read_unlock();
return err;
}
匹配函数
static int fib_rule_match(struct fib_rule *rule, struct fib_rules_ops *ops, struct flowi *fl, int flags)
{
int ret = ;
//规则中记录了设备索引,检查是否匹配索引
if (rule->ifindex && (rule->ifindex != fl->iif))
goto out; if ((rule->mark ^ fl->mark) & rule->mark_mask)
goto out; ret = ops->match(rule, fl, flags); //调用操作匹配函数
out:
return (rule->flags & FIB_RULE_INVERT) ? !ret : ret;
}
验证源地址
int fib_validate_source(__be32 src, __be32 dst, u8 tos, int oif, struct net_device *dev, __be32 *spec_dst, u32 *itag)
{
struct in_device *in_dev;
struct flowi fl = { .nl_u = { .ip4_u = //反向查询
{ .daddr = src,
.saddr = dst,
.tos = tos } },
.iif = oif }; //这个索引值为回环设备的索引值
struct fib_result res;
int no_addr, rpf;
int ret; no_addr = rpf = ;
rcu_read_lock();
in_dev = __in_dev_get_rcu(dev);
if (in_dev) {
no_addr = in_dev->ifa_list == NULL; //设备是否有配置ip地址
rpf = IN_DEV_RPFILTER(in_dev);
}
rcu_read_unlock(); if (in_dev == NULL)
goto e_inval;
if (fib_lookup(&fl, &res))
goto last_resort; if (res.type != RTN_UNICAST) //反向查找后路由类型指出目的地址不是一个单播地址
goto e_inval_res; *spec_dst = FIB_RES_PREFSRC(res);
//进行反向路径查找时来辅助查找realms
fib_combine_itag(itag, &res);
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (FIB_RES_DEV(res) == dev || res.fi->fib_nhs > ) //适合的下一跳不止一个
#else
if (FIB_RES_DEV(res) == dev)
#endif
{
ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST;
fib_res_put(&res);
return ret;
}
fib_res_put(&res); if (no_addr) //没有ip地址
goto last_resort; if (rpf)
goto e_inval; fl.oif = dev->ifindex; //外出接口是进入接口 ret = ;
if (fib_lookup(&fl, &res) == ) {
if (res.type == RTN_UNICAST) {
*spec_dst = FIB_RES_PREFSRC(res);
ret = FIB_RES_NH(res).nh_scope >= RT_SCOPE_HOST; }
fib_res_put(&res);
}
return ret;
last_resort:
if (rpf)
goto e_inval;
*spec_dst = inet_select_addr(dev, , RT_SCOPE_UNIVERSE);
*itag = ;
return ;
e_inval_res:
fib_res_put(&res);
e_inval:
return -EINVAL;
}
创建路由项
static inline int ip_mkroute_input(struct sk_buff *skb, struct fib_result* res, const struct flowi *fl, struct in_device *in_dev,
__be32 daddr, __be32 saddr, u32 tos)
{
struct rtable* rth = NULL;
int err;
unsigned hash;
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (res->fi && res->fi->fib_nhs > && fl->oif == ) //当查找返回的路由是一条多路径路由项时,需要选出下一跳
fib_select_multipath(fl, res);
#endif
//创建一个路由缓存项,由rth返回
err = __mkroute_input(skb, res, in_dev, daddr, saddr, tos, &rth);
if (err)
return err; /* put it into the cache */
//根据目的,源地址和包进入设备索引计算hash值
hash = rt_hash(daddr, saddr, fl->iif);
return rt_intern_hash(hash, rth, (struct rtable**)&skb->dst); //将结果存入hash表和 skb->dst指针.
}
当一条路由项有多个下一跳时可用时,选择出下一跳
void fib_select_multipath(const struct flowi *flp, struct fib_result *res)
{
struct fib_info *fi = res->fi;
int w; spin_lock_bh(&fib_multipath_lock);
//该字段被初始化为fib_info实例的所有下一跳权值(fib_nh->nh_weight)的总和,
//但不包含由于某些原因而不能使用的下一跳(带有RTNH_F_DEAD标志)。每当调用fib_select_multipath来选择一个下一跳时,
//fib_power的值递减。当该值递减为小于或等于零时被重新初始化。
if (fi->fib_power <= ) {
int power = ;
change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD)) {
power += nh->nh_weight; //下一跳的权值,默认为 1
//nh->nh_power是使该下一跳被选中的tokens。这个值是在初始化fib_info->fib_power时,
//首先被初始化为fib_nh->nh_weight。每当fib_select_multipath选中该下一跳时就递减该值。
//当这个值递减为零时,不再选中该下一跳,直到nh_power被重新初始化为fib_nh->nh_weight。
nh->nh_power = nh->nh_weight;
}
} endfor_nexthops(fi);
fi->fib_power = power;
if (power <= ) {
spin_unlock_bh(&fib_multipath_lock);
/* Race condition: route has just become dead. */
res->nh_sel = ;
return;
}
}
/* w should be random number [0..fi->fib_power-1],
it is pretty bad approximation. */ w = jiffies % fi->fib_power; change_nexthops(fi) {
if (!(nh->nh_flags&RTNH_F_DEAD) && nh->nh_power) {
if ((w -= nh->nh_power) <= ) { //递减 w, 当fib_power为1时,可能选不到那个唯一的nh_power为1的下一跳
nh->nh_power--;
fi->fib_power--;
res->nh_sel = nhsel; //记录下一跳的位置
spin_unlock_bh(&fib_multipath_lock);
return;
}
}
} endfor_nexthops(fi);
/* Race condition: route has just become dead. */
res->nh_sel = ;
spin_unlock_bh(&fib_multipath_lock);
}
具体创建路由缓存函数
static inline int __mkroute_input(struct sk_buff *skb, struct fib_result* res, struct in_device *in_dev,
__be32 daddr, __be32 saddr, u32 tos, struct rtable **result)
{
struct rtable *rth;
int err;
struct in_device *out_dev;
unsigned flags = ;
__be32 spec_dst;
u32 itag; //获取外出接口,增加引用计数
out_dev = in_dev_get(FIB_RES_DEV(*res));
if (out_dev == NULL) { //没有外出接口,出错
if (net_ratelimit())
printk(KERN_CRIT "Bug in ip_route_input_slow(). Please, report\n");
return -EINVAL;
}
//验证源地址是否正确
err = fib_validate_source(saddr, daddr, tos, FIB_RES_OIF(*res), in_dev->dev, &spec_dst, &itag);
if (err < ) {
ip_handle_martian_source(in_dev->dev, in_dev, skb, daddr, saddr);
err = -EINVAL;
goto cleanup;
}
//该标志主要用于告诉ICMP代码,不应当对地址掩码请求消息作出回应。
//每当调用fib_validate_source检查到接收报文的源地址通过一个本地作用范围(RT_SCOPE_HOST)的下一跳是可达时,就设置该标志
if (err)
flags |= RTCF_DIRECTSRC;
if (out_dev == in_dev && err && !(flags & (RTCF_NAT | RTCF_MASQ)) &&
(IN_DEV_SHARED_MEDIA(out_dev) || inet_addr_onlink(out_dev, saddr, FIB_RES_GW(*res))))
flags |= RTCF_DOREDIRECT; //当必须向源站送回ICMP_REDIRECT消息时,设置 if (skb->protocol != htons(ETH_P_IP)) { //非IP协议
/* Not IP (i.e. ARP). Do not create route, if it is invalid for proxy arp. DNAT routes are always valid. */
if (out_dev == in_dev && !(flags & RTCF_DNAT)) {
err = -EINVAL;
goto cleanup;
}
} rth = dst_alloc(&ipv4_dst_ops); //分配路由表项
if (!rth) {
err = -ENOBUFS;
goto cleanup;
}
atomic_set(&rth->u.dst.__refcnt, );
rth->u.dst.flags = DST_HOST; if (IN_DEV_CONF_GET(in_dev, NOPOLICY))
rth->u.dst.flags |= DST_NOPOLICY;
if (IN_DEV_CONF_GET(out_dev, NOXFRM))
rth->u.dst.flags |= DST_NOXFRM; rth->fl.fl4_dst = daddr;
rth->rt_dst = daddr;
rth->fl.fl4_tos = tos;
rth->fl.mark = skb->mark;
rth->fl.fl4_src = saddr;
rth->rt_src = saddr;
rth->rt_gateway = daddr;
rth->rt_iif =
rth->fl.iif = in_dev->dev->ifindex; //包进入时设备的索引
rth->u.dst.dev = (out_dev)->dev; //包输出设备结构
dev_hold(rth->u.dst.dev);
rth->idev = in_dev_get(rth->u.dst.dev);
rth->fl.oif = ; //外出接口索引设置为0 ??? why not is out_dev->dev->ifindex ?
rth->rt_spec_dst= spec_dst; rth->u.dst.input = ip_forward; //ip 转发函数,路由查找结束后会调用
rth->u.dst.output = ip_output; rt_set_nexthop(rth, res, itag); //设置下一条网关
rth->rt_flags = flags; *result = rth; //保存结果
err = ;
cleanup:
/* release the working reference to the output device */
in_dev_put(out_dev);
return err;
}
分配路由高速缓存项
void * dst_alloc(struct dst_ops * ops)
{
struct dst_entry * dst;
//有垃圾收集器而且项数已经超过最大限制
if (ops->gc && atomic_read(&ops->entries) > ops->gc_thresh) {
if (ops->gc()) //启动垃圾回收
return NULL;
}
//虽然这写的是分配dst_entry,但是在初始化是内存大小是sizeof(struct rtable)
dst = kmem_cache_zalloc(ops->kmem_cachep, GFP_ATOMIC);
if (!dst)
return NULL;
atomic_set(&dst->__refcnt, );
dst->ops = ops; //操作
dst->lastuse = jiffies;
dst->path = dst;
dst->input = dst->output = dst_discard; //初始化一个默认函数,调用kfree_skb(skb);
#if RT_CACHE_DEBUG >= 2
atomic_inc(&dst_total);
#endif
atomic_inc(&ops->entries); //增加项数
return dst;
}
路由缓存中保存下一跳地址
static void rt_set_nexthop(struct rtable *rt, struct fib_result *res, u32 itag)
{
struct fib_info *fi = res->fi; if (fi) { //找到路由信息
//#define FIB_RES_NH(res) ((res).fi->fib_nh[(res).nh_sel])
//#define FIB_RES_GW(res) (FIB_RES_NH(res).nh_gw)
if (FIB_RES_GW(*res) && FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK) //有下一跳网关
rt->rt_gateway = FIB_RES_GW(*res); //记录下一跳网关地址 memcpy(rt->u.dst.metrics, fi->fib_metrics, sizeof(rt->u.dst.metrics));
if (fi->fib_mtu == ) {
rt->u.dst.metrics[RTAX_MTU-] = rt->u.dst.dev->mtu;
if (rt->u.dst.metrics[RTAX_LOCK-] & ( << RTAX_MTU) && rt->rt_gateway != rt->rt_dst && rt->u.dst.dev->mtu > )
rt->u.dst.metrics[RTAX_MTU-] = ; }
#ifdef CONFIG_NET_CLS_ROUTE
rt->u.dst.tclassid = FIB_RES_NH(*res).nh_tclassid;
#endif
} else
rt->u.dst.metrics[RTAX_MTU-]= rt->u.dst.dev->mtu;
if (rt->u.dst.metrics[RTAX_HOPLIMIT-] == )
rt->u.dst.metrics[RTAX_HOPLIMIT-] = sysctl_ip_default_ttl; if (rt->u.dst.metrics[RTAX_MTU-] > IP_MAX_MTU)
rt->u.dst.metrics[RTAX_MTU-] = IP_MAX_MTU; if (rt->u.dst.metrics[RTAX_ADVMSS-] == )
rt->u.dst.metrics[RTAX_ADVMSS-] = max_t(unsigned int, rt->u.dst.dev->mtu - , ip_rt_min_advmss); if (rt->u.dst.metrics[RTAX_ADVMSS-] > - )
rt->u.dst.metrics[RTAX_ADVMSS-] = - ; #ifdef CONFIG_NET_CLS_ROUTE
#ifdef CONFIG_IP_MULTIPLE_TABLES
set_class_tag(rt, fib_rules_tclass(res));
#endif
set_class_tag(rt, itag);
#endif
rt->rt_type = res->type;
}
放入hash表
static int rt_intern_hash(unsigned hash, struct rtable *rt, struct rtable **rp)
{
struct rtable *rth, **rthp;
unsigned long now;
struct rtable *cand, **candp;
u32 min_score;
int chain_length;
int attempts = !in_softirq();
restart:
chain_length = ;
min_score = ~(u32);
cand = NULL;
candp = NULL;
now = jiffies; rthp = &rt_hash_table[hash].chain; //指向头 spin_lock_bh(rt_hash_lock_addr(hash));
while ((rth = *rthp) != NULL) { //循环直到空
if (compare_keys(&rth->fl, &rt->fl)) { //找到相同的项
*rthp = rth->u.dst.rt_next;
rcu_assign_pointer(rth->u.dst.rt_next, rt_hash_table[hash].chain);
rcu_assign_pointer(rt_hash_table[hash].chain, rth); dst_use(&rth->u.dst, now); //增加引用和使用计数,更新时间 (dst->__refcnt ->__use ->lastuse)
spin_unlock_bh(rt_hash_lock_addr(hash)); rt_drop(rt); //释放这个分配的路由项
*rp = rth; //skb->dst 保存这个路由项
return ; }
//不匹配,这个路由缓存项引用计数为 0
if (!atomic_read(&rth->u.dst.__refcnt)) {
u32 score = rt_score(rth); if (score <= min_score) { //价值最小
cand = rth;
candp = rthp;
min_score = score;
}
}
chain_length++;
rthp = &rth->u.dst.rt_next; //移动到下一个
}
if (cand) {
/* ip_rt_gc_elasticity used to be average length of chain length, when exceeded gc becomes really aggressive. * The second limit is less certain. At the moment it allows only 2 entries per bucket. We will see. */
if (chain_length > ip_rt_gc_elasticity) { //超过hash表桶长度
*candp = cand->u.dst.rt_next;
rt_free(cand);
}
}
/* Try to bind route to arp only if it is output route or unicast forwarding path. */
//单播转发路由和本地生成数据包输出路由(iif = 0)
if (rt->rt_type == RTN_UNICAST || rt->fl.iif == ) {
//需要ARP来解析下一跳的L2地址。而转发目的地为广播地址,
//多播地址和本机地址则不需要ARP解析,因为使用其它方法可以解析得到这个地址
int err = arp_bind_neighbour(&rt->u.dst); //将路由项和arp绑定
if (err) {
spin_unlock_bh(rt_hash_lock_addr(hash));
if (err != -ENOBUFS) {
rt_drop(rt);
return err;
}
/* Neighbour tables are full and nothing can be released. Try to shrink route cache,
* it is most likely it holds some neighbour records. */
if (attempts-- > ) { //不再软中断中
int saved_elasticity = ip_rt_gc_elasticity;
int saved_int = ip_rt_gc_min_interval;
ip_rt_gc_elasticity = ;
ip_rt_gc_min_interval = ;
rt_garbage_collect(); //进行路由缓存的回收
ip_rt_gc_min_interval = saved_int;
ip_rt_gc_elasticity = saved_elasticity;
goto restart;
}
if (net_ratelimit())
printk(KERN_WARNING "Neighbour table overflow.\n"); rt_drop(rt);
return -ENOBUFS;
}
}
//插入到头
rt->u.dst.rt_next = rt_hash_table[hash].chain;
rt_hash_table[hash].chain = rt;
spin_unlock_bh(rt_hash_lock_addr(hash));
*rp = rt; //skb->dst指向这个路由缓存
return ;
}
给定一个设备dev,一个IP地址dst,和一个作用范围scope,返回作用范围为scope的第一个主地址,在通过出设备dev向地址dst发送报文时使用.
每个设备可能配置有多个地址,而且每个地址有各自的scope.
提供dst参数的原因在于,如果在设备dev上配置的不同IP地址属于不同子网,程序就可以返回与dst在同一子网的IP地址.
__be32 inet_select_addr(const struct net_device *dev, __be32 dst, int scope)
{
__be32 addr = ;
struct in_device *in_dev; rcu_read_lock();
in_dev = __in_dev_get_rcu(dev);
if (!in_dev)
goto no_in_dev; for_primary_ifa(in_dev) {
if (ifa->ifa_scope > scope) //地址范围要更广, > scope表示更窄
continue; if (!dst || inet_ifa_match(dst, ifa)) {
addr = ifa->ifa_local;
break;
} if (!addr)
addr = ifa->ifa_local;
} endfor_ifa(in_dev);
no_in_dev:
rcu_read_unlock(); if (addr)
goto out;
//如果在dev上配置的地址都不满足由scope和dst限定的条件,程序则尝试其他设备,检验是否存在一个IP地址,配置有所要求的scope。
//因为loopback_dev是dev_baselist链中所插入的第一个设备,所以首先检查的就是它.
read_lock(&dev_base_lock);
rcu_read_lock();
for_each_netdev(&init_net, dev) {//循环所有设备
if ((in_dev = __in_dev_get_rcu(dev)) == NULL)
continue; for_primary_ifa(in_dev) {
if (ifa->ifa_scope != RT_SCOPE_LINK && ifa->ifa_scope <= scope) {
addr = ifa->ifa_local;
goto out_unlock_both;
} } endfor_ifa(in_dev);
}
out_unlock_both:
read_unlock(&dev_base_lock);
rcu_read_unlock();
out:
return addr;
}
[/输入流程]
[输出流程]
用于出流量的路由查找,这些流量是由本地生成,可能被送往本地或被发送出去
int ip_route_output_key(struct rtable **rp, struct flowi *flp)
{
return ip_route_output_flow(rp, flp, NULL, );
}
int ip_route_output_flow(struct rtable **rp, struct flowi *flp, struct sock *sk, int flags)
{
int err; if ((err = __ip_route_output_key(rp, flp)) != )
return err;
if (flp->proto) {
if (!flp->fl4_src)
flp->fl4_src = (*rp)->rt_src; if (!flp->fl4_dst)
flp->fl4_dst = (*rp)->rt_dst; err = __xfrm_lookup((struct dst_entry **)rp, flp, sk, flags); //IPSEC安全路由 if (err == -EREMOTE)
err = ipv4_dst_blackhole(rp, flp, sk);
return err;
}
}
查找路由,当缓存查找路由失败时调用ip_route_output_slow
int __ip_route_output_key(struct rtable **rp, const struct flowi *flp)
{
unsigned hash;
struct rtable *rth;
//hash目的/源和外出接口索引
hash = rt_hash(flp->fl4_dst, flp->fl4_src, flp->oif); rcu_read_lock_bh();
//在路由高速缓存中查找
for (rth = rcu_dereference(rt_hash_table[hash].chain); rth; rth = rcu_dereference(rth->u.dst.rt_next)) {
if (rth->fl.fl4_dst == flp->fl4_dst && rth->fl.fl4_src == flp->fl4_src &&
rth->fl.iif == && rth->fl.oif == flp->oif && rth->fl.mark == flp->mark &&
!((rth->fl.fl4_tos ^ flp->fl4_tos) & (IPTOS_RT_MASK | RTO_ONLINK))) {
dst_use(&rth->u.dst, jiffies);
RT_CACHE_STAT_INC(out_hit);
rcu_read_unlock_bh();
*rp = rth; //找到
return ;
}
RT_CACHE_STAT_INC(out_hlist_search);
}
rcu_read_unlock_bh();
return ip_route_output_slow(rp, flp);
}
主要的路由解析函数
static int ip_route_output_slow(struct rtable **rp, const struct flowi *oldflp)
{
//#define RT_FL_TOS(oldflp) ((u32)(oldflp->fl4_tos & (IPTOS_RT_MASK | RTO_ONLINK)))
//调用方可以将fl4_tos字段的两个最低位(two least significant bits)用于存储flags,
//ip_route_output_slow可以使用该flags来确定待搜索路由项的scope。因为TOS字段不需要占用整个八位,所以这种方法是可行的。
u32 tos = RT_FL_TOS(oldflp);
//源IP地址、目的IP地址和防火墙标记是直接从函数的输入参数拷贝而来
struct flowi fl = { .nl_u = { .ip4_u =
{ .daddr = oldflp->fl4_dst,
.saddr = oldflp->fl4_src,
.tos = tos & IPTOS_RT_MASK,
.scope = ((tos & RTO_ONLINK) ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE),
} },
.mark = oldflp->mark,
.iif = init_net.loopback_dev->ifindex, //因为调用ip_route_output_slow只是为了路由本地生成的流量,
//所以搜索key fl中的源设备被初始化为回环设备
.oif = oldflp->oif };
struct fib_result res;
unsigned flags = ;
struct net_device *dev_out = NULL;
int free_res = ;
int err; res.fi = NULL;
#ifdef CONFIG_IP_MULTIPLE_TABLES
res.r = NULL;
#endif if (oldflp->fl4_src) { //有源地址
err = -EINVAL;
//源地址为,多播,非法或
if (MULTICAST(oldflp->fl4_src) || BADCLASS(oldflp->fl4_src) || ZERONET(oldflp->fl4_src))
goto out; /* It is equivalent to inet_addr_type(saddr) == RTN_LOCAL */
dev_out = ip_dev_find(oldflp->fl4_src); //找出地址所在设备
if (dev_out == NULL)
goto out; //没有指定外出设备且目的地址为多播或广播 if (oldflp->oif == && (MULTICAST(oldflp->fl4_dst) || oldflp->fl4_dst == htonl(0xFFFFFFFF))) {
fl.oif = dev_out->ifindex; //指定外出设备为源地址所在设备
goto make_route;
}
if (dev_out)
dev_put(dev_out); dev_out = NULL;
}
if (oldflp->oif) { //指定了外出设备
dev_out = dev_get_by_index(&init_net, oldflp->oif); //根据索引号找出设备
err = -ENODEV;
if (dev_out == NULL)
goto out; /* RACE: Check return value of inet_select_addr instead. */
if (__in_dev_get_rtnl(dev_out) == NULL) {
dev_put(dev_out);
goto out; /* Wrong error code */
}
//目的地址为多播或广播
if (LOCAL_MCAST(oldflp->fl4_dst) || oldflp->fl4_dst == htonl(0xFFFFFFFF)) {
if (!fl.fl4_src) //没有指定源,在外出设备上选择一个
fl.fl4_src = inet_select_addr(dev_out, , RT_SCOPE_LINK); goto make_route; }
if (!fl.fl4_src) {
if (MULTICAST(oldflp->fl4_dst))
fl.fl4_src = inet_select_addr(dev_out, , fl.fl4_scope);
else if (!oldflp->fl4_dst) //当报文被送往本地
fl.fl4_src = inet_select_addr(dev_out, , RT_SCOPE_HOST);
}
}
if (!fl.fl4_dst) { //没有指定目的
fl.fl4_dst = fl.fl4_src;
if (!fl.fl4_dst) //没有目的,说明源和目的都为0,这些报文被送往本地,而不是被发送出去
fl.fl4_dst = fl.fl4_src = htonl(INADDR_LOOPBACK); //设置为127.0.0.1 if (dev_out)
dev_put(dev_out); dev_out = init_net.loopback_dev; //回环设备
dev_hold(dev_out);
fl.oif = init_net.loopback_dev->ifindex;
res.type = RTN_LOCAL; //到本地的
flags |= RTCF_LOCAL;
goto make_route;
}
if (fib_lookup(&fl, &res)) { //查找
res.fi = NULL; //查找失败
if (oldflp->oif) { //有外出设备
if (fl.fl4_src == )
fl.fl4_src = inet_select_addr(dev_out, , RT_SCOPE_LINK); res.type = RTN_UNICAST; //单播
goto make_route;
}
if (dev_out)
dev_put(dev_out); err = -ENETUNREACH; //网络不可达
goto out;
}
free_res = ; //查找成功 if (res.type == RTN_LOCAL) { //路由结果为到本地
if (!fl.fl4_src)
fl.fl4_src = fl.fl4_dst; //没有源,就把源和目的相同 if (dev_out)
dev_put(dev_out); dev_out = init_net.loopback_dev; //回环设备
dev_hold(dev_out);
fl.oif = dev_out->ifindex;
if (res.fi)
fib_info_put(res.fi); res.fi = NULL;
flags |= RTCF_LOCAL;
goto make_route;
}
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (res.fi->fib_nhs > && fl.oif == ) //当查找返回的路由是一条多路径路由项时,需要选出下一跳
fib_select_multipath(&fl, &res);
else
#endif
//当res.prefixlen字段为0时表示是缺省路由,这表示“前缀长度”,即与该地址相关的网络掩码长度为0
//注意当搜索key指定了要使用的egress设备(fl.oif)时,不需要调用这两个程序。这时res已经包含了最终的转发决策。
if (!res.prefixlen && res.type == RTN_UNICAST && !fl.oif) //当查找返回的路由是缺省路由时,需要选择使用的缺省网关
fib_select_default(&fl, &res); if (!fl.fl4_src)
fl.fl4_src = FIB_RES_PREFSRC(res); if (dev_out)
dev_put(dev_out);
dev_out = FIB_RES_DEV(res);
dev_hold(dev_out);
fl.oif = dev_out->ifindex;
make_route:
//分配输出路由高速缓存,插入到hash表
err = ip_mkroute_output(rp, &res, &fl, oldflp, dev_out, flags);
if (free_res)
fib_res_put(&res);
if (dev_out)
dev_put(dev_out);
out:
return err;
}
选择缺省的网关
void fib_select_default(const struct flowi *flp, struct fib_result *res)
{
if (res->r && res->r->action == FR_ACT_TO_TBL && FIB_RES_GW(*res) && FIB_RES_NH(*res).nh_scope == RT_SCOPE_LINK) {
struct fib_table *tb;
if ((tb = fib_get_table(res->r->table)) != NULL)
tb->tb_select_default(tb, flp, res); //参考下面路由表函数实现
}
}
路由高速缓存分配,插入hash表
static inline int ip_mkroute_output(struct rtable **rp, struct fib_result* res, const struct flowi *fl,
const struct flowi *oldflp, struct net_device *dev_out, unsigned flags)
{
struct rtable *rth = NULL;
int err = __mkroute_output(&rth, res, fl, oldflp, dev_out, flags);
unsigned hash;
if (err == ) {
hash = rt_hash(oldflp->fl4_dst, oldflp->fl4_src, oldflp->oif); //计算hash值
err = rt_intern_hash(hash, rth, rp); //插入hash表
}
return err;
}
创建路由缓存
static inline int __mkroute_output(struct rtable **result, struct fib_result* res, const struct flowi *fl,
const struct flowi *oldflp, struct net_device *dev_out, unsigned flags)
{
struct rtable *rth;
struct in_device *in_dev;
u32 tos = RT_FL_TOS(oldflp);
int err = ;
//源地址为127.0.0.1且外出设备不是回环,出错
if (LOOPBACK(fl->fl4_src) && !(dev_out->flags&IFF_LOOPBACK))
return -EINVAL; if (fl->fl4_dst == htonl(0xFFFFFFFF)) //目的是广播
res->type = RTN_BROADCAST;
else if (MULTICAST(fl->fl4_dst)) //多播
res->type = RTN_MULTICAST;
else if (BADCLASS(fl->fl4_dst) || ZERONET(fl->fl4_dst)) //错误地址
return -EINVAL; if (dev_out->flags & IFF_LOOPBACK) //回环设备,到本地
flags |= RTCF_LOCAL;
/* get work reference to inet device */
in_dev = in_dev_get(dev_out);
if (!in_dev)
return -EINVAL; if (res->type == RTN_BROADCAST) { //路由结果类型为广播
flags |= RTCF_BROADCAST | RTCF_LOCAL; //加上本地
if (res->fi) {
fib_info_put(res->fi);
res->fi = NULL;
}
} else if (res->type == RTN_MULTICAST) { //类型为多播
flags |= RTCF_MULTICAST|RTCF_LOCAL;
if (!ip_check_mc(in_dev, oldflp->fl4_dst, oldflp->fl4_src, oldflp->proto))
flags &= ~RTCF_LOCAL; /* If multicast route do not exist use default one, but do not gateway in this case. Yes, it is hack. */
if (res->fi && res->prefixlen < ) {
fib_info_put(res->fi);
res->fi = NULL;
}
}
//分配高速缓存项
rth = dst_alloc(&ipv4_dst_ops);
if (!rth) {
err = -ENOBUFS;
goto cleanup;
}
atomic_set(&rth->u.dst.__refcnt, );
rth->u.dst.flags= DST_HOST;
if (IN_DEV_CONF_GET(in_dev, NOXFRM))
rth->u.dst.flags |= DST_NOXFRM;
if (IN_DEV_CONF_GET(in_dev, NOPOLICY))
rth->u.dst.flags |= DST_NOPOLICY; rth->fl.fl4_dst = oldflp->fl4_dst;
rth->fl.fl4_tos = tos;
rth->fl.fl4_src = oldflp->fl4_src;
rth->fl.oif = oldflp->oif;
rth->fl.mark = oldflp->mark;
rth->rt_dst = fl->fl4_dst;
rth->rt_src = fl->fl4_src;
rth->rt_iif = oldflp->oif ? : dev_out->ifindex; /* get references to the devices that are to be hold by the routing cache entry */
rth->u.dst.dev = dev_out;
dev_hold(dev_out);
rth->idev = in_dev_get(dev_out);
rth->rt_gateway = fl->fl4_dst;
rth->rt_spec_dst= fl->fl4_src; rth->u.dst.output = ip_output; RT_CACHE_STAT_INC(out_slow_tot); if (flags & RTCF_LOCAL) {
rth->u.dst.input = ip_local_deliver;
rth->rt_spec_dst = fl->fl4_dst;
} if (flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
rth->rt_spec_dst = fl->fl4_src;
if (flags & RTCF_LOCAL && !(dev_out->flags & IFF_LOOPBACK)) {
rth->u.dst.output = ip_mc_output;
RT_CACHE_STAT_INC(out_slow_mc);
}
#ifdef CONFIG_IP_MROUTE if (res->type == RTN_MULTICAST) {
if (IN_DEV_MFORWARD(in_dev) && !LOCAL_MCAST(oldflp->fl4_dst)) {
rth->u.dst.input = ip_mr_input;
rth->u.dst.output = ip_mc_output;
}
}
#endif
}
//给定一个路由缓存项rtable和一个路由表查找结果res,完成rtable内各字段的初始化,
//诸如rt_gateway、所嵌入的dst_entry结构的metrics向量和路由标签初始化等等.
rt_set_nexthop(rth, res, ); rth->rt_flags = flags; *result = rth;
cleanup:
/* release work reference to inet device */
in_dev_put(in_dev);
return err;
}
[/输出流程]
[多播路由处理]
目的地址是本地多播的处理函数
static int ip_route_input_mc(struct sk_buff *skb, __be32 daddr, __be32 saddr, u8 tos, struct net_device *dev, int our)
{
unsigned hash;
struct rtable *rth;
__be32 spec_dst;
struct in_device *in_dev = in_dev_get(dev);
u32 itag = ; /* Primary sanity checks. */
if (in_dev == NULL)
return -EINVAL;
//源是多播或粗物或回环地址,协议不是ip都是出错
if (MULTICAST(saddr) || BADCLASS(saddr) || LOOPBACK(saddr) || skb->protocol != htons(ETH_P_IP))
goto e_inval; if (ZERONET(saddr)) {
if (!LOCAL_MCAST(daddr))
goto e_inval; spec_dst = inet_select_addr(dev, , RT_SCOPE_LINK);
} else if (fib_validate_source(saddr, , tos, , dev, &spec_dst, &itag) < )
goto e_inval;
rth = dst_alloc(&ipv4_dst_ops);
if (!rth)
goto e_nobufs; rth->u.dst.output= ip_rt_bug; atomic_set(&rth->u.dst.__refcnt, );
rth->u.dst.flags= DST_HOST;
if (IN_DEV_CONF_GET(in_dev, NOPOLICY))
rth->u.dst.flags |= DST_NOPOLICY;
//缓存中记录内容
rth->fl.fl4_dst = daddr;
rth->rt_dst = daddr;
rth->fl.fl4_tos = tos;
rth->fl.mark = skb->mark;
rth->fl.fl4_src = saddr;
rth->rt_src = saddr;
#ifdef CONFIG_NET_CLS_ROUTE
rth->u.dst.tclassid = itag;
#endif
rth->rt_iif =
rth->fl.iif = dev->ifindex;
rth->u.dst.dev = init_net.loopback_dev;
dev_hold(rth->u.dst.dev);
rth->idev = in_dev_get(rth->u.dst.dev);
rth->fl.oif = ;
rth->rt_gateway = daddr;
rth->rt_spec_dst= spec_dst;
rth->rt_type = RTN_MULTICAST;
rth->rt_flags = RTCF_MULTICAST;
if (our) { //表示目的地址是本地配置的多播地址
rth->u.dst.input= ip_local_deliver;
rth->rt_flags |= RTCF_LOCAL;
}
#ifdef CONFIG_IP_MROUTE
if (!LOCAL_MCAST(daddr) && IN_DEV_MFORWARD(in_dev)) //需要转发的多播包
rth->u.dst.input = ip_mr_input;
#endif
RT_CACHE_STAT_INC(in_slow_mc); in_dev_put(in_dev);
hash = rt_hash(daddr, saddr, dev->ifindex);
return rt_intern_hash(hash, rth, (struct rtable**) &skb->dst); //添加到高速缓存表
e_nobufs:
in_dev_put(in_dev);
return -ENOBUFS;
e_inval:
in_dev_put(in_dev);
return -EINVAL;
}
[/多播路由处理]
[路由缓存操作实现]
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,
.entry_size = sizeof(struct rtable),
};
[/路由缓存操作实现]
[路由规则操作实现]
static struct fib_rules_ops fib4_rules_ops = {
.family = AF_INET,
.rule_size = sizeof(struct fib4_rule), //这结构中包含struct fib_rule结构
.addr_size = sizeof(u32),
.action = fib4_rule_action,
.match = fib4_rule_match,
.configure = fib4_rule_configure,
.compare = fib4_rule_compare,
.fill = fib4_rule_fill,
.default_pref = fib4_rule_default_pref,
.nlmsg_payload = fib4_rule_nlmsg_payload,
.flush_cache = fib4_rule_flush_cache,
.nlgroup = RTNLGRP_IPV4_RULE,
.policy = fib4_rule_policy,
.rules_list = LIST_HEAD_INIT(fib4_rules_ops.rules_list),
.owner = THIS_MODULE,
};
路由规则匹配函数
static int fib4_rule_match(struct fib_rule *rule, struct flowi *fl, int flags)
{
struct fib4_rule *r = (struct fib4_rule *) rule;
__be32 daddr = fl->fl4_dst;
__be32 saddr = fl->fl4_src;
//源/目的地址匹配
if (((saddr ^ r->src) & r->srcmask) || ((daddr ^ r->dst) & r->dstmask))
return ;
//top匹配
if (r->tos && (r->tos != fl->fl4_tos))
return ;
return ; //匹配
}
规则匹配后,调用这个动作函数
static int fib4_rule_action(struct fib_rule *rule, struct flowi *flp, int flags, struct fib_lookup_arg *arg)
{
int err = -EAGAIN;
struct fib_table *tbl; //表示一张路由表。不要将它与路由表缓存混淆。 switch (rule->action) { //规则动作类型
case FR_ACT_TO_TBL: //正常
break; case FR_ACT_UNREACHABLE: //丢弃发送不可达信息
err = -ENETUNREACH;
goto errout; case FR_ACT_PROHIBIT: //丢弃给出EACCES错误
err = -EACCES;
goto errout; case FR_ACT_BLACKHOLE: //丢弃包,没有通知
default:
err = -EINVAL;
goto errout;
}
//根据路由规则中的标识查找,路由表
if ((tbl = fib_get_table(rule->table)) == NULL)
goto errout;
//路由表的查询函数,参考下面路由表函数实现
err = tbl->tb_lookup(tbl, flp, (struct fib_result *) arg->result);
if (err > )
err = -EAGAIN;
errout:
return err;
}
struct fib_table *fib_get_table(u32 id)
{
struct fib_table *tb;
struct hlist_node *node;
unsigned int h; if (id == )
id = RT_TABLE_MAIN;
h = id & (FIB_TABLE_HASHSZ - ); //hash值根据id rcu_read_lock();
hlist_for_each_entry_rcu(tb, node, &fib_table_hash[h], tb_hlist) {
if (tb->tb_id == id) { //标识相同
rcu_read_unlock();
return tb;
}
}
rcu_read_unlock();
return NULL;
}
[/路由规则操作实现]
[路由表函数实现]
fib_new_table->fib_hash_init初始化一个路由表,置于谁添加的和怎样添加的参考用户命令配置实现
struct fib_table * fib_hash_init(u32 id)
{
struct fib_table *tb; if (fn_hash_kmem == NULL)
fn_hash_kmem = kmem_cache_create("ip_fib_hash", sizeof(struct fib_node), , SLAB_HWCACHE_ALIGN, NULL);
if (fn_alias_kmem == NULL)
fn_alias_kmem = kmem_cache_create("ip_fib_alias", sizeof(struct fib_alias), , SLAB_HWCACHE_ALIGN, NULL);
tb = kmalloc(sizeof(struct fib_table) + sizeof(struct fn_hash), GFP_KERNEL); if (tb == NULL)
return NULL; tb->tb_id = id;
tb->tb_lookup = fn_hash_lookup; //查询是否匹配
tb->tb_insert = fn_hash_insert; //向路由表添加一条新路由表项
tb->tb_delete = fn_hash_delete; //从路由表删除一条路由表项
tb->tb_flush = fn_hash_flush;
tb->tb_select_default = fn_hash_select_default; //选择默认路由
tb->tb_dump = fn_hash_dump;
memset(tb->tb_data, , sizeof(struct fn_hash));
return tb;
}
static int fn_hash_lookup(struct fib_table *tb, const struct flowi *flp, struct fib_result *res)
{
int err;
struct fn_zone *fz;
struct fn_hash *t = (struct fn_hash*)tb->tb_data; //路由表后面的hash结构 read_lock(&fib_hash_lock);
for (fz = t->fn_zone_list; fz; fz = fz->fz_next) { //循环所有的路由域
struct hlist_head *head;
struct hlist_node *node;
struct fib_node *f;
__be32 k = fz_key(flp->fl4_dst, fz); //fh_hash函数把这留下的子网部分地址换算成hash项
head = &fz->fz_hash[fn_hash(k, fz)]; //路由项头指针
hlist_for_each_entry(f, node, head, fn_hash) {
if (f->fn_key != k) //目的要匹配
continue; //详细检察匹配,返回0匹配成功
err = fib_semantic_match(&f->fn_alias, flp, res, f->fn_key, fz->fz_mask, fz->fz_order);
if (err <= )
goto out;
}
}
err = ;
out:
read_unlock(&fib_hash_lock);
return err;
}
用目的地址和这域中的子网掩码相与,留下掩码部分作为key
例如,如果正在检查/ zone,目的地址flp->fl4_dst为10.0.1.,则搜索key k为10.0.1. & 255.255.255.0,结果为10.0.1.。
这意味着接下来的代码要搜索到子网10.0.1./24的路由:
static inline __be32 fz_key(__be32 dst, struct fn_zone *fz)
{
return dst & FZ_MASK(fz); //((fz)->fz_mask)
}
根据这个key计算出hash值
static inline u32 fn_hash(__be32 key, struct fn_zone *fz)
{
u32 h = ntohl(key)>>( - fz->fz_order);
h ^= (h>>);
h ^= (h>>);
h ^= (h>>);
h &= FZ_HASHMASK(fz); //((fz)->fz_hashmask)
return h;
}
一个fib_node覆盖了同一子网内的所有路由项,但这些路由项在诸如TOS等其他字段上可能不同.
查找与匹配的fib_node相关联的fib_alias实例。如果找到相应的fib_alias实例,在配置多路径情况下fib_semantic_match还需要选择出正确的下一跳.
详细检察由这函数完成,用查找结果来初始化输入参数res.
int fib_semantic_match(struct list_head *head, const struct flowi *flp, struct fib_result *res, __be32 zone, __be32 mask, int prefixlen)
{
struct fib_alias *fa;
int nh_sel = ;
//轮询所有的别名
list_for_each_entry_rcu(fa, head, fa_list) {
int err; if (fa->fa_tos && fa->fa_tos != flp->fl4_tos) //tox如果有,必需匹配
continue; //scope比搜索key更窄的路由项是不可以的。
//例如,如果路由子系统查找scope为RT_SCOPE_UNIVERSE的路由,则不能使用scope为RT_SCOPE_LINK的路由项。
if (fa->fa_scope < flp->fl4_scope)
continue; //该标志的设置与fib_alias是否被选中无关。当fib_alias实例被删除时,根据该标志来决定是否应当flush缓存。
fa->fa_state |= FA_S_ACCESSED; //该数组的每一个元素针对一种路由类型,每个元素包含一个相关的错误码和一个路由作用范围RT_SCOPE_XXX。
//以fa->fa_type为索引,就可以从数组fib_props得出该路由类型对应的错误码和路由作用范围(scope)。
err = fib_props[fa->fa_type].error; //路由类型是否正确
if (err == ) {
struct fib_info *fi = fa->fa_info; if (fi->fib_flags & RTNH_F_DEAD) //路由子系统已经设置RTNH_F_DEAD标志来标记该路由项应当被删除
continue; switch (fa->fa_type) {
case RTN_UNICAST:
case RTN_LOCAL:
case RTN_BROADCAST:
case RTN_ANYCAST:
case RTN_MULTICAST:
for_nexthops(fi) {
if (nh->nh_flags & RTNH_F_DEAD) //下一跳不可用
continue; if (!flp->oif || flp->oif == nh->nh_oif) //搜索key指定的egress设备与下一跳配置的不匹配
break;
}
#ifdef CONFIG_IP_ROUTE_MULTIPATH
if (nhsel < fi->fib_nhs) { //找到匹配的下一跳
nh_sel = nhsel;
goto out_fill_res;
}
#else
if (nhsel < ) { //不支持多路径,则只能有一个下一跳
goto out_fill_res;
}
#endif
endfor_nexthops(fi);
continue;
default:
printk(KERN_DEBUG "impossible 102\n");
return -EINVAL;
} }
return err;
}
return ; //没有匹配的路由
out_fill_res: //初始化结果
res->prefixlen = prefixlen; //掩码长度
res->nh_sel = nh_sel;
res->type = fa->fa_type;
res->scope = fa->fa_scope;
res->fi = fa->fa_info;
atomic_inc(&res->fi->fib_clntref);
return ; //成功返回
}
fn_hash_select_default接收一个结构为fib_result的res作为输入参数,该参数是前面调用fib_lookup而返回的路由查找结果。
fn_hash_select_default使用该结构作为缺省路由搜索的起点。
static void fn_hash_select_default(struct fib_table *tb, const struct flowi *flp, struct fib_result *res)
{
int order, last_idx;
struct hlist_node *node;
struct fib_node *f;
struct fib_info *fi = NULL;
struct fib_info *last_resort;
struct fn_hash *t = (struct fn_hash*)tb->tb_data;
struct fn_zone *fz = t->fn_zones[]; //缺省路由域 if (fz == NULL)
return; last_idx = -;
last_resort = NULL;
order = -; read_lock(&fib_hash_lock);
hlist_for_each_entry(f, node, &fz->fz_hash[], fn_hash) { //遍历所有路由项
struct fib_alias *fa;
list_for_each_entry(fa, &f->fn_alias, fa_list) { //该项的所有别名
struct fib_info *next_fi = fa->fa_info;
//scope必须相同,类型必须为单播
if (fa->fa_scope != res->scope || fa->fa_type != RTN_UNICAST)
continue; //权限要小于等于res指定的权限
if (next_fi->fib_priority > res->fi->fib_priority)
break; //下一跳的scope为RT_SCOPE_LINK(即必须直连)
if (!next_fi->fib_nh[].nh_gw || next_fi->fib_nh[].nh_scope != RT_SCOPE_LINK)
continue; fa->fa_state |= FA_S_ACCESSED; if (fi == NULL) {
if (next_fi != res->fi)
break;
} else if (!fib_detect_death(fi, order, &last_resort, &last_idx, &fn_hash_last_dflt)) {
if (res->fi)
fib_info_put(res->fi); res->fi = fi;
atomic_inc(&fi->fib_clntref);
fn_hash_last_dflt = order;
goto out;
}
fi = next_fi;
order++;
}
}
if (order <= || fi == NULL) {
fn_hash_last_dflt = -;
goto out;
}
//选择路由项也要考虑下一跳的状态是否可达。fib_detect_death函数将路由项中L3地址已经被解析为L2地址的
//下一跳(即状态为NUD_REACHABLE)给予更高的优先级。该检查可以确保如果当前所用的缺省路由的下一跳网关
//不可达而使该路由项不可用,则需要选择新的路由项。
if (!fib_detect_death(fi, order, &last_resort, &last_idx, &fn_hash_last_dflt)) {
if (res->fi)
fib_info_put(res->fi); res->fi = fi;
atomic_inc(&fi->fib_clntref);
fn_hash_last_dflt = order;
goto out;
}
if (last_idx >= ) {
if (res->fi)
fib_info_put(res->fi); res->fi = last_resort;
if (last_resort)
atomic_inc(&last_resort->fib_clntref);
}
fn_hash_last_dflt = last_idx;
out:
read_unlock(&fib_hash_lock);
}
[/路由表函数实现]

Linux IP 路由实现的更多相关文章

  1. 彻底理解Cisco/Linux/Windows的IP路由

    -1.只要理解实质,名称并不重要! 很多使用Linux的网络高手在面对Cisco管理员的诸如管理距离,路由度量等词汇时,还没有PK就自觉败下阵来了.我觉得这实在太可惜了,大家本是一家,为何这么为难对方 ...

  2. Linux基础命令---IP路由操作

    ip ip指令可以显示或操作路由.网路设备,设置路由策略和通道. 此命令的适用范围:RedHat.RHEL.Ubuntu.CentOS.SUSE.openSUSE.Fedora.   1.语法     ...

  3. Linux ip forward

    Linux 默认带有 ip forward 功能,只不过因为各种原因,默认的配置把该功能关闭了.本文通过 demo 来演示 Linux 的 ip forward 功能,具体场景为:开启 Linux 的 ...

  4. Linux下路由配置梳理

    在日常运维作业中,经常会碰到路由表的操作.下面就linux运维中的路由操作做一梳理:---------------------------------------------------------- ...

  5. Linux开启路由的方法

    Linux开启路由的命令很简单,只需要一条命令即可: [root@localhost ~]# echo 1 > /proc/sys/net/ipv4/ip_forward 这个只是临时修改,如果 ...

  6. linux 下路由配置

    转自 https://www.cnblogs.com/kevingrace/p/6490627.html 在日常运维作业中,经常会碰到路由表的操作.下面就linux运维中的路由操作做一梳理:----- ...

  7. linux的路由功能实现

    参考URL: https://blog.csdn.net/chengqiuming/article/details/80140768 一,启用Linux的路由转发功能. 二,新建veth pair 三 ...

  8. Linux下路由配置梳理(转)

    转自:https://www.cnblogs.com/kevingrace/p/6490627.html 在日常运维作业中,经常会碰到路由表的操作.下面就linux运维中的路由操作做一梳理:----- ...

  9. 第一种SUSE Linux IP设置方法

    第一种SUSE Linux IP设置方法ifconfig eth0 192.168.1.22 netmask 255.255.255.0 uproute add default gw 192.168. ...

随机推荐

  1. DOM4J方式解析XML文件

    dom4j介绍 dom4j的项目地址:http://sourceforge.net/projects/dom4j/?source=directory dom4j是一个简单的开源库,用于处理XML. X ...

  2. ubuntu恢复rm -rf误删文件

    使用extundelete工具 sudo apt-get install extundelete 恢复操作命令 首先需要umount或者read only 分区  umount /dev/partit ...

  3. UITableViewCell左滑的时候添加多个按钮的方法(iOS8+)以及UIRefreshControl(iOS6+)的使用。

    之前想在cell左滑的时候添加更多的按钮而不是只有‘删除’按钮如下所示,貌似不是一件简单的事.但是现在只要实现几个方法就行了. 代码写的比较垃圾,重在理解这个知识.. . 具体代码: // //  T ...

  4. (转)TCP注册端口号大全

    分类: 网络与安全 cisco-sccp 2000/tcp Cisco SCCPcisco-sccp 2000/udp Cisco SCCp# Dan Wing <dwing&cisco ...

  5. From MSI to WiX, Part 1 - Required properties, by Alex Shevchuk

    Following content is directly reprinted from From MSI to WiX, Part 1 - Required properties Author: A ...

  6. VMware10.0.4下 CentOS 6.5 cmake安装 MySQL 5.5.32

    一.准备工作 1.1.创建 zhuzz/tools目录 [root@localhost ~]# mkdir -p /home/zhuzz/tools [root@localhost ~]# cd /h ...

  7. leetcode problem 33 -- Search in Rotated Sorted Array

    Suppose a sorted array is rotated at some pivot unknown to you beforehand. (i.e., 0 1 2 4 5 6 7 migh ...

  8. 未指定的错误,发生了一个 Oracle 错误,但无法从 Oracle 中检索错误信息。数据类型不被支持。

    未指定的错误,发生了一个 Oracle 错误,但无法从 Oracle 中检索错误信息.数据类型不被支持. 博客分类: 雅芳生涯 .Net VB C# OracleMicrosoftSecurity  ...

  9. centos用yum安装mysql-server

    1.安装:#yum -y install mysql-server 2.修改配置:#vi /etc/my.cnf 暂时修改一下编码(添加在密码下方添加): default-character-set ...

  10. PHP 提取图片img标记中的任意属性

    PHP 提取图片img标记中的任意属性的简单实例. 复制代码代码如下: <?php /* PHP正则提取图片img标记中的任意属性 */ $str = '<center><im ...