ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

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

2020-11-29 22:02:27  阅读:274  来源: 互联网

标签:struct 32 dev fib ifa 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;

对策略数据库的影响

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

标签:struct,32,dev,fib,ifa,Linux,路由,路由表
来源: https://blog.csdn.net/weixin_44793395/article/details/109568348

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有