路由原理

数据链路层实现了链路接入功能,负责将数据从一个节点传输到相邻的节点。

数据链路层的协议很多,以太网是其中最为流行的一个。通过以太网通信的主机,需要连接到同一个以太网网络。不管采用何种设备,以何种方式连接,以太网从逻辑上都可以抽象成这样:

以太网中的主机都是邻居关系,任何主机都可以通过以太网帧与其他主机进行直接通信。

当然了,设备和连接方式会影响通信的效率,但不会改变通信逻辑。

单个以太网的规模是非常有限的,但我们可以用网络层设备—— 路由器 ,将多个以太网组织成更大的网络。大网络内的所有主机,都可以通过网络层协议—— IP协议 ,进行通信。

为了彻底理解网络层、IP协议以及路由器的工作原理,我们构建一个极简网络拓扑,深入研究:

图中有两个以太网络,分别是以太网①和以太网②。以太网①中有三台主机,分别是 antbeecicada ;以太网②中有两台主机,applebanana ;中间的路由器,同时接入这两个以太网。

我们给以太网①中的通信实体,分配一个 192.168.1.x 段的 IP 地址:

通信实体 网卡 MAC地址 IP地址
路由器 eth1 fa:1c:b2:d0:b0:01 192.168.1.1
ant eth0 1e:1f:84:08:d2:aa 192.168.1.2
bee eth0 e6:04:b1:10:f1:bb 192.168.1.3
cicada eth0 ce:ba:ec:ff:fd:cc 192.168.1.4

同样,给以太网②中的通信实体,分配一个 192.168.2.x 段的 IP 地址:

通信实体 网卡 MAC地址 IP地址
路由器 eth2 ee:21:30:4a:5a:02 192.168.2.1
apple eth0 b6:f1:81:44:21:11 192.168.2.2
banana eth0 22:5b:7c:b3:d6:22 192.168.2.3

此外,每个通信实体还需要配置路由表,我们列举几个例子:

通信实体 规则
ant 192.168.1.x 直接从eth0网卡发出去;192.168.2.x 先发给192.168.1.1,由它负责转发
bee 192.168.1.x 直接从eth0网卡发出去;192.168.2.x 先发给192.168.1.1,由它负责转发
apple 192.168.2.x 直接从eth0网卡发出去;192.168.1.x 先发给192.168.2.1,由它负责转发
路由器 192.168.1.x 直接从eth1网卡发出去;192.168.2.x 直接从eth2网卡发出去

路由表准备好后,主机就可以互相通信了,分为两种不同场景:

  • 本地网通信,例如 ant-bee
  • 网际通信,例如 ant-apple

本地网通信

主机 antbee 接入同一个以太网络,它们的 IP 地址也在同一段,这样与主机直接连接的网络称为 本地网 。同一网络内的主机可以直接通信,无须借助第三方。具体如何进行呢?

假设主机 ant 通过 IP 协议向主机 bee 发送数据,数据封装成 IP 包,其中:

  • 源地址antIP 地址,即: 192.168.1.2
  • 目的地址beeIP 地址,即: 192.168.1.3

IP 包封装好后,主机查询路由表:去往 192.168.1.x 网段的 IP 包,可以直接从 eth0 网卡发出去。这表明:目标网络就是 eth0 网卡接入的本地网络,该 IP 包可以通过以太网帧直接发给目标主机。

接着,主机将 IP 包封装到以太网帧中,从 eth0 网卡发送出去,其中:

  • 源地址ant 主机 eth0 网卡的 MAC 地址,即: 1e:1f:84:08:d2:aa
  • 目的地址bee 主机 eth0 网卡的 MAC 地址, e6:04:b1:10:f1:bb

那么,主机 ant 怎么知道 192.168.1.3 这台主机( bee )的 MAC 地址呢?实际上,主机 ant 内部需要维护一张映射表,记录本地网主机 IPMAC 地址的映射关系:

IP地址 MAC地址 备注
192.168.1.1 fa:1c:b2:d0:b0:01 路由器
192.168.1.3 e6:04:b1:10:f1:bb bee
192.168.1.4 ce:ba:ec:ff:fd:cc cicada

