• 内核IPv4路由选择子系统(简述)


    主要参考了《深入Linux内核架构》和《精通Linux内核网络》相关章节

    IPv4路由选择子系统

    图30-1给出了在网络协议栈中路由子系统的位置(灰色框),这张图没有包含所有的细节(Netfilter、网桥等),但给出了横跨路由的其他主要的内核子系统。

    image-20220727010714646

    本节重点介绍IPv4路由选择子系统及其使用的主要数据结构,如路由选择表、转发信息库(ForwardingInformation Base,FIB)和FIB别名、FIB TRIE等(顺便说一句,TRIE并非首字母缩写,而是由单词retrieval衍生而来的)。TRIE是一种特殊的树,它替代了FIB散列表。你将在本章学习路由选择子系统查找是如何进行的、在什么情况下生成ICMP重定向消息及如何生成它们、为何将路由选择缓存代码删除。

    转发和FIB(ForwardingInformation Base)

    简单地讲,路由器就是一台网络设备,它配备多个网络接口卡(Network Interface Card,NIC)、能利用它的网络知识正确转发入口流量。

    决定一个入口封包应当送给本地主机还是转发所需要的信息,以及在转发时正确转发封包所需要的信息,都存储在一个被称为转发信息库(Forwarding Information Base,FIB)的数据库中,它通常被简称为路由表(routing table)。

    ​ Linux网络栈最重要的目标之一是转发流量,对于Internet骨干中的核心路由器来说尤其如此。**Linux IP栈层被称为路由选择子系统,负责转发数据包和维护转发数据库。**对于小型网络,管理FIB的工作可由系统管理员手工完成,因为这种类型的网络拓扑几乎是静态的。而对于核心路由器来说,情况则有所不同,因为其拓扑是动态的,很多信息都在不断变化。在这种情况下,管理FIB的工作通常由用户空间路由选择守护程序负责,有时还结合使用特殊的硬件改进。这些用户空间守护程序通常用于维护独立的路由选择表,偶尔还会与内核路由选择表进行交互。

    ​ 先来介绍基本知识:何为路由选择?来看一个非常简单的转发示例。你有两个以太网局域网LAN1和LAN2。其中,LAN1包含子网192.168.1.0/24,而LAN2包含子网192.168.2.0/24。在这两个LAN之间,有一台转发路由器,它有两个以太网网络接口卡,其中连接到LAN1的网络接口为etho,其IP地址为192.168.1.200,而连接到LAN2的网络接口为eth1,其IP地址为192.168.2.200,如图5-1所示。出于简化考虑,咱们假设转发路由器没有运行防火墙守护程序。你开始从LAN1向LAN2发送流量。对到来的数据包(它们从LAN1发送到LAN2或反向发送)进行转发的过程被称为路由选择,是根据被称为路由选择表的数据结构进行的。本章和下一章都将对这个过程以及路由选择表进行讨论。

    image-20220726160922833

    ​ 在图5-1中,来自LAN1并前往LAN2的数据包到达etho后,通过外出设备eth1转发出去。在这个过程中,来到转发路由器的数据包从内核网络栈的第2层(数据链路层)移到第3层(网络层)。然而,不同于目的地为转发路由器的流量,无需将这些数据包移到第4层(传输层),因为这些流量无需由第4层的传输套接字进行处理,而应直接转发。移到第4层有一定的性能开销,最好尽可能避免。这些流量在第3层被处理。根据转发路由器配置的路由选择表,将这些数据包从出站接口eth1转发出去或将它们丢弃。

    ​ 这里还需要说说另外两个常见的路由选择术语:默认网关和默认路由。在路由选择表中指定了默认网关条目时,不与其他路由选择条目匹配的数据包都将转发到默认网关,不管其IP报头中的目标地址是什么。使用无类域间路由选择(Classless Inter-Domain Routing,CIDR)表示法时,默认路由用0.0.0.0/0表示。

    作为一个简单的示例,可按如下方法将IPv4地址为192.168.2.1的机器指定为默认网关。
    ip route add default via 192.168.2.1
    也可以使用如下route命令。
    route add default gateway 192.168.2.1

    本节介绍了转发的含义,并通过一个简单示例演示了如何在两个LAN之间转发数据包。还介绍了默认网关和默认路由是什么以及如何添加它们。至此,你了解了基本术语以及转发的含义,下面来看看如何在路由选择子系统中进行查找。

    在路由选择子系统中进行查找 fib_lookup

    ​ 接收或发送每个数据包时,都必须在路由选择子系统中进行查找。在3.6版之前的内核中,无论在接收还是传输路径中,**查找都包含两个阶段:首先在路由选择缓存中查找;如果没有找到,再在路由选择表中查找(路由选择缓存将在5.4.3节讨论)。**查找工作由方法fib_lookup()完成。如果在路由选择子系统中找到了匹配的条目,方法fib_lookup()将创建一个包含各种路由选择参数的fib_result对象,并返回0。本节以及本章的其他部分都将讨论到fib_result对象。fib_lookup()的原型如下。

    int fib_lookup(struct net *net,const struct flowi4 *flp,struct fib_result *res)

    • **flowi4对象包含对IPv4路由选择查找过程至关重要的字段,如目标地址、源地址、服务类型(TOS)等。事实上,flowi4对象定义了要在路由选择表中查找的键,必须在使用方法fib_lookup()执行查找前对其进行初始化。**对于IPv6,有一个类似的对象——flowi6。这两种对象都是在include/net/flow.h中定义的。

    • fib_result对象是在查找过程中生成的。

    • 方法fib_lookup()首先在本地FIB表中搜索。如果没有找到,再在主FIB表中查找。

      • 无论是在接收路径还是传输路径中,查找成功后都将创建一个dst对象(include/netdst.h中定义的结构dst_entry的实例,表示目标缓存)。稍后你将看到,dst对象将被嵌入到结构rtable中。
        • 事实上,rtable对象表示一个路由选择条目,可与SKB相关联。**在dst_entry对象中,最重要的成员是两个回调函数: input和output。**在路由选择查找过程中,需要根据路由选择查找结果将这两个回调函数设置为合适的处理程序。这两个回调函数将SKB作为唯一的参数。

    rtable 一条路由表缓存项(与SKB关联)

    include\net\route.h

    // 路由选择条目
    struct rtable {
    	struct dst_entry	dst; // 目录缓存,有input、output两个回调函数
    
    	int			rt_genid;
    	unsigned int		rt_flags;
    	__u16			rt_type;
    	__u8			rt_is_input; // 一个标志,对于输入路由设置为1
    	__u8			rt_uses_gateway; // 如果下一个为网关为1,下一个为直接路由为0
    
    	int			rt_iif;
    
    	/* 有关邻居的信息 */
    	__be32			rt_gateway;
    
    	/* Miscellaneous cached information */
    	u32			rt_pmtu; // 路径mtu
    
    	u32			rt_table_id;
    
    	struct list_head	rt_uncached;
    	struct uncached_list	*rt_uncached_list;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • rt_flags : rtable对象的标志。以下是一些重要的标志。
      • RTCF_BROADCAST:被设置时,表明目标地址为广播地址。在方法_mkroute_output()和ip_route_input_slow()中设置这个标志。
      • RTCF_MULTICAST:被设置时,表明目标地址为组播地址。在方法ip_route_input_mc()和_mkroute_output()中设置这个标志。
      • RTCF_DOREDIRECT:被设置时,表明必须发送一条ICMPv4重定向消息来对到来的数据包做出响应。仅当满足了多个条件时才设置这个标志,其中包括人站设备和出站设备相同,以及设置了对应的procfs条目( send_redirects)等。正如你将在本章后面看到的,还有其他一些条件。这个标志是在方法_mkroute_input()中设置的。
      • RTCF_LOCAL:被设置时,表明目的地为当前主机。这个标志是在下述方法中设置的;ip_route_input_slow()、_mkroute_output()、ip_route_input_mc()和_ip_route_output_key()。可同时设置一些RTCF_XXX标志。例如,设置了RTCF_BROADCAST或RTCF_MULTICAST时,可同时设置RTCF_LOCAL。完整的RTCF_XXX标志清单,请参阅include/uapi/linux/in_route.h。请注意,清单中的一些标志未被使用。
    • rt_is_input :一个标志,对于输人路由设置为1。
    • rt_uses_gateway:按如下方式设置其值。
      如果下一条为网关,rt_uses_gateway为1。如果下一条为直连路由,rt_uses_gateway为0。
    • rt_iif:入站接口的ifindex(请注意,内核3.6删除了结构rtable的成员rt_oif,该成员被设置为指定流键( flow key )的oif,但只有一个方法可使用它)。
    • rt_pmtu:路径MTU(路途上最小的MTU )。

    请注意,内核3.6新增了方法fib_compute_spec_dst(),它将一个SKB作为参数。这使得结构rtable的成员rt_spec_dst显得多余,因此将其删除了。在特殊情况下,例如,在方法icmp_reply()中将收到的数据包的源地址作为目标地址向发送方发送应答时,需要使用方法fib_compute_ spec_dst()

    ​ **对于目的地为当前主机的单播数据包,将dst对象的input回调函数设置为ip_local_deliver();而对于需要转发的单播数据包,将input回调函数设置为ip_forward()。对于由当前主机生成并要向外发送的数据包,将output回调函数设置为ip_output()。**对于组播数据包,在有些情况下(本章不详细讨论)将input回调函数设置为ip_mr_input()。正如你将在本章后面的PROHIBIT规则示例中看到的,在有些情况下会将input回调函数设置为ip_error()。下面来看看fib_result对象。

    fib_result 路由选择查找结果

    对路由表查找后返回该结构。它的内容并不是简单地包含下一跳信息,而且包含有其他特性。例如,策略路由所需的更多参数。

    struct fib_result {
    	unsigned char	prefixlen; // 前缀长度
    	unsigned char	nh_sel; // 下一跳数量
    	unsigned char	type; // 处理数据包的方式
    	unsigned char	scope;
    	u32		tclassid;
    	struct fib_info *fi; // 指向路由选择条目(存储了指向下一跳的引用)
    	struct fib_table *table; // FIB表
    	struct hlist_head *fa_head; // 使用fib_alias对象旨在优化路由选择条目
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • prefixlen:前缀长度,表示子网掩码,其取值为0~32。使用默认路由时,其值为0;
      • 使用命令ip route add 192.168.2.0/24 dev etho添加路由选择条目时,其值为24,这是根据添加路由选择条目时指定的子网掩码确定的。prefixlen是在方法 check_leaf()( net/ipv4/fib_trie.c)中设置的。
    • nh_sel:下一跳数量。只有一个下一跳时,其值为0;使用多路径路由选择(MultipathRouting)时,可能存在多个下一跳。下一跳对象存储在路由选择条目
      (fib_info对象)的一个数组中,将在下一节对此进行讨论。
    • type: fib_result对象中最重要的字段,它决定了处理数据包的方式,即:将数据包转发给其他机器、由当前主机接收、默默地丢弃、丢弃并发送一条ICMPv4消息等。
      • fib_result对象的类型是根据数据包的内容(其中最重要的是目标地址)以及管理员、路由选择守护程序或重定向消息设置的路由选择规则确定的。在本章和下一章,你将看到fib_result对象的类型是如何在查找过程中确定的。
      • 最常见的两种fib_result对象类型为RTN_UNICAST和RTN_LOCAL。其中,前者表示数据包需要通过网关或直连路由进行转发,后者表示数据包是发送给当前主机的。
      • 在本书中,你将见到的其他类型包括RTN_BROADCAST(表示当前主机应接收的广播数据包)、RTN_MULTICAST(表示组播路由)、RTN_UNREACHABLE(表示会引起一条ICMPv4“目的地不可达消息”发回的数据包)等。总共有12种路由类型,完整的清单请参阅include/uapi/linux/rtnetlink.h。
    • fi:一个指向fib_info对象(表示路由选择条目)的指针。fib_info对象存储了指向下一跳的引用( fib_nh )。结构fib_info将在5.3节讨论。
    • table:一个指针,指向用于查找的FIB表,是在方法check_leaf() ( net/ipv4/fib_trie.c )中设置的。
    • fa_head:一个指针,指向一个fib_alias列表(从而将其与路由关联起来)。使用fib_alias对象旨在优化路由选择条目,避免发生不顾存在其他极其相似的fib_info对象而为每个路由选择条目创建fib_info对象的情况。所有FIB别名都按fa_tos降序、按fib_priority(指标)升序排列。fa_tos为0的别名排在最后,与任意TOS都匹配。结构fib_alias将在5.3.4节讨论。

    相关数据结构

    fib_table 路由选择表

    路由表是路由子系统的核心。简单地说,它是由一个路由数据库组成,IPv4的其他子系统通过各种函数可以使用该数据库,其中最重要的函数是路由查找。

    FIB表

    路由选择子系统的主数据结构是路由选择表,由结构fib_table表示。

    ​ 简单地说,路由选择表的每个条目都指定了前往特定子网(或特定IPv4目标地址)的流量所对应的下一跳。当然,这些条目还包含本章将讨论的其他参数。**每个路由选择条目都包含一个fib_info对象( include/net/ip_fib.h ),其中存储了最重要的路由选择条目参数(但正如你在本章后面将要看到的,并非所有参数都存储在这里)。**fib_info对象由方法fib_create_info() ( netipv4/fib_semantics.c)创建,存储在散列表fib_info_hash中。路由使用prefsrc时,fib_info对象也将被加入到散列表fib_info_laddrhash中。

    ​ fib_info对象中有一个全局计数器——fib_info_cnt,方法fib_create_info()每次创建fib_info对象时都会将其加1,而方法free_fib_info()每次释放fib_info对象时又都将其减1。这个散列表增长到指定阈值时会自动调整大小。在散列表fib_info_hash中进行查找的工作是由方法fib_find_info()完成的。它在没有找到条目时返回NULL。以串行方式访问fib_info成员的工作由自旋锁( spinlock ) fib_info_lock完成。

    struct fib_table {
    	struct hlist_node	tb_hlist;
    	u32			tb_id;
    	int			tb_num_default;
    	struct rcu_head		rcu;
    	unsigned long 		*tb_data;
    	unsigned long		__data[0];
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • **tb_id:路由选择表标识符。对于主表,tb_id为254 (RT_TABLE_MAIN);对于本地表,tb_id为255 (RT_TABLE_LOCAL)。**主表和本地表将稍后介绍。这里需要指出的是,在不使用策略路由选择时,引导阶段将只创建这两个FIB表——主表和本地表。
    • **tb_num_default:表中包含的默认路由数。**创建表的方法fib_trie_table()将tb_num_default初始化为0。每添加一条默认路由,方法fib_table_insert()都将tb_num_default加1;每删除一条默认路由,方法fib_table_delete()都将tb_num_default减1。
    • tb_data:路由选择条目对象 (trie) 的占位符。

    fib_info 不同路由表项共享的参数

    不同路由表项之间可以共享一些参数,这些参数被存储在fib_info数据结构内。当一个新的路由表项所用的一组参数与一个已存在的路由项所用的参数匹配时,则重复使用已存在的fib_info结构。

    FIB信息

    路由选择条目由结构fib_info表示。它包含重要的路由选择条目参数,如出站网络设备( fib_dev)、优先级(fib_priority)、路由选择协议标识符(fib_protocol)等。下面来看看结构fib_info。

    struct fib_info {
    	struct hlist_node	fib_hash;
    	struct hlist_node	fib_lhash;
    	struct net		*fib_net; // 所属的网络命名空间
    	int			fib_treeref; // 一个引用计数器,表示包含指向该fib_info对象的引用的fib_alias对象的数量。
    	atomic_t		fib_clntref; // 引用fib_info对象自身的计数器
    	unsigned int		fib_flags;
    	unsigned char		fib_dead;
    	unsigned char		fib_protocol;
    	unsigned char		fib_scope;
    	unsigned char		fib_type;
    	__be32			fib_prefsrc;
    	u32			fib_tb_id;
    	u32			fib_priority;
    	struct dst_metrics	*fib_metrics;
    #define fib_mtu fib_metrics->metrics[RTAX_MTU-1]
    #define fib_window fib_metrics->metrics[RTAX_WINDOW-1]
    #define fib_rtt fib_metrics->metrics[RTAX_RTT-1]
    #define fib_advmss fib_metrics->metrics[RTAX_ADVMSS-1]
    	int			fib_nhs;
    #ifdef CONFIG_IP_ROUTE_MULTIPATH
    	int			fib_weight;
    #endif
    	unsigned int		fib_offload_cnt;
    	struct rcu_head		rcu;
    	struct fib_nh		fib_nh[0];
    #define fib_dev		fib_nh[0].nh_dev
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • fib_treeref:一个引用计数器,表示包含指向该fib_info对象的引用的fib_alias对象的数量。在方法fib_create_info()和fib_release_info()中,将分别对这个引用计数器进行加1和减1操作。上述两个方法都位于net/ipv4/fib_semantics.c中。

    • fib_clntref:一个引用计数器,方法fib_create_info() ( net/ipv4/fib_semantics.c)会将其加1,而fib_info_put() ( include/ net/ip_fib.h)则将其减1。方法fib_info_put()将它减1后,如果它变成了0,fib_info对象就会被方法free_fib_info()释放。

    • fib_dead:一个标志,指出了是否允许方法free_fib_info()将fib_info对象释放。在调用方法free_fib_info()前,必须将fib_dead设置为1。如果没有设置fib_dead标志(其值为0 ),fib_info对象将被视为处于活动状态,则若试图调用方法free_fib_info()来释放它,将以失败告终。

    • fib_protocol:路由的路由选择协议标识符。从用户空间添加路由选择规则时,如果没有指定路由选择协议ID,fib_protocol将被设置为RTPROT_BOOT。管理员添加路由时,可能会使用修饰符proto static,指出路由是由管理员添加的。例如,可以像下面这样做:ip route add proto static 192.168.5.3 via 192.168.2.1。fib_protocol可设置为下面的标志之一。

      • RTPROT_UNSPEC:一个错误值。
      • RTPROT_REDIRECT:被设置时,表明路由选择条目是因收到ICMP重定向消息而创建的。协议标识符RTPROT_REDIRECT仅用于IPv6中。
      • RTPROT_KERNEL:被设置时,表明路由选择条目是由内核创建的(例如,创建本地IPv4路由选择表时,这将在稍后讨论)。
      • RTPROT_BOOT:被设置时,表明路由是由管理员添加的,但添加时没有使用修饰符proto static。
      • RTPROT_STATIC:表示路由是由系统管理员添加的。
      • RTPROT_RA:不要误会,这个协议标识符表示的并非路由器警告,而是RDISCND路由器通告,仅供内核中的IPv6子系统使用,详情请参阅net/ipv6/route.c。这个协议标识符将在第8章讨论。

      路由选择表也可能是由用户空间路由选择守护程序(如ZEBRA、XORP、MROUTED等)添加的。在这种情况下,将指定相应的协议标识符(请参阅include/uapilinux/rtnetlink.h中RTPROT_XXX的定义)。例如,对于守护程序XORP,将指定RTPROT_XORP。请注意,这些标志(如RTPROT_KERNEL和RTPROT_STATIC)也被IPv6用来设置相应的字段(结构rt6_info的字段rt6i_protocol,对应于rtable的IPv6对象是rt6_info )。

    • fib_scope:目标地址的范围( scope ),它为地址和路由都指定了范围。简单地说,范围指出了主机相对于其他结点的距离。命令ip address show会显示主机配置的所有IP地址的范围,而命令ip route show会显示主表中所有路由条目的范围。范围分下面几种。

      • 主机(RT_SCOPE_HOST):结点无法与其他网络结点通信。环回地址的范围就是主机。
      • 全局(RT_SCOPE_UNIVERSE):地址可用于任何地方,这是最常见的情形。
      • 链路(RT_SCOPE_LINK):地址只能从直连主机访问。
      • 场点(RT_SCOPE_SITE):仅用于IPv6,这将在第8章讨论。.找不到(RT_SCOPE_NOWHERE):目的地不存在。

      管理员添加路由时,如果没有指定范围,将根据下述规则设置fib_scope字段的值。

      • 全局(RT_SCOPE_UNIVERSE):所有使用网关的单播路由。
      • 链路(RT_SCOPE_LINK):单播和广播直连路由。
      • 主机( RT_SCOPE_HOST ):本地路由。
    • fib_type:路由的类型。fib_type字段是内核3.7在结构fib_info中新增的。它们被用作键,旨在确保能够根据类型区分不同的fib_info对象。以前,只是在FIB别名(fib_alias )的fa_type字段中存储了这种类型。你可以添加规则,禁止特定的流量通过。例如,可以像下面这样做: ip route add prohibit 192.168.1.17 from 192.168.2.103。

      • 在生成的fib_info对象中,fib_type将为RTN_PROHIBIT。
      • 从192.168.2.103向192.168.1.17发送数据包时,将收到ICMPv4“数据包被过滤掉”消息( ICMP_PKT_FILTERED )。
    • fib_prefsrc:有时候,你可能想将查找键指定为特定的源地址,为此可设置fib_prefsrc。fib_priority:路由的优先级,默认为0,表示最高优先级。值越大,表示优先级越低。例如,优先级3比最高优先级0低。可使用以下面方式的ip命令之一配置优先级。

      • ip route add 192.168.1.10 via 192.168.2.1 metric 5
      • ip route add 192.168.1.10 via 192.168.2.1 priority 5
      • ip route add 192.168.1.10 via 192.168.2.1 preference 5

      这3个命令都将fib_priority设置为了5,它们没有任何差别。另外,命令ip route的参数metric与结松fib info的字段fib metrics没有任何关系

    • fib_mtu、 fib_window 、 fib_rtt和fib_advmss只是数组fib_metrics中常用元素的别名。fib_metrics是一个包含15 (RTAX_MAX)个元素的数组,存储了各种指标,被初始化为net/core/dst.c中定义的dst_default_metrics。很多指标都与TCP协议相关,如初始拥塞窗口( initcwnd )。本章末尾的表5-1列出了所有指标,并指出了它们是否与TCP相关。在用户空间中,可以这样设置TCPv4指标initcwnd。

      • ip route add 192.168.1.0/24 initcwnd 35

      有些指标并非TCP专用的,如mtu。在用户空间中,可以这样设置指标mtu;

      • ip route add 192.168.1.0/24 mtu 800

      也可以这样设置它:

      • ip route add 192.168.1.0/24 mtu lock 800

      这两个命令的差别在于:指定了修饰符lock时,不会尝试路径MTU发现;而没有指定修饰符lock时,内核可能基于路径MTU发现更新MTU。要更详细地了解这是如何实现的,请参阅net/ipv4/route.c中的方法__ip_rt_update_pmtu()。

    • fib_nhs:下一跳的数量。没有设置多路径路由选择(CONFIG_IP_ROUTE_MULTIPATH)时,其值不能超过1。多路径路由选择功能为路由指定了多条替代路径,并可能给这些路径指定不同的权重。这种功能提供了诸如容错、增加带宽和提高安全性等好处,这将在第6章讨论。

    • fib_dev:将数据包传输到下一跳的网络设备。

    • fib_nh[o]:表示下-跳。使用多路径路由选择时,可在一条路由中指定多个下一跳。在这种情况下,将有一个下一跳数组。
      例如,要指定两个下一跳结点,可以这样做:

      • ip routeadd default scope global nexthop dev etho nexthop dev eth1.

    **前面说过,fib_type为RTN_PROHIBIT时,将发送一条ICMPv4“数据包被过滤掉”消息( ICMP_PKT_FILTERED)。**这是如何实现的呢?在netipv4/fib_semantics.c中,定义了一个数组,它包含12(RTN_MAX)个fib_props对象。这个数组使用的索引为路由类型。可能的路由类型(如RTN_PROHIBIT和RTN_UNICAST )可在include/uapi/linux/rtnetlink.h中找到。这个数组的每个元素都是结构fib_prop的实例。结构fib_prop非常简单,其代码实现如下。

    struct fib_prop {
    	int	error;
    	u8	scope;
    };
    
    • 1
    • 2
    • 3
    • 4

    对于每种路由类型,相应的fib_prop对象都包含其error和scope。例如,对于极其常见的路由类型RTN_UNICAST(经由网关的路由或直连路由),error为0(表示没有错误),而scope为RT_SCOPE_UNIVERSE;对于路由类型RTN_PROHIBIT(系统管理员为禁止流量通过而配置的规则),error 为-EACCES,而scope为RT_SCOPE_UNIVERSE。

    对于每种路由类型,相应的fib_prop对象都包含其error和scope。例如,对于极其常见的路由类型RTN_UNICAST(经由网关的路由或直连路由),error为0(表示没有错误),而scope为RT_SCOPE_UNIVERSE;对于路由类型RTN_PROHIBIT(系统管理员为禁止流量通过而配置的规则),error 为-EACCES,而scope为RT_SCOPE_UNIVERSE。

    就这里介绍的情形而言,方法fib_lookup()(由它发起在IPv4路由选择子系统中进行查找的操作)最终将返回错误-EACCES。这种错误从check_leaf()出发,不断向后传播,经fib_table_lookup()等最终到达触发调用链的方法,即fib_lookup()。在接收路径中,方法fib_lookup()返回错误时,将由方法ip_error()进行处理,根据错误采取相应的措施。就错误-EACCES而言,采取的措施是发回一条代码为“数据包被过滤掉”(ICMP_PKT_FILTERED)的ICMPv4“目的地不可达”消息,同时将数据包丢弃。

    缓存

    ​ 缓存路由选择查找结果是一种优化技术,可用于改善路由选择子系统的性能。**路由选择查找结果通常缓存在下一跳对象(fib_nh)中,但在数据包不是单播数据包或使用了realms(数据包的itag不为0)时,则不会将查找结果缓存到下一跳中。这是因为,如果缓存所有数据包类型的查找结果,将导致不同类型的路由使用相同的下一跳,而这种情况必须避免。**还存在其他一些不那么重要的例外情况,但本章不作讨论。在接收路径和传输路径中进行缓存的过程如下。

    • 在接收路径中,通过设置下一跳对象(fib_nh)的nh_rth_input字段,将fib_result对象缓存到下一跳对象(fib_nh)中。
    • 在传输路径中,通过设置下一跳对象(fib_nh)的nh_pcpu_rth_output字段,将fib_result对象缓存到下一跳对象(fib_nh)中。
    • nh_rth_input和nh_pcpu_rth_output都是结构rtable的实例。
    • 在接收和传输路径中,缓存fib_result的工作都是由方法rt_cache_route() ( net/ipv4/route.c)完成的。
    • 缓存路径MTU和ICMPv4重定向的工作是使用FIB例外( xception)实现的。

    nh_pcpu_rth_output是一个基于CPU的变量,用以改善性能。这意味着对于每个CPU,都有一个输出条目dst的副本。几乎在任何情况下都需要使用缓存,只有为数不多的几种例外情形,如发送ICMPv4重定向消息、设置了itag ( tclassid)以及没有足够的内存等。
    本小节介绍了使用下一跳对象来实现缓存的过程,下一小节将讨论表示下一跳的结构fib_nh以及FIB下一跳例外(FIB nexthop exception )。

    下一跳

    结构fib_nh表示下一跳,包含诸如外出网络设备(nh_dev)、外出接口索引(nh_oif)、范围(nh_scope)等信息,如下所示。

    struct fib_nh {
    	struct net_device	*nh_dev; // 外出网络设备
    	struct hlist_node	nh_hash;
    	struct fib_info		*nh_parent;
    	unsigned int		nh_flags;
    	unsigned char		nh_scope; // 范围
    #ifdef CONFIG_IP_ROUTE_MULTIPATH
    	int			nh_weight;
    	atomic_t		nh_upper_bound;
    #endif
    #ifdef CONFIG_IP_ROUTE_CLASSID
    	__u32			nh_tclassid;
    #endif
    	int			nh_oif; // 外出接口索引
    	__be32			nh_gw;
    	__be32			nh_saddr;
    	int			nh_saddr_genid;
    	struct rtable __rcu * __percpu *nh_pcpu_rth_output;
    	struct rtable __rcu	*nh_rth_input;
    	struct fnhe_hash_bucket	__rcu *nh_exceptions;
    	struct lwtunnel_state	*nh_lwtstate;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    **nh_dev字段指出了将流量传输到下一跳所使用的网络设备( net_device对象)。**与一条或多条路由相关联的网络设备被禁用时,将发送NETDEV_DOWN通知。处理这种事件的FIB回调函数为方法fib_netdev_event()。它是通知者对象fib_netdev_notifier的回调函数,是在方法ip_fib_init()中调用方法register_netdevice_notifier()注册的(通知链将在第14章讨论)。收到通知NETDEV_DOWN后,方法fib_netdev_event()调用方法fib_disable_ip()。在方法fib_disable_ip(中,执行的步骤如下。

    • 首先,调用方法fib_sync_down_dev() ( net/ipv4/fib_semantics.c )。在方法fib_sync_down_dev()中,设置下一跳标志( nh_flags )中的RTNH_F_DEAD标志以及FIB信息标志( fib_flags )。
    • 调用方法fib_flush()刷新路由。
    • 调用方法rt_cache_flush()和arp_ifdown()。方法arp_ifdown()不包含在任何通知链中。

    FIB下一跳例外
    FIB下一跳例外( nexthop exceptions )是3.6版内核新增的,旨在处理这样的情形,即路由选择条目变更并非用户空间操作引起的,而是ICMPv4重定向消息或路径MTU发现导致的。使用的散列键为目标地址。FIB下一跳例外基于包含2048个条目的散列表。释放散列表条目时,始于长度为5的链条。每个下一跳对象(fib_nh)都包含一个FIB下一跳例外散列表——nh_exceptions,它是一个fnhe_hash_bucket结构实例。

    策略路由选择

    策略路由背后的主要思想是允许用户除了可以根据目的IP地址配置路由外,还可以根据其他多个参数来配置路由。

    在因特网繁荣稳定地发展的这些年中,大多数路由器被配置为只能够根据封包内的目的IP地址来路由封包。(为了简化,这里不考虑跨越多个ISP或多个国家等因素)。只基于目的地址(以及一些额外的配置参数)的路由,使路由表在面临大多数情况时都是最优的。

    但商业界还需要考虑更多的因素,例如,出于安全或计费考虑需要分开流量,或者通过单独的路由发送实时数据流。这就出现了策略路由,由于路由可以有各种各样的标准,在本章中,我们将除基于目的地址外的路由都认为是策略路由。

    ​ 不使用策略路由选择(没有设置CONFIG_IP_MULTIPLE_TABLES时),将创建两个路由选择表:本地表和主表。主表的ID为254 (RT_TABLE_MAIN ),本地表的ID为255( RT_TABLE_LOCAL)。本地表包含针对本地地址的路由选择条目,只有内核才能在本地表中添加路由选择条目。

    **在主表(RT_TABLE_MAIN )中添加路由选择条目的工作是由系统管理员完成的(例如,使用命令ip route add )。**这些表由net/ipv4/fib_frontend.c中的方法fib4_rules_init()创建,它们在2.6.25版之前的内核中分别被命名为ip_fib_local_table和ip_fib_main_table,但为了支持以统一的方式(通过方法fib_get_table()和合适的参数)访问路由选择表,现在已不再使用这些名称。

    所谓以统一的方式访问,指的是无论是否启用了策略路由选择,通过方法fib_get_table()访问路由选择表的方式都相同。方法fib_get_table()只接受两个参数:网络命名空间和表ID。请注意,还有一个用于策略路由选择的方法——fib4_rules_init(),它位于net/ipv4/fib_rules.c中。在支持策略路由选择的情况下将调用它。当支持策略路由选择(设置了CONFIG_IP_MULTIPLE_TABLES)时,默认有3个表(本地表、主表和默认表),且最多可以有255个路由选择表。

    策略路由选择将在第6章讨论。要访问主表,可以采取如下做法。

    使用系统管理员命令ip route或route。

    • 使用命令ip route add添加路由,是通过从用户空间发送RTM_NEWROUTE消息实现的,由方法inet_rtm_newroute()处理。请注意,路由未必都属于允许流量通过的规则,也可以添加禁止流量通过的路由。
      • 如,使用命令ip route add prohibit 192.168.1.17 from192.168.2.103,应用这条规则后,所有从192.168.2.103发送到192.168.1.17的数据包都将被禁止通过。
      • 使用命令ip route del删除路由,是通过从用户空间发送RTM_DELROUTE消息实现的,由方法inet_rtm_delroute()处理。
      • 使用命令转储路由选择表,是通过从用户空间发送RTM_GETROUTE消息实现的,由方法inet_dump_fib()处理。
        请注意,命令ip route show用于显示主表。要显示本地表,应使用命令ip route show tablelocal。
      • 使用命令route add添加路由,是通过发送SIOCADDRT IOCTL消息实现的,由方法ip_rt_ioctl() method ( net/ipv4/fib_frontend.c )处理。
      • 使用命令route del删除路由,是通过发送SIOCDELRT IOCTL消息实现的,由方法ip_rt_ioct1() ( net/ipv4/fib_frontend.c)处理。
      • 使用实现了BGP(边界网关协议)、EGP(外部网关协议)、OSPF(开放最短路径优先)或其他路由选择协议的路由选择守护程序。这些路由选择守护程序运行在Internet骨干的核心路由器上,能够处理数十万条路由。
    • 这里需要指出的是,因ICMPv4重定向消息或路径MTU发现而变更的路由被缓存到下一跳例外表中,这将稍后讨论。

    ICMPv4重定向消息

    有时候,路由选择条目可能是次优的。在这种情况下,将发送ICMPv4重定向消息。次优条目的主要判断标准是:输入设备和输出设备相同。但正如你即将在本节看到的,还必须满足其他条件,才会发送ICMPv4重定向消息。

    ICMPv4重定向消息的代码有4种:

    • ICMP_REDIR_NET:重定向网络
    • ICMP_REDIR_HOST:重定向主机
    • ICMP_REDIR_NETTOS:针对TOS重定向网络
    • ICMP_REDIR_HOSTTOS:针对TOS重定向主机

    下图演示了一种存在次优路由的情形。图中有3台机器,它们位于同一个子网( 192.168.2.0/24)中,并通过一个网关(192.168.2.1)相连。

    1. 在AMD服务器( 192.168.2.200 )中,使用命令ip route add 192.168.2.7 via 192.168.2.10将Windows服务器(192.168.2.10)指定为访问笔记本电脑(192.168.2.7)时使用的网关。
    2. 接下来,AMD服务器使用ping 192.168.2.7向笔记本电脑发送数据包。由于默认网关为192.168.2.10,流量将发送到192.168.2.10。
    3. Windows服务器发现这条路由是次优的(因为AMD服务器可以直接将数据发送到192.168.2.7),因此向AMD服务器发回一条代码为ICMP_REDIR_HOST的ICMPv4重定向消息。

    image-20220727004302673

    生成ICMPv4重定向消息

    存在次优路由时,将发送ICMPv4重定向消息。判断次优路由的最重要标准是:输人设备和输出设备相同,但同时还需满足其他一些条件。ICMPv4重定向消息的生成过程分如下两个阶段。
    在方法_mkroute_input()中,必要时设置标志RTCF_DOREDIRECT。
    在方法ip_forward()中,实际上是通过调用方法ip_rt_send_redirect()来发送ICMPv4重定向消息。

    接收ICMPv4重定向消息

    仅当ICMPv4重定向消息通过一些完整性检查后,才会对其进行处理,处理ICMPv4重定向消息的工作是由__ip_do_redirect()完成。

  • 相关阅读:
    Spring事务相关
    ctfshow-ssti
    HittER: Hierarchical Transformers for Knowledge Graph Embeddings
    XML 编辑器:功能、选择与使用技巧
    Pre-trained Language Models Can be Fully Zero-Shot Learners
    [CISCN 2019华北Day1]Web1
    Neutron — API Service Web 开发框架
    河流概化技巧方法介绍
    Django学习笔记:第二章django的安装和创建应用
    DN gc影响hbase查询性能
  • 原文地址:https://blog.csdn.net/qq_53111905/article/details/126251996