本文转载自微信公众号《内功修炼之养成》,作者张燕飞allen。转载本文请联系内功修炼发展公众号。大家好,我是飞哥。容器是一种新的虚拟化技术,每个容器都是一个逻辑上独立的网络环境。Linux提供了一种软件虚拟化的二层交换机Bridge,可以解决同一主机上多个容器之间的互联问题,但这还不够。二层交换无法解决容器与宿主机外网的互通。容器肯定需要与主机以外的外部网络进行通信,才有实用价值。例如,在Kubernetes中,所有Pod都需要相互通信。相当于在原来由物理机组成的网络之上,构建了一个可以互操作的虚拟网络。这就是Overlay网络的概念,下面用一个简单的示例图来表示。回想一下传统物理网络中不同子网上的服务器是如何互连的。没错,工作在三层的路由器也叫网关。路由器使数据包能够从一个子网传输到另一个子网,从而实现更广泛的网络互通。如下图所示,路由器连接192.168.0.x和192.168.1.x这两个子网。在容器虚拟化网络中,自然需要这样一个角色,将容器与主机以外的网络连接起来。其实Linux天生就有路由的能力,但是在云原生时代,它的路由功能又一次找到了用武之地。在容器与外部网络通信的过程中,Linux再次承担起路由器的角色,实现容器数据包的正确转发和投递。在各种基于容器的云原生技术盛行的今天,回过头来深入理解路由的工作原理是非常有必要和有价值的。今天,让我们再次加强Linux上的路由知识!1.什么时候需要路由下面说说Linux在什么情况下需要路由进程。实际上,发送和接收数据都涉及路由。为什么?让我们一一看看。1.1发送数据时的路由选择Linux之所以在发送数据包时需要选择路由,是因为服务器上可能有多个网卡设备。发送数据包时,一路经过用户态,TCP层到IP层,都要进行路由选择,确定使用哪个网卡设备发送数据包。详细过程看25张图,10000字,拆解linux网络发包过程大致过一遍路由相关的源码。网络层发送的入口函数是ip_queue_xmit。//file:net/ipv4/ip_output.cintip_queue_xmit(structsk_buff*skb,structflowi*fl){//路由选择过程//选择后记录路由信息到skbrt=(structrtable*)__sk_dst_check(sk,0);if(rt==NULL){//如果没有缓存,寻找路由项rt=ip_route_output_ports(...);sk_setup_caps(sk,&rt->dst);}skb_dst_set_noref(skb,&rt->dst);...//sendip_local_out(skb);}在ip_queue_xmit中,一开始我们看到了路由项查找,路由选择是在函数ip_route_output_ports中完成的。路由选择就是在路由表中进行匹配,然后决定使用哪个网卡发送出去。Linux中最多可以有255个路由表,其中默认有两个,local和main。使用ip命令查看路由表的具体配置。以本地路由表为例。#iproutelisttablelocallocal10.143.x.ydeveth0protokernelscopehostsrc10.143.x.ylocal127.0.0.1devloprotokernelscopehostsrc127.0.0.11.2接收数据时选择路由是正确的,接收数据包时也需要选择路由。这是因为Linux可能像路由器一样,通过适当的网卡转发传入的数据包。Linux在执行完IP层的接收入口ip_rcv后调用ip_rcv_finish。在这里展开路由。如果发现确实是设备的网络包,就会通过ip_local_deliver上报TCP上层处理。如果经过路由发现有非本设备的网络包,就会进入ip_forward进行转发,最后通过ip_output发送出去。具体代码如下。//file:net/ipv4/ip_input.cstaticintip_rcv_finish(structsk_buff*skb){...if(!skb_dst(skb)){interr=ip_route_input_noref(skb,iph->daddr,iph->saddr,iph->tos,skb->dev);...}...returndst_input(skb);}其中ip_route_input_noref正在执行路由查找。//file:net/ipv4/route.cintip_route_input_noref(structsk_buff*skb,__be32daddr,__be32saddr,u8tos,structnet_device*dev){...res=ip_route_input_slow(skb,daddr,saddr,tos,dev);returnres;}这里只是按照ip_route_input_slow,我们稍后会看到。1.3linux路由总结路由在内核协议栈中的位置可以用下图来表示。发送网络包时,需要从本机的多个网卡设备中选择一个合适的发送出去。当接收到网络数据包时,还需要对其进行路由。如果数据包属于设备,则将其发送到上层到网络层、传输层,然后到套接字的接收缓冲区。如果不是设备上的报文,则选择合适的设备进行转发。2.Linux的路由实现2.1路由表内核源代码中的路由表(routingtable)又叫转发信息库(ForwardingInformationBase,FIB)。所以你在源码中看到的fib开头的定义基本上就是路由表相关的函数。路由表本身由structfib_table表示。structfib_table{structhlist_nodetb_hlist;u32tb_id;inttb_default;inttb_num_default;unsignedlongtb_data[0];};所有路由表都由一个散列-fib_table_hash组织和管理。它位于网络命名空间net下。这意味着每个命名空间都有自己独立的路由表。//file:include/net/net_namespace.hstructnet{structnetns_ipv4ipv4;...}//file:include/net/netns/ipv4.hstructnetns_ipv4{//默认的所有路由表structhlist_head*fib_table_hash;//netfilter...}一般情况下,Linux只有两个路由表,local和main。如果内核在启用策略路由的情况下编译,管理员最多可以配置255个独立的路由表。如果你的服务器上创建了多个网络命名空间,就会有多组路由表。以在默认命名网络空间之外新建网络命名空间为例,整个内核数据结构中路由表的关系总结如下图。2.2RouteLookup上一节我们看到发送进程调用ip_route_output_ports查找路由,接收进程调用ip_route_input_slow查找。但实际上这两个函数最终都会调用核心函数fib_lookup,源码如下。//file:net/ipv4/route.cstructrtable*__ip_route_output_key(structnet*net,structflowi4*fl4){...//输入fib_lookupif(fib_lookup(net,fl4,&res)){}}//file:net/ipv4/route.cstaticintip_route_input_slow(structsk_buff*skb,__be32daddr,__be32saddr,u8tos,structnet_device*dev){...//输入fib_lookuperr=fib_lookup(net,&fl4,&res);}让我们看看fib_loopup做了什么。为了便于理解,我们只看不支持多路由表的fib_lookup版本。//file:include/net/ip_fib.hstaticinlineintfib_lookup(structnet*net,conststructflowi4*flp,structfib_result*res){structfib_table*table;table=fib_get_table(net,RT_TABLE_LOCAL);if(!fib_table_lookup(table,flp,res,FIB_LOOKUP_NOREF))return0;table=fib_get_table(net,RT_TABLE_MAIN);if(!fib_table_lookup(table,flp,res,FIB_LOOKUP_NOREF))return0;return-ENETUNREACH;}这个函数是在本地表和主表依次匹配,匹配后只是返回,不会继续匹配。从上面可以看出,本地表的优先级高于主表。如果在本地表中找到规则,则路由过程结束。这就是很多同学说ping本机在eth0上抓不到包的根本原因。所有命中本地表的数据包都将被发送到环回设置并且不会通过eth0。3、如何使用路由3.1开启转发路由默认情况下,Linux上的转发功能是关闭的。这时候Linux会丢弃接收到的不属于自己的网络包。但是在某些场景下,比如容器网络,Linux需要在本机转发来自其他网络命名空间的数据包,需要手动开启转发。下面两种方法都可以。#sysctl-wnet.ipv4.ip_forward=1#sysctlnet.ipv4.conf.all.forwarding=1开启后,Linux可以对不属于本机的IP数据包起到路由器的作用(严格来说,这网络命名空间)执行路由。3.2查看路由表默认情况下,Linux只有两个路由表,local和main。如果内核在启用策略路由的情况下编译,管理员最多可以配置255个独立的路由表。在centos上,您可以通过以下方式检查是否启用了CONFIG_IP_MULTIPLE_TABLES多路由表支持。#cat/boot/config-3.10.0-693.el7.x86_64CONFIG_IP_MULTIPLE_TABLES=y...所有路由表的编号为0-255,每个编号都有一个别名。编号和别名的对应关系可以在文件/etc/iproute2/rt_tables中找到。#cat/etc/iproute2/rt_tables255local254main253default0unspec200eth0_table使用iproutelisttable{tablename}查看路由表的配置。#iproutelisttablelocallocal10.143.x.ydeveth0protokernelscopehostsrc10.143.x.ylocal127.0.0.1devloprotokernelscopehostsrc127.0.0.1如果是查看main路由表,也可以直接使用route命令#route-nKernelIProutingtableDestinationGatewayGenmaskFlagsMetricRefUseIface10.0.0.010.*.*.254255.0.0.0UG000eth010.*.*.00.0.0.0255.255.248.0U000eth0上述字段含义如下Destination:目的地址,可以是特定的IP或网段,与Genmask一起表示。Gateway:网关地址,如果是0.0.0.0,表示不需要经过网关。Flags:U表示有效,G表示连接路由,H表示这条规则是主机路由,不是网络路由。iface:网卡设备,用来发送数据包的网卡。上面结果中输出的第一条路由规则表示在本机下,准确的说是在这个网络环境下,所有的目的地都是10.0.0.0/8(Genmask255.0.0.0表示前8位是子网掩码)网段所有的网络数据包都通过eth0设备发送到网关10...254,然后帮助转发。第二条路由规则表示如果目的地址为10...0/21(Genmask255.255.248.0表示前21位为子网掩码),可以直接通过eth0发送,不用走也可以通信通过网关。3.3修改路由表默认的本地路由表是内核根据本机网卡设备的配置自动生成的,不需要手动维护。对于主路由表的配置,我们一般只需要使用routeadd命令,使用routedel删除。修改主机路由#routeadd-host192.168.0.100deveth0//不带网关直接连接#routeadd-host192.168.1.100deveth0gw192.168.0.254//下一跳网关修改网络路由#routeadd-net192.168.1.0/24deveth0//直接connectionwithoutgateway#routeadd-net192.168.1.0/24deveth0gw10.162.132.110//下一跳网关也可以指定一条默认规则,当其他规则不匹配时执行。#routeadddefaultgw192.168.0.1eth0如果要修改其他号码的路由表,需要使用iproute命令。这里不多展开,仅以主表为例,有更多使用需求的同学请自行搜索。#iprouteadd192.168.5.0/24via10.*.*.110deveth0tablemain3.4路由规则测试配置完一系列路由规则后,为了快速验证是否符合预期,可以使用iprouteget命令进行确认。#iprouteget192.168.2.25192.168.2.25via10.*.*.110deveth0src10.*.*.161cache本文总结了当今各种网络虚拟化技术中路由功能的灵活应用。所以今天我们深入探讨了Linux路由的工作原理。在Linux内核中,发送进程和接收进程都涉及路由选择,接收进程的路由选择是决定是在本地接收还是转发出去。默认情况下有两个路由表,local和main,但是如果安装的linux启用了CONFIG_IP_MULTIPLE_TABLES选项,最多可以支持255个路由表。选路过程其实并不复杂。就是根据各个路由表的配置,找到合适的网卡设备和下一跳地址,然后转发数据包,完成任务。通过适当配置路由规则,容器内网络环境与外部的通信不再困难。通过大量路由规则的介入,可以实现虚拟网络互通。好了,今天的分享就到这里了,期待你们的点赞,重温转发~~
