主要数据结构
路由代码定义和使用的主要数据结构中的rt代表路由(route),fib转发信息库(Forwarding Informations Base),fn功能(function)。
- struct ip_rt_acct
该结构被路由表的分类器使用,用于跟踪和一个标签(tag)关联的路由上的流量统计信息,统计信息包括报文个数和字节数。
这个结构初始化为长度256的数组。因为路由标签的取值范围是0~255。
struct ip_rt_acct {
__u32 o_bytes;
__u32 o_packets;
__u32 i_bytes;
__u32 i_packets;
};
ip_rt_acct = __alloc_percpu(256 * sizeof(struct ip_rt_acct), __alignof__(struct ip_rt_acct));
if (!ip_rt_acct)
panic("IP: failed to allocate ip_rt_acct\n");
- struct rt_cache_stat
存储路由查找的统计信息。每个处理器有该数据结构的一个实例。
struct rt_cache_stat {
unsigned int in_slow_tot;
unsigned int in_slow_mc;
unsigned int in_no_route;
unsigned int in_brd;
unsigned int in_martian_dst;
unsigned int in_martian_src;
unsigned int out_slow_tot;
unsigned int out_slow_mc;
};
- struct inet_peer
维护远程IP端点的长期信息。 - struct fib_result
路由表查找后返回该结构。 - struct fib_rule
表示策略路由在路由流量时选择路由表的规则。
下边给出构造路由表的数据结构。
- struct fib_table
表示一张路由表 - struct fib_info
不同路由表之间可以共享一些参数,这些参数被存储在该数据结构内。 - struct fib_nh
表示下一跳,一个路由通常只有一个下一跳,但当内核支持多路径特性时,可以对一条路由配置多个下一跳。 - struct fn_hash
该结构包含33个fn_zone链表头指针以及一个链表。 - struct dst_entry
表示路由表缓存中与协议无关的部分。 - struct dst_ops
表示DST核心代码使用虚函数表(VFT),该表用于向三层协议通知特定的事件。 - struct rtable
在IPv4中表示一条路由表缓存项的数据结构。
地址scope与路由Scope
路由scope
- RT_SCOPE_NOWHERE
路由项不通往任何地方,意味没有到达目的地路由。 - RT_SCOPE_HOST
为本地接口配置IP地址自动创建的路由表项。 - RT_SCOPE_LINK
为本地接口配置地址时,派生的目的地为本地网络和子网广播地址的路由表项的scope。 - RT_SCOPE_UNIVERSE
所有通往远程非直连目的地的路由表项。
地址scope
地址scope保存在struct in_ifaddr中的ifa_scope字段。设备上配置的每一个IP地址对应一个该结构。
路由表项下一跳网关由一个fib_nh结构描述,该结构中nh_scope表示该地址的scope,nh_gw代表下一跳网关的IP地址。
路由scope与下一跳scope之间关系
全局锁
路由代码使用锁保护竞争条件。
- fib_hash_lock 该锁保护所有路由表,防止被并发写入,但是这并不是一个瓶颈,因为配置改变的频率不高。
- fib_info_lock 这个读写锁保护所有的fib_info
路由子系统初始化
路由子系统由ip_rt_init函数初始化。这个函数主要初始化路由系统使用的缓存和全局变量。在ip_rt_init函数中重点做的时候包括
- 创建ip_dst_cache
- ip_fib_init初始化默认路由表并注册两个通知链
- devinet_init 注册netdev通知链
外部事件
路由子系统需要感知网卡和外部网络变化以调整路由表和路由缓存。路由子系统感兴趣的事件:
- 网络设备状态变化
- 网络设备上IP配置变化。
IP配置变化
设备的IP配置发生变化,路由子系统收到一个通知,并运行函数fib_inetaddr_event处理该通知。
- NETDEV_UP
本地设备已经配置了一个新的IP地址。调用fib_add_ifaddr函数将必要的路由项添加到local_table路由表中。 - NETDEV_DOWN
本地设备上删除一个IP地址,调用fib_del_ifaddr函数将与该地址相关的路由项删除。
rt_cache_flush函数用于触发一次刷新路由缓存。
static int fib_inetaddr_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct in_ifaddr *ifa = (struct in_ifaddr *)ptr;
struct net_device *dev = ifa->ifa_dev->dev;
struct net *net = dev_net(dev);
switch (event) {
case NETDEV_UP:
fib_add_ifaddr(ifa);
#ifdef CONFIG_IP_ROUTE_MULTIPATH
fib_sync_up(dev, RTNH_F_DEAD);
#endif
atomic_inc(&net->ipv4.dev_addr_genid);
rt_cache_flush(dev_net(dev));
break;
case NETDEV_DOWN:
fib_del_ifaddr(ifa, NULL);
atomic_inc(&net->ipv4.dev_addr_genid);
if (!ifa->ifa_dev->ifa_list) {
/* Last address was deleted from this interface.
* Disable IP.
*/
fib_disable_ip(dev, event, true);
} else {
rt_cache_flush(dev_net(dev));
}
break;
}
return NOTIFY_DONE;
}
添加一个IP地址
每当一个接口上配置了一个IP地址时,内核将一组特殊的路由项添加到一个独立的ip_fib_local_table的路由表中,fib_add_ifaddr函数负责添加。
这个函数首先调用fib_magic函数将IP地址插入到路由表中
void fib_add_ifaddr(struct in_ifaddr *ifa)
{
struct in_device *in_dev = ifa->ifa_dev;
struct net_device *dev = in_dev->dev;
struct in_ifaddr *prim = ifa;
__be32 mask = ifa->ifa_mask;
__be32 addr = ifa->ifa_local;
__be32 prefix = ifa->ifa_address & mask;
if (ifa->ifa_flags & IFA_F_SECONDARY) {
prim = inet_ifa_byprefix(in_dev, prefix, mask);
if (!prim) {
pr_warn("%s: bug: prim == NULL\n", __func__);
return;
}
}
fib_magic(RTM_NEWROUTE, RTN_LOCAL, addr, 32, prim, 0);
接下来处理广播地址的情况。
if (!(dev->flags & IFF_UP))
return;
/* Add broadcast address, if it is explicitly assigned. */
if (ifa->ifa_broadcast && ifa->ifa_broadcast != htonl(0xFFFFFFFF))
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, ifa->ifa_broadcast, 32,
prim, 0);
if (!ipv4_is_zeronet(prefix) && !(ifa->ifa_flags & IFA_F_SECONDARY) &&
(prefix != addr || ifa->ifa_prefixlen < 32)) {
if (!(ifa->ifa_flags & IFA_F_NOPREFIXROUTE))
fib_magic(RTM_NEWROUTE,
dev->flags & IFF_LOOPBACK ? RTN_LOCAL : RTN_UNICAST,
prefix, ifa->ifa_prefixlen, prim,
ifa->ifa_rt_priority);
/* Add network specific broadcasts, when it takes a sense */
if (ifa->ifa_prefixlen < 31) {
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix, 32,
prim, 0);
fib_magic(RTM_NEWROUTE, RTN_BROADCAST, prefix | ~mask,
32, prim, 0);
}
}
删除一个IP地址
当从一个接口删除一个IP地址时,路由子系统得知通知清理路由表和路由缓存。由fib_del_ifaddr函数实现这个功能。
设备状态改变
当一个设备的状态或某些配置发生变化时,路由子系统收到通知并调用fib_netdev_event处理该事件。
对路由表的影响
当event是NETDEV_UNREGISTER,一个设备被注销,从路由表中删除使用这个设备的所有路由,如果多径路由的下一跳有一个使用该设备,则该路由项也被删除。
NETDEV_UNREGISTER
static int fib_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
{
struct net_device *dev = netdev_notifier_info_to_dev(ptr);
struct netdev_notifier_changeupper_info *upper_info = ptr;
struct netdev_notifier_info_ext *info_ext = ptr;
struct in_device *in_dev;
struct net *net = dev_net(dev);
unsigned int flags;
if (event == NETDEV_UNREGISTER) {
fib_disable_ip(dev, event, true);
rt_flush_dev(dev);
return NOTIFY_DONE;
}
- NETDEV_UP
启动一个设备时,将该设备上所有IP地址有关的路由表项添加到本地路由表。通过为网卡上的每一个IP地址调用fib_add_ifaddr函数实现。 - NETDEV_DOWN
关闭一个设备时,调用fib_disable_ip函数从路由表删除使用该设备的所有路由。
in_dev = __in_dev_get_rtnl(dev);
if (!in_dev)
return NOTIFY_DONE;
switch (event) {
case NETDEV_UP:
for_ifa(in_dev) {
fib_add_ifaddr(ifa);
} endfor_ifa(in_dev);
#ifdef CONFIG_IP_ROUTE_MULTIPATH
fib_sync_up(dev, RTNH_F_DEAD);
#endif
atomic_inc(&net->ipv4.dev_addr_genid);
rt_cache_flush(net);
break;
case NETDEV_DOWN:
fib_disable_ip(dev, event, false);
break;
}
其他的是设备的配置被更新需要刷新路由表缓存。如修改MTU等。
case NETDEV_CHANGE:
flags = dev_get_flags(dev);
if (flags & (IFF_RUNNING | IFF_LOWER_UP))
fib_sync_up(dev, RTNH_F_LINKDOWN);
else
fib_sync_down_dev(dev, event, false);
rt_cache_flush(net);
break;
case NETDEV_CHANGEMTU:
fib_sync_mtu(dev, info_ext->ext.mtu);
rt_cache_flush(net);
break;
case NETDEV_CHANGEUPPER:
upper_info = ptr;
/* flush all routes if dev is linked to or unlinked from
* an L3 master device (e.g., VRF)
*/
if (upper_info->upper_dev &&
netif_is_l3_master(upper_info->upper_dev))
fib_disable_ip(dev, NETDEV_DOWN, true);
break;
}
return NOTIFY_DONE;
对策略数据库的影响
一个策略可以与一个设备相关联。当设备被注销时,其相关的所有策略都要标记位不可用。而当设备注册时,如果有与该设备关联的未激活的策略,则需要重新激活。