路由的设计远比一般的理解要复杂的多。典型的路由条目包括了源IP,目的IP,网关IP,scope,dev和type六个要素。
网关IP就是在配置路由的时候指定的via后面的地址,在路由表中叫Gateway,这是说明这条路由的下一跳是这个IP地址。这个IP地址之所以出现,是因为目的地址不是当前自己出口可以直接可达的,需要经过网关路由到下个网络才能投递。
也就是因此,如果这个via域配置为0.0.0.0,或者是用*表示,总之是代表一定的通配,那么就意味着这个路由的目的地和自己在一个二层的网络,到达那个目的地并不需要网关转发,只需要配置MAC地址从端口上发出去即可。这个发送出去的过程显然是去查ARP表,通过IP地址查询目标的MAC地址。很容易理解网关在路由条目中的意义,如果到达一个目标地址是需要通过网关转发出去的,via就要指定网关。大部分的个人局域网中,都会指定一个默认网关,目的IP填写了0.0.0.0,也就是所有的目的地址(通常使用命令的时候,这个词语叫做default),via后面填写网关地址。这样在其他的更精确的路由条目都不命中的情况下,就一定会命中这个默认路由条目。因为这个条目的目的IP设置是通配。使用ip命令设置这样的默认路由是例如ip route add default via 10.0.0.1。
假设一个路由条目指定了gateway,那么决策还需要知道这个gateway到底是从哪个网口发出去可达的,这就是dev的作用。既然到一个gateway必然要从一个设备出去,而其他的地方并不能指定这个gateway和设备的对应关系,于是就在路由表这里就指定了。通过dev可以到达该gateway。
如果gateway不指定,也就是该路由在同一个二层,那么仍然需要指定dev,因为即使是发送出去,也需要查从哪里发送出去。因为在收到一个数据包的时候,进入系统的时候目的IP不是自己就需要根据目的IP来查找路由,这个路由会决定这个目的IP是要转发给哪个端口(通常通过目的IP和网关IP和dev来决定)。
Dev是相对于gateway的一个更小的约束。同样起到约束作用的还有scope。Scope是一个更小程度的约束,指明了该路由在什么场景下才有效。也是用于约束目的地址的。例如不指定网关的二层路由,通常对应的scope类型是scope link。scope link的意义就是说明在同一个二层。这个意义与网关不指定的效果是呼应的。
有四种scope,global是在任何的场景下都有效,link是在链路上才有效,这个链路是指同一个端口,也就是说接收和发送都是走的同一个端口的时候,这条路由才会生效(也就是说在同一个二层)。Global则可以转发,例如从一个端口收到的包,可以查询global的路由条目,如果目的地址在另外一个网卡,那么该路由条目可以匹配转发的要求,进行路由转发。Link的scope路由条目是不会转发任何匹配的数据包到其他的硬件网口的。还有就是host,host表示这是一条本地路由,典型的是回环端口,loopback设备使用这种路由条目,该路由条目比link类型的还要严格,约定了都是本机内部的转发,不可能转发到外部。Site则是ipv6专用的路由scope。
源IP是一个路由条目的重要组成部分,这个源IP的意义在于一个补充作用。匹配还是根据目的IP进行匹配,但是由于在查找路由条目的时候很可能源地址还没有指定。典型的就是没有进行bind的发送情况,通常是随机选择端口和按照一定的规则源地址。这个一定的规则就是在这里的路由条目的src域可以影响。也就是如果进程没有bind一个源地址,将会使用这里src域里面的源地址作为数据包的源地址进行发送。但是如果进程提前bind了,命中了这个条目,就仍然会使用进程bind的源地址作为数据包的源地址。所以说这里的src只是一个建议的作用。
# ip route default via 115.238.122.129 dev eth1 115.238.122.128/25 dev eth1 proto kernel scope link src 115.238.122.163 192.168.0.160/24 dev dpdk0.kni proto kernel scope link src 192.168.0.163 192.168.1.160/24 dev dpdk1.kni proto kernel scope link src 192.168.1.163
举一个例子,从本机发出的目的地址是192.168.0.160/24网段的数据包将匹配第三条路由,如果在查询路由表之前没有设置bind,这个查询路由表的操作就会把数据包的源地址设置为192.168.0.163 。如果设置了bind,就保留bind的结果(所以你可以很容易的在Linux的主机上伪造原地址发送数据)。
src域在处理转发的数据包的时候,由于数据包是从外部收到的,外部进来的数据包也会查找路由表,也能命中同一个路由条目。但是由于外部进来的数据包已经有了明确的源地址,这里的src源地址建议就不会起作用了。所以关键就是理解src只是一个源地址的一个建议的作用即可。
对于路由表,是一个匹配的过程。一个数据包去查找匹配自己最能够匹配哪条路由表,然后就使用该路由条目指定的路由方法进行路由转发。匹配的方法就是鼎鼎大名的LPM,简单的说,就是匹配最匹配的那一个。
所以整个过程可以看到,核心的是对目的地址的限制,其他的域都是用于辅助这个限制,甚至可以辅助决策。
我们看一个虚拟机里的默认路由表:
root@ubuntu:~/# ip route show default via 192.168.142.2 dev ens33 proto static metric 100 169.254.0.0/16 dev ens33 scope link metric 1000 192.168.142.0/24 dev ens33 proto kernel scope link src 192.168.142.135 metric 100
跳过default,后面两条的第一个域都是目的地址,确切的说,这里指定的是目的网段,然后约束了设备,也就是ens33,这个路由条目是scope link的,也就是说当主机收到目标地址是169.254.0.0/16 这个网段的时候,通过ens33这个设备将包转发出去。
虽然理论上是如此,但是实际上,例如在linux中,这个dev ens33是没有在路由中起到任何作用的,也就是说你改了ens33的名字,而不改路由表,那这个路由表项一样命中,从改名后的网口发送出去。所以dev的这个限制相当于不存在,也就是只是一个命名的作用。但是并不确定在其他的实现中是否有限制的意义。
Default路由本质上就是目标地址填了0.0.0.0的路由。Default路由有两种添加方式,一种是约束网关地址,另外一种是约束源IP。因为要添加到网关地址的默认路由,是需要在添加的时候发一个arp请求到网络上,看这个网关的地址是否存在于二层的,但是这个arp请求也是需要首先经过路由的。也就是一个鸡生蛋,蛋生鸡的问题。所以一个空的路由表是不能直接配置默认路由是一个网关的。但是明明网关确实是和当前的主机在同一个网段的。
如果要配置默认网关,首先需要先让这个网络通。这个通的方法一个是配一个scope link的路由,也就是目的地址是该网段的发包,都可以匹配这个路由。因为是link scope的,所有的请求都会走二层的路由表,这就解决了arp不能到达网关的问题。Link scope的特点是所有的数据请求走二层arp,而不是走三层路由。所以在配置了这条路由之后,再配置网关就可以了。
但是还有一个思路是使用源地址约束,我们要的只是这个查询能命中一个可以出去的路由,当我们使用源地址约束,不指定目标地址,也就是源地址是自己设备的IP的地址的包全部走link scope,同样也可以匹配,由于是link scope,也就可以触发arp请求了。
所以我们看到,整个过程的关键在于区分同二层和三层转发。Link scope的作用是用在二层转发,命中该路由条目的可以触发arp查找,但是如果是网关式的,就是一个三层转发,虽然也会触发arp查找,但是目标MAC地址永远是网关的地址,这样下一跳就锁死了。
但是这里有一个问题是如果先添加了link scope的路由条目,然后又添加了gateway的路由,这个时候再把link scope的路由条目删除,那么gateway的路由条目仍然存在并且生效,这个时候,所有的转发都会匹配这个gateway的路由条目,包括本来应该走二层转发的数据包。也就是说,原本应该在同一个二层传输走arp的数据包,在这种情况下,也会直接走网关,网关回复一个icmp redirect,但是网关仍然会把这个数据包转发到同一个二层的目标地址。
理论上,收到icmp redirect的主机应当更新自己的arp表,但是并不会更新路由表,而arp表是要先经过路由表查询的,所以这个icmp redirect相当于没有意义。Arp表里面即使是有了IP到MAC的映射关系,但是由于路由没有命中link scope,所以永远不会查询ARP表。
另外linux下的路由条目还会有一个proto的域,一般有proto kernel和proto dhcp两种。Proto表明的是这个路由条目是由谁添加,例如给一个linux设备添加一个IP的时候会自动添加一条有这个源IP约束的Link scope的路由。前面说了,也正是有了这条路由才能够使得配置网关的路由条目可以进行。这个内核自动添加的路由就是proto kernel了。
需要理解的是,路由表和网络设备是两个实体,路由表在决策的时候,由路由表看到的网络设备是独立于路由表存在的。他们是并行的关系,先要查询了路由表,找到满足路由表的路由条目,才有可能按照条目约定的路由路径去找到对应的设备。路由过程并不是发生在设备逻辑的内部,而是外部。所以实际上给设备添加了一个IP地址的时候,同时生成的路由条目实际上是两个操作被在上层进行了组合。技术上,完全可以分别的添加IP地址和路由条目,上面也说了这个添加的路由条目可以被删除,然后再次添加回去