至于这个映射表是如何获得的,谜底将在 ARP 协议一章揭晓。

主机 bee 接到以太网帧后,即可取出 IP 包,进而取出封装在其中的数据。

这就是本地网主机通过 IP 协议通信的全过程,请结合下图理解,重点体会 IP 包和以太网帧的地址:

网际通信

主机 antapple 位于不同的网络中,IP 地址也不在同一段,无法直接通信。这种跨网络通信称为 网际通信 ,需要借助 路由器 来实现。具体步骤又是怎样的呢?

假设主机 ant 通过 IP 协议向主机 apple 发送数据,数据封装成 IP 包,其中:

  • 源地址antIP 地址,即: 192.168.1.2
  • 目的地址appleIP 地址,即: 192.168.2.2

IP 包封装好后,主机查询路由表:去往 192.168.2.x 网段的 IP 包,需要先发给路由器 192.168.1.1 ,由它负责转发。由于路由器 192.168.1.1 位于本地网,主机可以将 IP 包搭载在以太网帧中,通过 eth0 网卡发给它。

主机先根据路由 IP 地址从映射表中取出路由的 MAC 地址,然后完成以太网帧封装,其中:

  • 源地址ant 主机 eth0 网卡的 MAC 地址,即: 1e:1f:84:08:d2:aa
  • 目的地址 是路由器 eth1 网卡的 MAC 地址, fa:1c:b2:d0:b0:01

当路由器接到以太网帧后,从中取出 IP 包,发现它是发往 192.168.2.2 的。路由器同样查询路由表,发现: 192.168.2.x 是个直连的本地网络,可以通过 eth2 网卡直接通信。

路由器从内部映射表中查到 192.168.2.2 对应的 MAC 地址,并将 IP 封装在以太网帧中从 eth2 网卡发出去:

  • 源地址 是路由器 eth2 网卡的 MAC 地址,即: ee:21:30:4a:5a:02
  • 目的地址apple 主机 eth0 网卡的 MAC 地址,即 b6:f1:81:44:21:11

主机 apple 接到以太网帧后,即可取出 IP 包,进而得到 ant 发给它的数据。

这就是网际主机通过 IP 协议通信的全过程,请结合下图理解,重点体会 IP 包和以太网帧封装和转发步骤:

实验演示

本文讨论的网络拓扑环境,同样由 docker 提供,只需执行以下命令即可一键打开:

1
docker run --name single-router --rm -it --privileged --cap-add=NET_ADMIN --cap-add=SYS_ADMIN -v /data fasionchan/netbox:0.11 launch-netenv single-router

如果屏幕出现 press Ctrl-C to exit 说明实验环境已经成功启动,实验完毕后,输入 Ctrl-C 即可一键退出。

实验环境启动后,只需执行以下命令,即可进入主机 ant

1
docker exec -it single-router netbox-main launch-netenv nsvm-util enter-nsvm ant

如果看到这个熟悉的命令行提示符,说明已经成功进入主机 ant

1
root@ant [ ~ ]  ➜

先观察一下主机 ant 网卡 eth0 的情况,重点它的 MAC 地址和 IP 地址:

1
2
3
4
5
root@ant [ ~ ]  ➜ ip addr show eth0
6: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether 1e:1f:84:08:d2:aa brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.2/24 scope global eth0
       valid_lft forever preferred_lft forever

这里看到的信息跟前面的表格是一致的。

将上一个命令中的 ant 改成 router ,执行即可进入中间的路由器;同理,我们可以进入主机 bee

1
docker exec -it single-router netbox-main launch-netenv nsvm-util enter-nsvm bee

同样先观察 eth0 网卡上的地址信息,跟前面的表格是一致的:

1
2
3
4
5
root@bee [ ~ ]  ➜ ip addr show eth0
8: eth0@if7: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
    link/ether e6:04:b1:10:f1:bb brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 192.168.1.3/24 scope global eth0
       valid_lft forever preferred_lft forever

接下来我们观察路由表,可以用 route 命令或者 ip 命令,以主机 ant 为例:

