深入理解Linux网络技术内幕 第32章 路由-Linux的实现

主要数据结构

路由代码定义和使用的主要数据结构中的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;

对策略数据库的影响

一个策略可以与一个设备相关联。当设备被注销时,其相关的所有策略都要标记位不可用。而当设备注册时,如果有与该设备关联的未激活的策略,则需要重新激活。

上一篇:Python 模块——__name__属性和dir() 函数


下一篇:python 并发专题(四):yield以及 yield from