目录

网络基础 | IP 层【专栏摘记】

IP 地址

分类网络地址

32 位的 IP 地址一开始被分为 5 类。

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20210926221628684.png

  • 对于 A、B、 C 类来说,IP 地址主要分两部分,前面一部分是网络号,后面一部分是主机号。

    下面这个表格,详细地展示了 A、B、C 三类地址所能包含的主机的数量。

    https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20210926221723701.png

  • D 类是组播地址。使用这一类地址,属于某个组的机器都能收到。这有点类似在公司里面大家都加入了一个邮件组。发送邮件,加入这个组的都能收到。组播地址在 VXLAN 协议的时候会提到。

CIDR

无类型域间选路,简称 CIDR。这种方式打破了原来设计的几类地址的做法,将 32 位的 IP 地址一分为二,前面是网络号,后面是主机号。从哪里分呢?你如果注意观察的话可以看到,10.100.122.2/24,这个 IP 地址中有一个斜杠,斜杠后面有个数字 24。这种地址表示形式,就是 CIDR。后面 24 的意思是,32 位中,前 24 位是网络号,后 8 位是主机号。

伴随着 CIDR 存在的,一个是广播地址,10.100.122.255。如果发送这个地址,所有 10.100.122 网络里面的机器都可以收到。

另一个是子网掩码,255.255.255.0。将子网掩码和 IP 地址按位进行 AND 计算就得到了网络号(前面三个 255,转成二进制都是 1。1 和任何数值取 AND,都是原来数值,因而前三个数不变,为 10.100.122。后面一个 0,转换成二进制是 0,0 和任何数值取 AND,都是 0,因而最后一个数变为 0,合起来就是 10.100.122.0)。

公有和私有 IP 地址

在日常的工作中,几乎不用划分 A 类、B 类或者 C 类,所以时间长了,很多人就忘记了这个分类,而只记得 CIDR。但是有一点还是要注意的,就是公有 IP 地址和私有 IP 地址。

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20210926221840656.png

  • 表格最右列是私有 IP 地址段。平时我们看到的数据中心里,办公室、家里或学校的 IP 地址,一般都是私有 IP 地址段。因为这些地址允许组织内部的 IT 人员自己管理、自己分配,而且可以重复。因此,你学校的某个私有 IP 地址段和我学校的可以是一样的。

    这就像每个小区有自己的楼编号和门牌号,你们小区可以叫 6 栋,我们小区也叫 6 栋,没有任何问题。但是一旦出了小区,就需要使用公有 IP 地址。就像人民路 888 号,是国家统一分配的,不能两个小区都叫人民路 888 号

    表格中的 192.168.0.x 是最常用的私有 IP 地址。你家里有 Wi-Fi,对应就会有一个 IP 地址。一般你家里地上网设备不会超过 256 个,所以 /24 基本就够了。有时候我们也能见到 /16 的 CIDR,这两种是最常见的,也是最容易理解的。对于 192.168.0.x 来说,很明显地可以看出 192.168.0 是网络号,后面是主机号。而整个网络里面的第一个地址 192.168.0.1,往往就是你这个私有网络的出口地址。例如,你家里的电脑连接 Wi-Fi,Wi-Fi 路由器的地址就是 192.168.0.1,而 192.168.0.255 就是广播地址。一旦发送这个地址,整个 192.168.0 网络里面的所有机器都能收到。

  • 公有 IP 地址有个组织统一分配,你需要去买。

    如果你搭建一个网站,给你学校的人使用,让你们学校的 IT 人员给你一个 IP 地址就行。但是假如你要做一个类似网易 163 这样的网站,就需要有公有 IP 地址,这样全世界的人才能访问。

IP 和 MAC 地址的区别

IP 是地址,有一定的定位功能;MAC 是身份证,有标识的功能。网卡地址一定是有的,但是 IP 地址不一样有。

IP 地址分配

静态分配的方式

1
2
3
4
5
$ sudo ifconfig eth1 10.0.0.1/24
$ sudo ifconfig eth1 up

$ sudo ip addr add 10.0.0.1/24 dev eth1
$ sudo ip link set up eth1

需要注意的是,静态分配的方式并不能随意分配 IP 地址,而是需要根据所在局域网的情况来进行分配,比如根据 CIDR、子网掩码、广播地址和网关地址等进行配置。

比如局域网中机器的 IP 地址是 192.168.1.x,假如自己的机器 A 也是接入到这个局域网中,但是我给它配了一个 16.158.23.6 这个地址。那么机器 A 不会发生任何现象,只是包会发不出去。下面对其进行解释:

假如机器 A 准备给 192.168.1.6 这台机器发送数据包,那么机器 A 在发送数据包的时候,可以将源 IP 地址 16.158.23.6 和源 MAC 地址填入,同时也将目标地址 192.168.1.6 填入。

而对于目标 MAC 地址来说,填入的是 16.158.23.6 这个网段的网关地址,而不是 192.168.1.6 这个 IP 对应的 MAC 地址。这个是因为,Linux 内核首先会判断目标 IP 地址是不是和自己是一个网段的,如果不是一个网段的话,那么不会将包发送到网络上,而是直接发送给网关。假如是一个网段的话,那么会发送 ARP 请求,获取 MAC 地址。

这里就到网关情况的阐述了,假如配置了网关的话,Linux 会获取网关的 MAC 地址,然后将包发送给网关。192.168.1.6 不会收到这个包。假如没有配置网关的话,那包压根就发送不出去。能不能将机器 A 的网关配置为 192.168.1.6 呢?不可能,Linux 不会让你配置成功的,因为网关和机器 A 至少一个网卡是同一个网段。

DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)

除了上述静态的配置方式之外,还可以使用 DHCP 方式。只需要配置一段共享的 IP 地址,那么每一台新接入的机器都可以通过 DHCP 协议,从这个共享的 IP 地址里申请,然后将主机配置为申请到的 IP 地址即可。当机器断开了之后,就将 IP 地址还回去,供其他机器使用。

DHCP 的工作方式

  • DHCP Discover 阶段。当一台机器新接入一个网络之后,将会使用 IP 地址 0.0.0.0 和目标 IP 地址 255.255.255.255 发送一个广播包。这个包里封装了 UDP,而 UDP 里面封装了 BOOTP(DHCP 是 BOOTP 的增强版,但是抓包的话会发现还是用 BOOTP 这个名称)。

    可以看到,一台机器在刚入网的时候,会带着自己的 MAC 地址,而此时 MAC 地址是它的唯一身份。如果这个 MAC 地址和该网络中的某一个 MAC 地址都重了的话,那么就没办法配置了。

    https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20210927161828671.png

  • DHCP Offer 阶段。此时,如果在网络里配置了 DHCP Server 的话,那么它会收到这个包。这个时候 DHCP Server 根据 MAC 地址进行判断,假如这个 MAC 地址没有分配过 IP 的话,那么就会给它分配一个 IP 地址。这个过程就称为 DHCP Offer。同时,DHCP Server 会记录给分配它的 IP 地址,从而不会将该 IP 地址分配给其他 DHCP 客户。

    需要注意的是,这个时候 DHCP Server 仍然使用广播 IP 作为目的地址。因为,此时请求分配 IP 地址的新机器还没有自己的 IP。在这个包里除了要分配的 IP 地址之外,还包含了子网掩码、网关和 IP 地址租用期等信息。

    https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20210927162334364.png

  • DHCP Request 阶段。新机器发出申请之后,假如网络中有多个 DHCP Server 那么会收到多个 IP 地址。这个时候它会选择其中一个 DHCP Offer,一般选择最先到达的那个。之后,会向网络中发送一个 DHCP Request 广播数据包(这是因为此时还没得到 DHCP Server 的最后确认,客户端仍然使用 0.0.0.0 作为源 IP 地址、255.255.255.255 为目标地址进行广播),其中包含了客户端的 MAC 地址、接受的租约中的 IP 地址、提供此租约的 DHCP 服务器地址等。这就相当于告诉所有 DHCP Server 它接受了哪一台服务器提供的 IP 地址,告诉其他 DHCP 服务器,可以撤销它们提供的 IP 地址,以便提供给下一个 IP 租用请求者。

    https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20210927164937175.png

  • DHCP ACK。当 DHCP Server 接收到客户机的 DHCP request 之后,会广播返回给客户机一个 DHCP ACK 数据包,表明已经接受客户机的选择,并将这一 IP 地址的合法租用信息和其他配置信息都放入广播包,发送给客户机。

    https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20210927165822757.png

  • 最终租约达成的时候,还需要广播一下,让其他机器都知道。

上述提到 DHCP Offer 和 DHCP ACK 都是广播包,其实也可以是单播包。而采用广播包发送还是采用单播包发送,取决于客户端协议栈的实现。有的协议栈如果没有配置好 IP 的话,是不能接受单播的包,所以这个时候就以广播的方式进行交互了。有的协议栈实现的比较厉害,即使没有配置好 IP,也可以接受单播的包,那么就以单播的方式进行交互。

IP 地址的续租和回收

客户机会在租期过去 50% 的时候,直接向为其提供 IP 地址的 DHCP Server 发送 DHCP request 消息包。客户机接收到该服务器回应的 DHCP ACK 消息包,会根据包中所提供的新的租期以及其他已经更新的 TCP/IP 参数,更新自己的配置。这样,IP 租用更新就完成了。

网卡信息查看-ifconfig/ip addr

在 Windows 上是 ipconfig,在 Linux 上是 ifconfig,也可以是 ip addr

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
root@test:~# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether fa:16:3e:c7:79:75 brd ff:ff:ff:ff:ff:ff
    inet 10.100.122.2/24 brd 10.100.122.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::f816:3eff:fec7:7975/64 scope link 
       valid_lft forever preferred_lft forever

这个命令显示了这台机器上所有网卡的信息。

  • <BROADCAST,MULTICAST,UP,LOWER_UP> 叫做 net_device flags,是网络设备的状态标识。

    BROADCAST 表示这个网卡有广播地址,可以发送广播包;MULTICAST 表示网卡可以发送多播包;UP 表示网卡处于启动的状态;LOWER_UP 表示 L1 是启动的,也即网线插着。

    MTU1500,MTU 是二层 MAC 层的概念。1500 表示 MAC 包的正文部分不允许超过 1500 个字节(1500 是以太网的默认值)。MAC 有 MAC 的头,MTU 大小是不包含二层头部和尾部的。正文部分不允许超过 1500 个字节,正文里面有 IP 的头、TCP 的头、HTTP 的头。如果放不下,就需要分片来传输。

  • qdisc pfifo_fast。qdisc 全称是 queueing discipline,中文叫排队规则。内核如果需要通过某个网络接口发送数据包,它都需要按照为这个接口配置的 qdisc(排队规则)把数据包加入队列。

    最简单的 qdisc 是 pfifo,它不对进入的数据包做任何的处理,数据包采用先入先出的方式通过队列。

    pfifo_fast 稍微复杂一些,它的队列包括三个波段(band)。在每个波段里面,使用先进先出规则。三个波段(band)的优先级也不相同。band 0 的优先级最高,band 2 的最低。如果 band 0 里面有数据包,系统就不会处理 band 1 里面的数据包,band 1 和 band 2 之间也是一样。数据包是按照服务类型(Type of Service,TOS)被分配到三个波段(band)里面的。TOS 是 IP 头里面的一个字段,代表了当前的包是高优先级的,还是低优先级的。

  • link/ether fa:16:3e:c7:79:75 brd ff:ff:ff:ff:ff:ff,这个被称为 MAC 地址,是一个网卡的物理地址,用十六进制,6 个 byte 表示。

  • 10.100.122.2 就是一个 IP 地址。这个地址被点分隔为四个部分,每个部分 8 个 bit,所以 IP 地址总共是 32 位。大部分的网卡都会有一个 IP 地址,当然,这不是必须的,也会没有 IP 地址的情况。

  • IPv6,也就是上面输出结果里面 inet6 fe80::f816:3eff:fec7:7975/64。这个有 128 位。

  • 在 IP 地址的后面有个 scope,对于 eth0 这张网卡来讲,是 global,说明这张网卡是可以对外的,可以接收来自各个地方的包。

  • 对于 lo 来讲,是 host,说明这张网卡仅仅可以供本机相互通信。lo 全称是 loopback,又称环回接口,往往会被分配到 127.0.0.1 这个地址。这个地址用于本机通信,经过内核处理后直接返回,不会在任何网络中出现。

ifconfig 和 ip addr 分别来自于 net-tools 和 iproute2

net-tools起源于BSD,自2001年起,Linux社区已经对其停止维护,而iproute2旨在取代net-tools,并提供了一些新功能。一些Linux发行版已经停止支持net-tools,只支持iproute2。 net-tools通过procfs(/proc)和ioctl系统调用去访问和改变内核网络配置,而iproute2则通过netlink套接字接口与内核通讯。 net-tools中工具的名字比较杂乱,而iproute2则相对整齐和直观,基本是ip命令加后面的子命令。

IP 数据包格式

下面阐述一下 IP 数据包的格式。

  • 在 MAC 头里面,先是目标 MAC 地址,然后是源 MAC 地址,然后有一个协议类型,用来说明里面是 IP 协议,还是 ARP 协议。
  • IP 头里面的版本号,目前主流的还是 IPv4。服务类型 TOS 见[网卡信息查看-ifconfig/ip addr](#网卡信息查看-ifconfig/ip addr)。8 位标识协议,也就是 TCP 还是 UDP。先是源 IP 地址,然后是目标 IP 地址。
  • TTL 域,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211024200335505.png

相关协议

ICMP

ICMP 可以当成 IP 层的协议。

ICMP 全称 Internet Control Message Protocol,就是互联网控制报文协议。

ICMP 报文封装在 IP 包里面,因为要检测目标机器肯定需要源地址和目的地址。

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211024155459312.png

ICMP 报文有很多的类型,不同的类型有不同的代码。

查询报文类型

ping 使用的就是查询报文,是一种主动请求,并且可以获得主动应答。对 ping 的主动请求,称为 ICMP ECHO REQUEST。同理主动请求的回复,称为 ICMP ECHO REPLY。主动请求的类型为 8,主动请求的应答的类型为 0。

ping 发的包也是符合 ICMP 协议格式的,只不过它在后面增加了自己的格式。一个是标识符,对此次查询进行标识;另一个是序号,对同一查询发出的多个包进行标识;最后一个是数据,这个字段中会存放发送请求的时间值,从而计算往返时间。

差错报文类型

差错报文类型有以下几种:终点不可达为 3,源站抑制为 4,超时为 11,路由重定向为 5。

  • 终点不可达类型的报文,具体的原因会用代码字段来表示。

    网络不可达代码为 0,主机不可达代码为 1,协议不可达代码为 2,端口不可达代码为 3,需要进行分片但设置了不分片位代码为 4。

  • 源站抑制,也就是让源站放慢发送速度。

  • 时间超时,也就是超过网络包的生存时间还是没到。

  • 路由重定向,也就是让下次发给另一个路由器。

差错报文中,还会将出错的 IP 包的 IP 头和 IP 正文的前 8 个字节封装进去。

ping 对查询报文类型的使用

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211024161552886.png

假定主机 A 的 IP 地址是 192.168.1.1,主机 B 的 IP 地址是 192.168.1.2,它们都在同一个子网。假设在主机 A 上运行“ping 192.168.1.2”后,

  • 个,第一个是类型字段,对于请求数据包而言该字段为 8;另外一个是顺序号,主要用于区分连续 ping 的时候发出的多个数据包。每发出一个请求数据包,顺序号会自动加 1。为了能够计算往返时间 RTT,它会在报文的数据部分插入发送时间。
  • 由 ICMP 协议将这个数据包连同地址 192.168.1.2 一起交给 IP 层。IP 层将以 192.168.1.2 作为目的地址,本机 IP 地址作为源地址,加上一些其他控制信息,构建一个 IP 数据包。
  • 接下来,需要加入 MAC 头。如果在本节 ARP 映射表中查找出 IP 地址 192.168.1.2 所对应的 MAC 地址,则可以直接使用;如果没有,则需要发送 ARP 协议查询 MAC 地址。获得 MAC 地址后,由数据链路层构建一个数据帧。还要附加上一些控制信息,依据以太网的介质访问规则,将它们传送出去。
  • 主机 B 收到这个数据帧后,先检查它的目的 MAC 地址,并和本机的 MAC 地址对比,如符合,则接收,否则就丢弃。接收后检查该数据帧,将 IP 数据包从帧中提取出来,交给本机的 IP 层。同样,IP 层检查后,将有用的信息提取后交给 ICMP 协议。
  • 主机 B 会构建一个 ICMP 应答包,应答数据包的类型字段为 0,顺序号为接收到的请求数据包中的顺序号,然后再发送出去给主机 A。
  • 在规定的时候间内,源主机如果没有接到 ICMP 的应答包,则说明目标主机不可达;如果接收到了 ICMP 应答包,则说明目标主机可达。此时,源主机会检查,用当前时刻减去该数据包最初从源主机上发出的时刻,就是 ICMP 数据包的时间延迟。

当然上述只是最简单的,同一个局域网里面的情况。如果跨网段的话,还会涉及网关的转发、路由器的转发等等。但是对于 ICMP 的头来讲,是没什么影响的。会影响的是根据目标 IP 地址,选择路由的下一跳,还有每经过一个路由器到达一个新的局域网,需要换 MAC 头里面的 MAC 地址。

如果在自己的可控范围之内,当遇到网络不通的问题的时候,除了直接 ping 目标的 IP 地址之外,还应该有一个清晰的网络拓扑图。并且从理论上来讲,应该要清楚地知道一个网络包从源地址到目标地址都需要经过哪些设备,然后逐个 ping 中间的这些设备或者机器。如果可能的话,在这些关键点,通过 tcpdump -i eth0 icmp,查看包有没有到达某个点,回复的包到达了哪个点,可以更加容易推断出错的位置。

如果不在我们的控制范围内,也就是很多中间设备都是禁止 ping 的,但是 ping 不通不代表网络不通。这个时候就要使用 telnet,通过其他协议来测试网络是否通。

Traceroute 对差错报文类型的使用

Traceroute 会使用 ICMP 的规则,故意制造一些能够产生错误的场景。

  • 它的一个作用就是故意设置特殊的 TTL,来追踪去往目的地时沿途经过的路由器。

    • Traceroute 的参数指向某个目的 IP 地址,它会发送一个 UDP 的数据包。将 TTL 设置成 1,也就是说一旦遇到一个路由器或者一个关卡,就表示它“牺牲”了。
    • 如果中间的路由器不止一个,当然碰到第一个就“牺牲”。于是,返回一个 ICMP 包,也就是网络差错包,类型是时间超时。
    • 接下来,将 TTL 设置为 2。第一关过了,第二关就“牺牲”了,那我就知道第二关有多远。如此反复,直到到达目的主机。

    这样,Traceroute 就拿到了所有的路由器 IP。当然,有的路由器压根不会回这个 ICMP。这也是 Traceroute 一个公网的地址,看不到中间路由的原因。

    怎么判断 UDP 有没有到达目的主机呢?Traceroute 程序会发送一份 UDP 数据报给目的主机,但它会选择一个不可能的值作为 UDP 端口号(大于 30000)。当该数据报到达时,将使目的主机的 UDP 模块产生一份“端口不可达”错误 ICMP 报文。

  • Traceroute 的另一个作用是故意设置不分片,从而确定路径的 MTU。要做的工作首先是发送分组,并设置“不分片”标志。发送的第一个分组的长度正好与出口 MTU 相等。如果中间遇到窄的关口会被卡住,会发送 ICMP 网络差错包,类型为“需要进行分片但设置了不分片位”。这样,每次收到“需要进行分片但设置了不分片位”的回复包之后就减小分组的长度,直到到达目标主机就能测出路径的 MTU 了。

路由器

在任何一台机器上,当要访问另一个 IP 地址的时候,都会先判断,这个目标 IP 地址和当前机器的 IP 地址,是否在同一个网段。

  • 如果是同一个网段,直接将源地址和目标地址放入 IP 头中,然后通过 ARP 获得 MAC 地址,将源 MAC 和目的 MAC 放入 MAC 头中,发出去就可以了。

  • 如果不是同一网段,这就需要发往默认网关 Gateway(Gateway 的地址一定是和源 IP 地址是一个网段的)。就是将源地址和目标 IP 地址放入 IP 头中,通过 ARP 获得网关的 MAC 地址,将源 MAC 和网关的 MAC 放入 MAC 头中,发送出去。

网关往往是一个路由器,是一个三层转发的设备。很多情况下,人们把网关就叫做路由器。其实不完全准确,路由器是一台设备。假设它有五个网口或者网卡,这五个网口分别连着 5 个局域网。每个网口都是相应局域网的网关。

路由器会把 MAC 头和 IP 头都取下来(三层转发设备),然后根据路由规则,选择一个网口,修改下源 MAC 地址,然后再将数据包发送出去(源 IP 地址视情况而定改变不改变)

路由

路由器就是一台网络设备,它有多张网卡。当一个入口的网络包送到路由器时,它会根据一个本地的转发信息库,来决定如何正确地转发流量。这个转发信息库通常被称为路由表。一张路由表中会有多条路由规则。每一条规则至少包含这三项信息:

  • 目的网络:这个包的目的网段是多少?
  • 出口设备:这个包从哪个网口出去
  • 下一跳网关:下一跳路由器的地址,可以是没有

那么,在转发的时候就是匹配一条条路由规则,找到符合的规则,按照规则中指明的那样从某个网口发出去,并且指定下一跳网关。

静态路由

静态路由,其实就是在路由器上,手动配置路由规则。

比如,我们可以使用 ip route 设置路由。如 ip route add 10.176.48.0/20 via 10.173.32.1 dev eth0,就说明要去 10.176.48.0/20 这个目标网络,要从 eth0 端口出去,下一跳地址选择 10.173.32.1。而网关上的路由策略就是按照这三项配置信息进行配置的。这种配置方式的一个核心思想是:根据目的 IP 地址来配置路由。

除了根据目的 ip 地址配置路由外,还可以根据多个参数来配置路由,这就称为策略路由。

  • 可以配置多个路由表。之后根据源 IP 地址、入口设备、TOS 等选择路由表,然后在路由表中查找路由。这样可以使得来自不同来源的包走不同的路由。

    1
    2
    
    $ ip rule add from 192.168.1.0/24 table 10 
    $ ip rule add from 192.168.2.0/24 table 20
    

    表示从 192.168.1.10/24 这个网段来的,使用 table 10 中的路由表,而从 192.168.2.0/24 网段来的,使用 table20 的路由表。

  • 在一条路由规则中,也可以走多条路径。

    例如,在下面的路由规则中下一跳有两个地方,分别是 100.100.100.1 和 200.200.200.1,权重分别为 1 比 2。

    1
    
    $ ip route add default scope global nexthop via 100.100.100.1 weight 1 nexthop via 200.200.200.1 weight 2
    

下面举一个例子,家里从运营商那儿拉了两根网线。这两根网线分别属于两个运行商。一个带宽大一些,一个带宽小一些。这个时候,需要一个可以接两个外网的路由器,并且两个运营商都要为这个网关配置一个公网的 IP 地址。运行商里面也有一个 IP 地址,是运营商网络里面的网关。不同的运营商方法不一样,有的是 /32 的,也即一个一对一连接。例如,运营商 1 给路由器分配的地址是 183.134.189.34/32,而运营商网络里面的网关是 183.134.188.1/32。有的是 /30 的,也就是分了一个特别小的网段。运营商 2 给路由器分配的地址是 60.190.27.190/30,运营商网络里面的网关是 60.190.27.189/30。

家里的网络呢,就是普通的家用网段 192.168.1.x/24。家里有两个租户,分别把线连到路由器上。IP 地址为 192.168.1.101/24 和 192.168.1.102/24,网关都是 192.168.1.1/24,网关在路由器上。由于出去的包需要 NAT 成公网的 IP 地址,因而路由器是一个 NAT 路由器。

拓扑图如下所示:

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211024221747842.png

相应的路由规则如下所示,意思是:

  • 如果去运营商二,就走 eth3;
  • 如果去运营商一呢,就走 eth2;
  • 如果访问内网,就走 eth1;
  • 如果所有的规则都匹配不上,默认走运营商一,也即走快的网络。
1
2
3
4
5
6
$ ip route list table main 
60.190.27.189/30 dev eth3  proto kernel  scope link  src 60.190.27.190
183.134.188.1 dev eth2  proto kernel  scope link  src 183.134.189.34
192.168.1.0/24 dev eth1  proto kernel  scope link  src 192.168.1.1
127.0.0.0/8 dev lo  scope link
default via 183.134.188.1 dev eth2

但是问题来了,租户 A 不想多付钱,他说我就上上网页,从不看电影。这个时候,我们就可以添加一个 table,名叫 chao。并且往里面添加一条规则:从 192.168.1.101 来的包都查看个 chao 这个新的路由表。

1
2
3
4
5
6
7
8
$ echo 200 chao >> /etc/iproute2/rt_tables

$ ip rule add from 192.168.1.101 table chao
$ ip rule ls
0:  from all lookup local 
32765:  from 10.0.0.10 lookup chao
32766:  from all lookup main 
32767:  from all lookup default

之后,在 chao 路由表中添加这样的规则,也就是默认路由走慢的。当然也可以在这个表中设置相应的权重。

1
2
$ ip route add default via 60.190.27.189 dev eth3 table chao
$ ip route flush cache

上面说的都是静态的路由,一般来说网络环境简单的时候,在自己的可控范围之内,采用静态路由的方式是可以的。但是有时候网络环境复杂并且多变,如果总是用静态路由,一旦网络结构发生变化,让网络管理员手工修改路由太复杂了,因而需要动态路由。

动态路由算法

使用动态路由,可以根据路由协议算法生成动态路由表,随网络运行状况的变化而变化。在数据包转发的过程中,肯定希望数据包经过的跳数越少越好,而机器和中间的路由器其实构成了一张图。因此,动态路由算法主要是在找最短路径。求最短路径常用的有两种方法,一种是 Bellman-Ford 算法,一种是 Dijkstra 算法。在计算机网络中基本也是用这两种方法计算的。

距离矢量路由算法

基于 Bellman-Ford 算法。这种算法的基本思路是,每个路由器都保存一个表,表包含多行,每行对应网络中的一个路由器。每一行包含两部分信息,一个是要到目标路由器,从哪条线出去,另一个是到目标路由器的距离。由此可以看出,每个路由器都是知道全局信息的。

那这些信息如何更新呢?每过几秒,每个路由器都将自己所知的到达所有的路由器的距离告知邻居,每个路由器也能从邻居那里得到相似的信息。每个路由器都知道自己和邻居之间的距离。每个路由器根据新收集的信息,计算和其他路由器的距离,比如自己的一个邻居距离目标路由器的距离是 M,而自己距离邻居是 x,则自己距离目标路由器是 x+M。

这种算法比较简单,但是存在一些问题:

  • 好消息传得快,坏消息传得慢。

    好消息传得快:如果有个路由器加入了这个网络,它的邻居就能很快发现它,然后将消息广播出去。要不了多久,整个网络就都知道了。

    坏消息传得慢:但是一旦一个路由器挂了,挂的消息是没有广播的。当每个路由器发现原来的道路到不了这个路由器的时候,感觉不到它已经挂了,而是试图通过其他的路径访问,直到试过了所有的路径,才发现这个路由器是真的挂了。

    具体的例子如下所示:

    原来的网络中只有 B C 两个节点。A 加入了网络,它的邻居 B 很快就发现 A 启动起来了。于是它将自己和 A 的距离设为 1,同样 C 也发现 A 起来了,将自己和 A 的距离设置为 2。

    但是如果 A 挂掉,情况就不妙了。B 本来和 A 是邻居,发现连不上 A 了,但是 C 还是能够连上,只不过距离远了点,是 2,于是将自己的距离设置为 3。殊不知 C 的距离 2 其实是基于原来自己的距离为 1 计算出来的。C 发现自己也连不上 A,并且发现 B 设置为 3,于是自己改成距离 4。依次类推,数越来越大,直到超过一个阈值,我们才能判定 A 真的挂了。

    https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211024235125656.png

  • 每次发送的时候,要发送整个全局路由表,所以网络规模小的时候没什么问题,但是大规模网络的场景下就不适用了。早期的 RIP 路由协议就采用这个算法,它适合于小型网络(小于 15 跳)。

总得来说,距离矢量路由算法不适合于大规模网络场景。

链路状态路由算法

基于 Dijkstra 算法。这种算法的基本思路是:当一个路由器启动的时候,首先是发现邻居,向邻居 say hello,邻居都回复。然后计算和邻居的距离,发送一个 echo,要求马上返回,除以二就是距离。然后将自己和邻居之间的链路状态包广播出去,发送到整个网络的每个路由器。这样每个路由器都能够收到它和邻居之间的关系的信息。因而,每个路由器都能在自己本地构建一个完整的图,然后针对这个图使用 Dijkstra 算法,找到两点之间的最短路径。

不像距离距离矢量路由协议那样,更新时发送整个路由表。链路状态路由协议只广播更新的或改变的网络拓扑,这使得更新信息更小,节省了带宽和 CPU 利用率。而且一旦一个路由器挂了,它的邻居都会广播这个消息,可以使得坏消息迅速收敛。

动态路由协议

基于链路状态路由算法的 OSPF

OSPF(Open Shortest Path First,开放式最短路径优先)就是这样一个基于链路状态路由协议,广泛应用在数据中心中的协议。由于主要用在数据中心内部,用于路由决策,因而称为内部网关协议(Interior Gateway Protocol,简称 IGP)。它使用 IP 协议号89。

内部网关协议的重点就是找到最短的路径。在一个组织内部,路径最短往往最优。当然有时候 OSPF 可以发现多个最短的路径,可以在这多个路径中进行负载均衡,这常常被称为等价路由。这一点非常重要。有了等价路由,到一个地方去可以有相同的两个路线,可以分摊流量,还可以当一条路不通的时候,走另外一条路。一般应用的接入层会有负载均衡 LVS。它可以和 OSPF 一起,实现高吞吐量的接入层设计。

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211025005655732.png

基于路径矢量路由算法的 BGP

BGP (Border Gateway Protocol)是自治系统 AS(Autonomous System)之间的路由协议。每个自治系统都有边界路由器,通过它和外面的世界建立联系。比如,每个数据中心都有设置自己的 Policy。哪些外部的 IP 可以让内部知晓,哪些内部的 IP 可以让外部知晓,哪些可以通过,哪些不能通过。这就相当于你可能通过这个数据中心中的机器走可以更近,但是这个数据中心却不允许你从中经过。

AS 是一组内部路由器,且共同使用一种路由选择和度量。AS 感觉是针对路由这一层面的,BGP 路由器相当于路由器的网关。并且 AS 中可包含公网 IP 地址的。

自治系统分以下几种类型:

  • Stub AS:对外只有一个连接。这类 AS 不会传输其他 AS 的包。例如,个人或者小公司的网络。
  • Multihomed AS:可能有多个连接连到其他的 AS,但是大多拒绝帮其他的 AS 传输包。例如一些大公司的网络。
  • Transit AS:有多个连接连到其他的 AS,并且可以帮助其他的 AS 传输包。例如主干网。

BGP 又分为两类,eBGP 和 iBGP。自治系统间,边界路由器之间使用 eBGP 广播路由。内部网络也需要访问其他的自治系统,这个时候可以运行 iBGP,使得内部的路由器能够找到到达外网目的地的最好的边界路由器。

BGP 协议使用的算法是路径矢量路由协议(path-vector protocol),它是距离矢量路由协议的升级版。距离矢量路由协议的缺点,其中一个是收敛慢。在 BGP 里面,除了下一跳 hop 之外,还包括了自治系统 AS 的路径,从而可以避免坏消息传得慢的问题。也即上面所描述的,B 知道 C 原来能够到达 A,是因为通过自己,一旦自己都到达不了 A 了,就不用假设 C 还能到达 A 了。另外,在路径中将一个自治系统看成一个整体,不区分自治系统内部的路由器,这样自治系统的数目是非常有限的。就像大家都能记住出去玩,从中国出发先到韩国然后到日本,只要不计算细到具体哪一站,就算是发送全局信息,也是没有问题的。

最后,BGP 使用的是 TCP179。

转发过程中 IP 的变化

路由器在转发的时候可能会涉及到 IP 地址的变换(MAC 地址只要过网关就会改变,因为 MAC 更多是一个局域网才有效的地址),大致可以分为两类:

  • 转发网关,这一类是不改变 IP 地址;
  • NAT 网关,这一类会改变 IP 地址;

转发网关

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211024205437170.png

如图所示,假如服务器 A 想要访问服务器 B。

  • 在服务器 A 的时候,由于 192.168.4.101 和 192.168.1.101 不是一个网段的,那么这个时候就需要发送给网关了。由于服务器 A 上已经将网关配置好了,是 192.168.1.1。但是,服务器 A 还不知道网关 192.168.1.1 的 MAC 地址,所以会使用 ARP 获取网关的 MAC 地址,然后发送数据包。

    1
    2
    3
    4
    
    源 MAC:服务器 A 的 MAC
    目标 MAC:192.168.1.1 这个网口的 MAC
    源 IP:192.168.1.101
    目标 IP:192.168.4.101
    
  • 包到达 192.168.1.1 这个网口,发现 MAC 一致,将包收进来。之后路由器根据路由规则判断这个包需要发送给哪个网口。在路由器 A 中配置了这样的路由规则:要想访问 192.168.4.0/24,要从 192.168.56.1 这个口出去,下一跳为 192.168.56.2。那么,这个时候路由器就准备将这个数据包从 192.168.56.1 这个口发出去,发给 192.168.56.2。此时路由器 A 还需要发送 ARP 获取 192.168.56.2 的 MAC 地址。

    1
    2
    3
    4
    
    源 MAC:192.168.56.1 的 MAC 地址
    目标 MAC:192.168.56.2 的 MAC 地址
    源 IP:192.168.1.101
    目标 IP:192.168.4.101
    
  • 包到达 192.168.56.2 这个网口,发现 MAC 一致,将包收进来。在路由器 B 中配置了路由规则,要想访问 192.168.4.0/24,要从 192.168.4.1 这个口出去,没有下一跳了。因为 192.168.4.1 这个网口就是这个网段的,已经是最后一跳了。那么,路由器就准备将数据包从 192.168.4.1 这个口发出去,发给 192.168.4.101。此时,路由器 B 还需要发送 ARP 获取 192.168.4.101 的 MAC 地址,然后发送包。

    1
    2
    3
    4
    
    源 MAC:192.168.4.1 的 MAC 地址
    目标 MAC:192.168.4.101 的 MAC 地址
    源 IP:192.168.1.101
    目标 IP:192.168.4.101
    
  • 包到达服务器 B,MAC 地址匹配,将包收进来。

通过这个过程可以看出,每到一个新的局域网,MAC 都是要变的,但是源和目标 IP 地址都不变。在 IP 头里面,不会保存任何网关的 IP 地址。所谓的下一跳 IP 地址是要将这个 IP 地址转换为 MAC 地址放入 MAC 头。由于这个 IP 地址在三个局域网都可见,所以这三个局域网的网段不能冲突。

NAT 网关

https://img.dawnguo.cn/NetworkingProtocol/Basic/image-20211024211501655.png

首先需要说明的是在 NAT(Network Address Translation) 网关模式中,IP 地址都会进行转换,比如访问 192.168.56.2 都会被转成 192.168.1.101。

如图所示,假如服务器 A 想要访问服务器 B,那么使用前面的转发网关模式的话,就会导致问题。所以访问过程如下:

  • 源服务器 A 要访问目标服务器 B,要指定的目标地址为 192.168.56.2。服务器 A 发现 192.168.56.2 和自己不是一个网段的,因而需要发给网关。此时,网关已经配置好了,是 192.168.1.1。所以需要发送 ARP 获取网关的 MAC 地址,然后发送包。

    1
    2
    3
    4
    
    源 MAC:服务器 A 的 MAC
    目标 MAC:192.168.1.1 这个网口的 MAC
    源 IP:192.168.1.101
    目标 IP:192.168.56.2
    
  • 包到达 192.168.1.1 这个网口,发现 MAC 一致,将包收进来。在路由器 A 中配置了路由规则:要想访问 192.168.56.2/24,要从 192.168.56.1 这个口出去,没有下一跳了。路由器 A 匹配了这条路由,准备将数据包从 192.168.56.1 这个口发出去,发给 192.168.56.2。此时,路由器 A 也需要发送 ARP 获取 192.168.56.2 的 MAC 地址。

    需要注意的是,此时源地址会改变,会从 192.168.1.101 改变成 192.168.56.1。

    1
    2
    3
    4
    
    源 MAC:192.168.56.1 的 MAC 地址
    目标 MAC:192.168.56.2 的 MAC 地址
    源 IP:192.168.56.1
    目标 IP:192.168.56.2
    
  • 包到达 192.168.56.2 这个网口,发现 MAC 一致,将包收进来,开始思考往哪里转发。路由器 B 是一个 NAT 网关,它上面配置了,访问 192.168.56.2 其实是访问 192.168.1.101,于是改为访问 192.168.1.101。在路由器 B 中配置了静态路由:要想访问 192.168.1.0/24,要从 192.168.1.1 这个口出去,没有下一跳了。

    路由器 B 匹配上这条路由,准备将数据包从 192.168.1.1 这个口发出去,目的地址是 192.168.1.101。此时,路由器 B 发送 ARP 获取 192.168.1.101 的 MAC 地址,然后发送包。

    1
    2
    3
    4
    
    源 MAC:192.168.1.1 的 MAC 地址
    目标 MAC:192.168.1.101 的 MAC 地址
    源 IP:192.168.56.1
    目标 IP:192.168.1.101
    
  • 包到达服务器 B,MAC 地址匹配,将包收进来。从服务器 B 接收的包可以看出,源 IP 为服务器 A NAT 转化之后的地址,因而发送返回包的时候,也发给这个地址。之后再由路由器 A 做 NAT,转换为内网 IP 地址。

可以发现在这个过程中,IP 地址是会变的。其实,这这种方式我们经常见,现在大家每家都有家用路由器,家里的网段都是 192.168.1.x,所以你肯定访问不了你邻居家的这个私网的 IP 地址的。所以,当我们家里的包发出去的时候,都被家用路由器 NAT 成为了运营商的地址了。

很多办公室访问外网的时候,也是被 NAT 过的,因为不可能办公室里面的 IP 也是公网可见的,公网地址实在是太贵了,所以一般就是整个办公室共用一个到两个出口 IP 地址。可以在网上直接搜 IP 地址,可以查看自己的出口 IP 地址。

上述转发过程中涉及到地址转换的方式叫做 NAT,是指将 m 个内网 IP 映射成 m 个公网 IP。还有另外一种方式叫做 NAPT,是将 m 个内网 ip:port 映射为公网 ip:port,节省公网 ip,比 nat 更灵活,但是端口在映射后可能会变(此时 NAT 路由器不能再说它充当的是三层设备了)。

  • 对于传出 NAT 路由器的数据包,NAT 根据源 IP 地址和传输层端口号在尽可能保留源端口号的情况下将其转写为 NAT 可用 IP 地址池里的 IP 地址和可用的 NAT 端口,并用 Session(TCP)或者活跃计时器(UDP)的方法来记忆”源IP地址+传输层端口号 <=> NAT IP 地址 + NAT 端口号“的映射。这样,当相应的回复数据包返回 NAT 路由器时,我们可以根据记录信息将 IP 地址和端口号转写回去。
  • 而对于传入的情况,位于 NAT 路由器后面的服务器,它需要通过监听端口来向互联网提供服务,由于服务器并不主动向 NAT 注册,所以这个时候需要手动向 NAT 路由器写入端口转发或者端口映射规则,从而可以将传入 NAT 路由器某一个端口的数据段转发给内部网络某一台主机。