1
2
3
4
5
root@ant [ ~ ]  ➜ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
192.168.1.0     0.0.0.0         255.255.255.0   U     0      0        0 eth0
192.168.2.0     192.168.1.1     255.255.255.0   UG    0      0        0 eth0

route 命令输出两条路由规则,每条占一行,分别有目的地、网关、子网掩码、标志位以及出口设备等好几列。其中,目的地结合子网掩码确定目的网段,例如 192.168.1.0/255.255.255.0 表示 192.168.1.x 这个网段。

子网掩码将在后面章节详细介绍,这里只需有一个大概印象即可。

第一条路由记录(第 4 行)的意思是,去往 192.168.1.x 网段的 IP 包,都可以通过 eth0 直接发送出去。网关 0.0.0.0 表示 IP 包无须通过任何路由中介进行转发,也就是说 192.168.1.x 网段是一个本地网段,可以直接通信。

第二条路由记录(第 5 行)的意思是,去往 192.168.2.x 网段的 IP 包,需要先发给 192.168.1.1 ,由它负责转发;而网关 192.168.1.1 可以通过 eth0 网卡直接通信。

ip 是一个比较新的命令,更推荐使用。它输出的路由信息相对来说更紧凑:

1
2
3
root@ant [ ~ ]  ➜ ip route
192.168.1.0/24 dev eth0 proto kernel scope link src 192.168.1.2
192.168.2.0/24 via 192.168.1.1 dev eth0

192.168.1.0/24 是子网掩码的另一种表示法,代表 192.168.1.x 这个网段,具体细节将在后续章节展开介绍。

第一条路由的意思是,去往 192.168.1.x 网段的 IP 包,可以直接从 eth0 网卡中发送出去;第二条路由则表示,去往 192.168.2.x 也可以通过 eth0 网卡发送,但必须先发给 192.168.1.1 ,由它负责转发。

很显然,主机 ant 上的这两条路由,特征显著不同:

  • 第一条属于 直接路由 ,目的网段是本地网,可以通过网卡直接发送出去,属于 本地网通信 场景;
  • 第二条属于 间接路由 ,目的网段是其他网络,需要通过路由转发,属于 网际通信 场景;
路由种类 通信场景 route命令特征 ip命令特征
直接路由 本地网通信 网关为空,即0.0.0.0;标志位不带G 不带via
间接路由 网际通信 网关非空;标志位带G 带via

现在,尝试在主机 ant 上,向主机 bee 发送 IP 包。我们使用 ping 命令,它可以向目标主机发送一个特殊的 IP 包;主机接到这样的包后,将返回另一个 IP 包作为响应,以此检查网络是否畅通。

开始之前,我们分别在主机 bee 和路由器 router 上运行 tcpdump 程序,观察可能达到的 IP 包:

1
root@bee [ ~ ]  ➜ tcpdump -eni any ip
1
root@router [ ~ ]  ➜ tcpdump -eni any ip

tcpdump -e 参数用于显示以太网帧头部信息。

现在,回到主机 ant ,执行 ping 命令,向 bee 发出一个 IP 包:

1
2
3
4
5
6
7
root@ant [ ~ ]  ➜ ping -c 1 192.168.1.3
PING 192.168.1.3 (192.168.1.3) 56(84) bytes of data.
64 bytes from 192.168.1.3: icmp_seq=1 ttl=64 time=0.219 ms

--- 192.168.1.3 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.219/0.219/0.219/0.000 ms

命令输出显示,主机 ant 很快就收到 bee 响应的回包,往返时间在 0.2 毫秒左右。

主机 bee 上,tcpdump 观察到 ant 发来的 IP 包以及系统自动回复的响应包:

1
2
3
4
5
root@bee [ ~ ]  ➜ tcpdump -eni any ip
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
11:12:02.330044  In 1e:1f:84:08:d2:aa ethertype IPv4 (0x0800), length 100: 192.168.1.2 > 192.168.1.3: ICMP echo request, id 629, seq 1, length 64
11:12:02.330114 Out e6:04:b1:10:f1:bb ethertype IPv4 (0x0800), length 100: 192.168.1.3 > 192.168.1.2: ICMP echo reply, id 629, seq 1, length 64

第一行是主机 bee 接到主机 ant 的来包,关键字 In 表示入站。该包是 1e:1f:84:08:d2:aa 发来的以太网帧,以太类型是 0x0800 ,数据承载着一个 IP 包;IP 包是由 192.168.1.2/ant 发到 192.168.1.3/bee 的。

IP 包中还承载着一个 ICMP 协议的包,用于探测主机连通性,我们先按下不表。

第二行是主机 bee 回复主机 ant 的响应包,关键字 OUT 表示出站,请结合上一个对照理解。

注意到,路由器 router 可能也会收到这个包,这是为什么呢?

1
2
3
4
root@router [ ~ ]  ➜ tcpdump -eni any ip
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
11:12:02.329995   P 1e:1f:84:08:d2:aa ethertype IPv4 (0x0800), length 100: 192.168.1.2 > 192.168.1.3: ICMP echo request, id 629, seq 1, length 64

我们知道,IP 包承载在以太网帧中从 1e:1f:84:08:d2:aa/ant 发往 e6:04:b1:10:f1:bb/bee 。还记得以太网协议中学到的内容吗?以太网交换机一开始可能还没有学习到主机 bee 的地址 e6:04:b1:10:f1:bb ,因此只能将该帧广播到所有端口。这就是路由器 router 也收到到这个包的原因,这种情况下其他主机 beecicada 也会收到。

注意到,路由器知道这个包不是发给自己的。关键字 P 表明,这是一个发往其他主机的帧,因网卡开启混杂模式而被嗅探到。路由器系统协议栈不会处理该包,因此也不会回复响应包。

回过头来执行 arp 命令查看主机 ant 上维护的本地网主机 IPMAC 地址的映射表:

1
2
3
root@ant [ ~ ]  ➜ arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
192.168.1.3              ether   e6:04:b1:10:f1:bb   C                     eth0

主机 ant 就是根据这个映射表,找到 192.168.1.3 对应的 MAC 地址,从而完成以太网帧的封装和发送的。至于这个映射表是如何获得的,同样先按下不表,等到介绍 ARP 协议时再揭晓。

最后,考察一下网际通信的场景。先进入路由器 router ,观察它的路由表:

1
2
3
root@router [ ~ ]  ➜ ip route
192.168.1.0/24 dev eth1 proto kernel scope link src 192.168.1.1
192.168.2.0/24 dev eth2 proto kernel scope link src 192.168.2.1

router 只有两条直接路由,因为两个网络都与路由直接相连,它可以与图中任一台主机进行直接通信。

以主机 ant 向 主机 apple 发包为例,我们先在 routerapple 上执行 tcpdump 命令,观察网络流量:

1
root@router [ ~ ]  ➜ tcpdump -eni any ip
1
root@apple [ ~ ]  ➜ tcpdump -eni any ip

回到主机 ant ,执行 ping 命令向主机 apple 发送一个 IP 包,它的 IP 地址是 192.168.2.2

1
2
3
4
5
6
7
root@ant [ ~ ]  ➜ ping -c 1 192.168.2.2
PING 192.168.2.2 (192.168.2.2) 56(84) bytes of data.
64 bytes from 192.168.2.2: icmp_seq=1 ttl=63 time=0.674 ms

--- 192.168.2.2 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 0.674/0.674/0.674/0.000 ms

主机 ant 路由规则显示,去往 192.168.2.x 网段的 IP 包,需要先发给本地网上的 192.168.1.1 ,由它转发。ant 从自己的映射表中找到 192.168.1.1MAC 地址,并封装以太网帧,将 IP 包作为帧数据发送出去。

这时,我们可以在路由上观察到这个 IP 包:

1
2
3
4
5
6
7
root@router [ ~ ]  ➜ tcpdump -eni any ip
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
17:44:27.460433  In 1e:1f:84:08:d2:aa ethertype IPv4 (0x0800), length 100: 192.168.1.2 > 192.168.2.2: ICMP echo request, id 811, seq 1, length 64
17:44:27.460542 Out ee:21:30:4a:5a:02 ethertype IPv4 (0x0800), length 100: 192.168.1.2 > 192.168.2.2: ICMP echo request, id 811, seq 1, length 64
17:44:27.460714  In b6:f1:81:44:21:11 ethertype IPv4 (0x0800), length 100: 192.168.2.2 > 192.168.1.2: ICMP echo reply, id 811, seq 1, length 64
17:44:27.460730 Out fa:1c:b2:d0:b0:01 ethertype IPv4 (0x0800), length 100: 192.168.2.2 > 192.168.1.2: ICMP echo reply, id 811, seq 1, length 64

第一行显示的是 eth1 网卡收到 ant 发来的帧, In 表示入站。帧的源地址是 antMAC 地址,以太类型表明该帧承载着一个 IP 包。IP 包是从 192.168.1.2/ant 发到 192.168.2.2/apple 的。

路由器查询本地路由表,知道 IP 包的目的网段 192.168.2.x 是一个本地网,可以通过 eth2 网卡直接通信。查询本地的 IP/MAC 地址映射表后,路由器知道 192.168.2.2MAC 地址 ee:21:30:4a:5a:02

正如第二行显示的那样,路由器可以将 IP 包封装在以太网帧中,通过 eth2 网卡发给主机 apple 。其中, Out 表示出站,帧的目的地是 ee:21:30:4a:5a:02/apple ,帧中承载的是 ant 发给 appleIP 包。

同样可以观察到主机 apple 接到 IP 包后,回复一个响应包,细节就不再赘述了:

1
2
3
4
5
root@apple [ ~ ]  ➜ tcpdump -eni any ip
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on any, link-type LINUX_SLL (Linux cooked v1), capture size 262144 bytes
17:44:27.460609  In ee:21:30:4a:5a:02 ethertype IPv4 (0x0800), length 100: 192.168.1.2 > 192.168.2.2: ICMP echo request, id 811, seq 1, length 64
17:44:27.460701 Out b6:f1:81:44:21:11 ethertype IPv4 (0x0800), length 100: 192.168.2.2 > 192.168.1.2: ICMP echo reply, id 811, seq 1, length 64

根据 apple 的路由规则,响应包同样被发给路由器,从 eth2 网卡进入,正如路由器观察到的第三行。路由器则接力将响应包从 eth1 网卡转发给主机 ant ,正如第四行。

FAQ

Linux系统如何获取下一跳网关(路由)的IP地址?

我们知道,一个 IP 包下一跳应该发给谁是系统路由表决定的。因此我们只需查询路由表,即可获取下一跳网关的地址。执行 route 命令即可查询系统当前路由表:

1
2
3
4
5
$ route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.0.8.1        0.0.0.0         UG    100    0        0 eth0
10.0.8.0        0.0.0.0         255.255.252.0   U     0      0        0 eth0

这个例子中第 5 行是一条直接路由,本地直连网络可以通过网卡直接发给目的主机;第 4 行是一条默认路由,即发往其他 IP 地址的 IP 包,下一跳发给网关 10.0.8.1 。如果路由表比较复杂,配置了多个网卡,那么需要根据目的 IP 和网段,看匹配哪个网关。

执行 ip route 命令也可以查询系统当前的路由表,默认它会列出所有路由记录:

1
2
3
$ ip route
default via 10.0.8.1 dev eth0 proto dhcp src 10.0.8.10 metric 100
10.0.8.0/22 dev eth0 proto kernel scope link src 10.0.8.10

想要直接查询去往某个目的地的路由记录,可以这样查询:

1
2
3
$ ip route get 8.8.8.8
8.8.8.8 via 10.0.8.1 dev eth0 src 10.0.8.10 uid 1002
    cache

这次我们直接取出了决定目的地址 8.8.8.8 的路由记录,这表明发给 8.8.8.8IP 包需要发给网关 10.0.8.1

小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注:

【小菜学网络】系列文章首发于公众号【小菜学编程】,敬请关注: