Linux工作原理9网络及配置

9网络及配置

网络是连接计算机并在它们之间发送数据的实践。这听起来很简单,但要了解其工作原理,你需要问两个基本问题:

  • 发送数据的计算机如何知道向何处发送数据?
  • 当目的地计算机收到数据时,它如何知道刚刚收到了什么?

计算机通过使用一系列组件来回答这些问题,每个组件负责发送、接收和识别数据的某个方面。这些组件按组排列,形成网络层,层层叠加,构成一个完整的系统。Linux内核处理网络的方式与第3章中描述的SCSI子系统类似。

由于每一层都是独立的,因此可以用多种不同的组件组合来构建网络。这就是网络配置变得非常复杂的原因。因此,在本章开始时,我们将介绍非常简单网络中的各层。你将学会如何查看自己的网络设置,当你了解了每一层的基本工作原理后,就可以学习如何自己配置这些层了。最后,您将进入更高级的主题,如构建自己的网络和配置防火墙。(如果你开始眼花缭乱,可以跳过这些内容;你可以随时回来学习)。

9.1 网络基础知识

在学习网络层理论之前,请先看一下图 9-1 所示的简单网络。

这种网络无处不在,大多数家庭和小型办公室网络都是这样配置的。连接到网络的每台机器都称为主机。其中一个主机是路由器,它可以将数据从一个网络转移到另一个网络。在本例中,这四台主机(主机 A、B、C 和路由器)组成一个局域网(LAN)。局域网上的连接可以是有线的,也可以是无线的。局域网没有严格的定义;局域网内的机器通常物理距离很近,并且共享相同的配置和访问权限。你很快就会看到一个具体的例子。

路由器还连接到互联网--图中的云。这种连接被称为上行链路或广域网(WAN)连接,因为它将更小的局域网连接到更大的网络。由于路由器同时连接局域网和互联网,因此局域网上的所有机器也都可以通过路由器访问互联网。本章的目标之一就是了解路由器是如何提供这种访问的。

你的初始视角将来自一台基于 Linux 的机器,如图 9-1 中局域网中的主机 A。

9.2 数据包

计算机在网络上传输的数据以称为数据包的小块形式传输,数据包由两部分组成:报头和有效载荷。报头包含识别信息,例如源主机和目标主机以及基本协议。另一方面,有效载荷是计算机要发送的实际应用数据(例如 HTML 或图像数据)。

主机可以以任何顺序发送、接收和处理数据包,而不管数据包从哪里来,要到哪里去,这就使得多台主机可以 “同时 ”通信。例如,如果一台主机需要同时向另外两台主机传输数据,它就可以在发出的数据包中交替选择目的地。将信息分解成更小的单元还能更容易地检测和弥补传输中的错误。

在大多数情况下,您不必担心数据包和应用程序使用的数据之间的转换问题,因为操作系统会为您完成这项工作。不过,了解数据包在即将看到的网络层中的作用还是很有帮助的。

9.3 网络层

一个功能完善的网络包含一组网络层,称为网络协议栈。任何功能正常的网络都有一个堆栈。典型的互联网堆栈从顶层到底层是这样的:

  • 应用层

包含应用程序和服务器用于通信的 “语言”--通常是某种高级协议。常见的应用层协议包括超文本传输协议(HTTP,用于网络)、加密协议(如 TLS)和文件传输协议(FTP)。应用层协议通常可以组合使用。例如,TLS 通常与 HTTP 结合使用,形成 HTTPS。
应用层处理发生在用户空间。

  • 传输层

定义应用层的数据传输特性。这一层包括数据完整性检查、源端口和目的端口,以及在主机端将应用数据分解成数据包(如果应用层尚未这样做)并在目的端重新组装数据包的规范。传输控制协议(TCP)和用户数据报协议(UDP)是最常见的传输层协议。传输层有时也被称为协议层。

在 Linux 中,传输层及其以下各层主要由内核处理,但也有一些例外情况,数据包会被发送到用户空间进行处理。

  • 网络层或互联网层(Network or internet layer)

定义如何将数据包从源主机传送到目的主机。互联网的特定数据包传输规则被称为互联网协议(IP)。由于我们在本书中只讨论互联网网络,因此我们实际上只讨论互联网层。不过,由于网络层与硬件无关,因此可以在一台主机上同时配置多个独立的网络层,如 IP(IPv4)、IPv6、IPX 和 AppleTalk。

  • 物理层

定义如何通过以太网或调制解调器等物理介质发送原始数据。有时也称为链路层或主机到网络层。
了解网络协议栈的结构非常重要,因为数据在到达目的地的程序之前至少要经过两层。例如,如果从主机 A 向主机 B 发送数据,如图 9-1 所示,字节离开主机 A 上的应用层,经过主机 A 上的传输层和网络层;然后向下到达物理介质,穿过介质,再以同样的方式向上经过各种较低层到达主机 B 上的应用层。如果通过路由器向互联网上的主机发送信息,则会经过路由器上的部分(但通常不是全部)层以及中间的任何其他层。

这些层有时会以奇怪的方式相互渗透,因为按顺序处理所有层的效率可能很低。例如,过去只处理物理层的设备现在有时会同时处理传输层和互联网层数据,以便快速过滤和路由数据。此外,术语本身也会造成混淆。例如,TLS 代表传输层安全,但实际上它位于应用层的更高一层。(在学习基础知识时,不用担心这些恼人的细节)。

我们先来看看你的 Linux 机器是如何连接到网络的,以回答本章开头的 “在哪里 ”的问题。这是堆栈的下层部分--物理层和网络层。稍后,我们将了解回答 “是什么 ”问题的上两层。

注释
你可能听说过另一组层,即开放系统互连 (OSI) 参考模型。这是一个七层网络模型,常用于教学和设计网络,但我们不会介绍 OSI 模型,因为你将直接使用这里介绍的四层。要了解有关层(以及一般网络)的更多信息,请参阅 Andrew S. Tanenbaum 和 David J. Wetherall 的《Computer Networks (5th Edition)》(Prentice Hall,2010 年)。

9.4 Internet层

我们不从网络堆栈最底层的物理层开始,而是从网络层开始,因为这样更容易理解。我们目前所知的互联网是基于互联网协议第 4 版(IPv4)和第 6 版(IPv6)。互联网层最重要的一点是,它是一个软件网络,对硬件或操作系统没有特殊要求。我们的想法是,你可以使用任何操作系统,通过任何类型的硬件发送和接收互联网数据包。

我们的讨论将从 IPv4 开始,因为它更容易读取地址(也更容易理解其局限性),但我们会解释 IPv6 的主要区别。

互联网的拓扑结构是分散的,由称为子网的小型网络组成。我们的想法是,所有子网都以某种方式相互连接。例如,在图 9-1 中,局域网通常是一个子网。

一台主机可以连接到多个子网。如第 9.1 节所述,如果主机能将数据从一个子网传输到另一个子网,那么这种主机就称为路由器(路由器的另一个术语是网关)。图 9-2 对图 9-1 进行了细化,将局域网确定为一个子网,并为每台主机和路由器确定了互联网地址。图中的路由器有两个地址,一个是本地子网 10.23.2.1,另一个是连接互联网的链路(互联网链路的地址现在并不重要,因此只标注为上行链路地址)。我们先看地址,再看子网符号。

每台互联网主机至少有一个数字 IP 地址。对于 IPv4,它的形式是 a.b.c.d,如 10.23.2.37。用这种符号表示的地址称为点四序列。如果主机连接到多个子网,则每个子网至少有一个 IP 地址。每台主机的 IP 地址在整个互联网上都应该是唯一的,但正如你稍后会看到的,专用网络和网络地址转换(NAT)会让这一点变得有点混乱。

先别急着看图 9-2 中的子网符号,我们稍后再讨论。

注意:从技术上讲,IP 地址由 4 个字节(或 32 位)组成,即 abcd。字节 a 和 d 是 1 到 254 之间的数字,b 和 c 是 0 到 255 之间的数字。计算机以原始字节的形式处理 IP 地址。不过,对于人类来说,读写点阵地址(如 10.23.2.37)要容易得多,而不是像十六进制 0x0A170225 这样难看的地址。

IP 地址在某些方面类似于邮政地址。要与另一台主机通信,你的机器必须知道另一台主机的 IP 地址。

让我们来看看你机器上的地址。

9.4.1 查看IP地址

一台机器可以有多个 IP 地址,可容纳多个物理接口、虚拟内部网络等。要查看 Linux 机器上的活动地址,请运行

$ ip address show
#可能会有很多输出(按物理接口分组,见第 9.10 节),但应该包括类似下面这样的内容:
...
2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 40:8d:5c:fc:24:1f brd ff:ff:ff:ff:ff:ff:ff
    inet 10.23.2.4/24 brd 10.23.2.255 scope global noprefixroute enp0s31f6
       valid_lft forever preferred_lft forever

ip 命令的输出包括互联网层和物理层的许多细节。(有时甚至根本不包括互联网地址!)我们稍后将更详细地讨论输出结果,但现在先集中讨论第四行,该行报告主机配置的 IPv4 地址(用 inet 表示)为 10.23.2.4。地址后面的 /24 帮助定义了 IP 地址所属的子网。让我们看看它是如何工作的。

注意: ip 命令是当前的标准网络配置工具。在其他文档中,你可能会看到 ifconfig 命令。这个较老的命令在其他版本的 Unix 中已经使用了几十年,但功能较弱。为了与现代推荐做法保持一致(而且发行版可能默认不包含 ifconfig),我们将使用 ip。ip 取代的其他工具还有 route 和 arp。

9.4.2 子网

子网的定义如前,是指 IP 地址在特定范围内的一组相连主机。例如,范围在 10.23.2.1 至 10.23.2.254 之间的主机可以组成一个子网,范围在 10.23.1.1 至 10.23.255.254 之间的所有主机也可以组成一个子网。通常,子网主机位于同一物理网络中,如图 9-2 所示。

子网由两部分组成:网络前缀(也称为路由前缀)和子网掩码(有时称为网络掩码或路由掩码)。比方说,你想创建一个包含 10.23.2.1 至 10.23.2.254 之间 IP 地址的子网。网络前缀是子网中所有地址共有的部分;在本例中,网络前缀为 10.23.2.0,子网掩码为 255.255.255.0。让我们看看这些数字从何而来。

要了解前缀和掩码如何共同作用,为你提供子网中所有可能的 IP 地址,我们先来看看二进制形式。掩码标记 IP 地址中子网共有的位位置。例如,下面是 10.23.2.0 和 255.255.255.0 的二进制形式。

10.23.2.0: 00001010 00010111 00000010 00000000
255.255.255.0: 11111111 11111111 11111111 00000000

综上所述,我们可以看出,一台 IP 地址为 10.23.2.1、子网掩码为 255.255.255.0 的主机与其他任何 IP 地址以 10.23.2 开头的计算机都在同一个子网中。可以将整个子网表示为 10.23.2.0/255.255.255.0。

现在,让我们来看看这如何变成您从 ip 等工具中看到的速记符号(如 /24)。

9.4.3 常用子网掩码和 CIDR 符号

在大多数互联网工具中,你会遇到一种不同的子网表示形式,称为无类域间路由(CIDR)符号,其中子网(如 10.23.2.0/255.255.255.0)被写成 10.23.2.0/24。这种速记方法利用了子网掩码遵循的简单模式。

看看二进制形式的掩码,就像你在上一节中看到的例子一样。你会发现,所有子网掩码都是(或者说,根据 RFC 1812,应该是)一个 1 字块,后面跟一个 0 字块。例如,你刚才看到的二进制形式的 255.255.255.0 就是 24 个 1 位和 8 个 0 位。CIDR 符号通过子网掩码中前导 1 的个数来识别子网掩码。因此,10.23.2.0/24 这样的组合既包括子网前缀,也包括子网掩码。

表 9-1 显示了几个子网掩码及其 CIDR 形式的示例。/24 子网掩码在本地终端用户网络中最为常见;它通常与第 9.22 节中介绍的专用网络之一结合使用。

注意
如果您不熟悉十进制、二进制和十六进制格式之间的转换,可以使用 bc 或 dc 等计算器实用程序在不同的弧度表示之间进行转换。例如,在 bc 中运行 obase=2; 240 命令,就能以二进制(基数 2)形式打印数字 240。

再往前走一步,你可能已经注意到,如果你已经有了 IP 地址和子网掩码,你甚至不需要费心去单独定义网络。正如第 9.4.1 节所述,你可以将它们结合起来;ip 地址 show 输出包括 10.23.2.4/24。

识别子网及其主机是了解互联网如何工作的第一块基石。不过,你仍然需要将子网连接起来。

9.5 路由和内核路由表

连接互联网子网主要是通过连接到多个子网的主机发送数据的过程。回到图 9-2,想想 IP 地址为 10.23.2.4 的主机 A。这台主机连接到 10.23.2.0/24 的本地网络,可以直接连接到该网络上的主机。要连接到互联网其他部分的主机,它必须通过 10.23.2.1 的路由器(主机)进行通信。

Linux 内核通过使用路由表来确定路由行为,从而区分这两种不同的目的地。要显示路由表,请使用 ip route show 命令。下面是一台简单主机(如 10.23.2.4)的路由表:

$ ip route show
default via 10.23.2.1 dev enp0s31f6 proto static metric 100 
10.23.2.0/24 dev enp0s31f6 proto kernel scope link src 10.23.2.4 metric 100

查看路由的传统工具是 route 命令,以 route -n 运行。-n 选项指示路由显示 IP 地址,而不是试图通过名称显示主机和网络。这是一个需要牢记的重要选项,因为你可以在其他与网络相关的命令(如 netstat)中使用它。

这个输出可能有点难读。每一行都是一条路由规则;让我们从示例中的第二行开始,将其分成几个字段。

第一个字段是 10.23.2.0/24,这是目标网络。与前面的例子一样,这是主机的本地子网。该规则表示,主机可以通过其网络接口直接到达本地子网,目的地后的 dev enp0s31f6 机制标签表示了这一点。(该字段后是有关路由的更多细节,包括如何设置路由。您现在不需要担心这些)。

然后,我们可以回到输出的第一行,其中有目的地网络默认值。这条规则匹配任何主机,也被称为默认路由,将在下一节解释。机制是 via 10.23.2.1,表示使用默认路由的流量将发送到 10.23.2.1(在我们的示例网络中,这是一个路由器);dev enp0s31f6 表示物理传输将在该网络接口上进行。

9.6 默认网关

路由表中的默认网关条目具有特殊意义,因为它可以匹配互联网上的任何地址。在 CIDR 符号中,它是 IPv4 的 0.0.0.0/0。这就是默认路由,而默认路由中作为中介配置的地址就是默认网关。当没有其他规则匹配时,默认路由总是匹配的,当没有其他选择时,默认网关就是你发送信息的地方。你可以配置一台没有默认网关的主机,但它无法连接到路由表中目的地以外的主机。

在大多数净掩码为 /24 (255.255.255.0) 的网络中,路由器通常位于子网的地址 1(例如,10.23.2.0/24 中的 10.23.2.1)。这只是一种惯例,也可能有例外。

内核如何选择路由

路由选择中有一个棘手的细节。假设主机要向 10.23.2.132 发送信息,而这两条路线都符合路由表中的默认路由和 10.23.2.0/24 这两条规则。内核如何知道使用第二条规则?路由表中的顺序并不重要,内核会选择匹配的最长目的地前缀。这就是 CIDR 符号特别有用的地方:10.23.2.0/24 匹配,它的前缀长度为 24 位;0.0.0.0/0 也匹配,但它的前缀长度为 0 位(也就是说,它没有前缀),所以 10.23.2.0/24 的规则优先。

9.7 IPv6 地址和网络

回顾第 9.4 节,可以发现 IPv4 地址由 32 位或 4 个字节组成。这就产生了大约 43 亿个地址,这对于目前的互联网规模来说是不够的。IPv4 地址不足导致了一些问题,因此,互联网工程任务组(IETF)开发了下一个版本--IPv6。在了解更多网络工具之前,我们先来讨论一下 IPv6 地址空间。

一个 IPv6 地址有 128 位-32 个字节,以 8 组 4 字节排列。地址的长式写法如下:

2001:0db8:0a0b:12f0:0000:0000:0000:8b6e

有几种常用的缩写方法。首先,可以省略任何前导零(例如,0db8 变为 db8),而且一组--只有一组--连续的零组可以变为::(两个冒号)。因此,可以将前面的地址写成

2001:db8:a0b:12f0::8b6e

子网仍使用 CIDR 符号表示。对于最终用户来说,子网通常覆盖地址空间可用位数的一半(/64),但也有使用较少位数的情况。地址空间中对每台主机都是唯一的部分称为接口 ID。图 9-3 显示了一个 64 位子网地址的分解示例。

关于 IPv6,最后要知道的是,主机通常至少有两个地址。第一个地址在互联网上有效,称为全局单播地址。第二个地址用于本地网络,称为链路本地地址。链路本地地址的前缀总是 fe80::/10,然后是全零的 54 位网络 ID,最后是 64 位接口 ID。因此,当你在系统上看到一个链路本地地址时,它将位于 fe80::/64 子网中。

全局单播地址的前缀为 2000::/3。由于该前缀的第一个字节以 001 开头,因此该字节可填写为 0010 或 0011。因此,全局单播地址总是以 2 或 3 开头。

9.7.1 查看系统中的 IPv6 配置

如果你的系统有 IPv6 配置,那么你应该已经从之前运行的 ip 命令中获得了一些 IPv6 信息。要单独列出 IPv6,请使用 -6 选项:

$ ip -6 address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 2001:db8:8500:e:52b6:59cc:74e9:8b6e/64 scope global dynamic noprefixroute 
       valid_lft 86136sec preferred_lft 86136sec
    inet6 fe80::d05c:97f9:7be8:bca/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

除了环回接口(我们稍后会讨论),还可以看到两个地址。全局单播地址用 scope global 表示,链路本地地址用 scope link 标签表示。

查看路由的方法与此类似:

$ ip -6 route show
::1 dev lo proto kernel metric 256 pref medium
1 2001:db8:8500:e::/64 dev enp0s31f6 proto ra metric 100 pref medium
2 fe80::/64 dev enp0s31f6 proto kernel metric 100 pref medium
3 default via fe80::800d:7bff:feb8:14a0 dev enp0s31f6 proto ra metric 100 pref medium

这比 IPv4 设置稍微复杂一些,因为同时配置了链路本地子网和全局子网。第二行 1 针对的是本地连接的全局单播地址子网中的目的地;主机知道它可以直接到达这些子网,而 2 下面的链路本地行与此类似。对于默认路由 3(在 IPv6 中也写成 ::/0;记住,这是任何非直接连接的地址),该配置安排流量通过路由器的链路本地地址 fe80::800d:7bff:feb8:14a0,而不是其在全局子网中的地址。稍后你将看到,路由器通常并不关心如何获取流量,只关心流量应该流向哪里。使用链路本地地址作为默认网关的好处是,如果全局 IP 地址空间发生变化,该地址也无需更改。

9.7.2 配置双协议栈网络

正如你现在可能已经猜到的,配置主机和网络同时运行 IPv4 和 IPv6 是可能的。这种网络有时被称为双协议栈网络,但使用协议栈这个词值得商榷,因为在这种情况下,典型的网络协议栈只有一层是重复的(真正的双协议栈应该是 IP+IPX)。撇开迂腐不谈,IPv4 和 IPv6 协议是相互独立的,可以同时运行。在这样的主机上,应用程序(如网络浏览器)可以选择 IPv4 或 IPv6 连接到另一台主机。

最初为 IPv4 编写的应用程序不会自动支持 IPv6。幸运的是,由于堆栈中位于网络层之上的各层没有变化,因此与 IPv6 通信所需的代码很少,而且很容易添加。目前,大多数重要的应用程序和服务器都支持 IPv6。

9.8 基本 ICMP 和 DNS 工具

现在我们来看看一些帮助你与主机交互的基本实用工具。这些工具使用两个特别值得关注的协议: 互联网控制消息协议(ICMP)可以帮助你找出连接和路由问题,域名服务(DNS)系统可以将名称映射到 IP 地址,这样你就不必记住一大堆数字了。

ICMP 是一种传输层协议,用于配置和诊断互联网网络;它与其他传输层协议的不同之处在于,它不携带任何真正的用户数据,因此上面没有应用层。相比之下,DNS 是一个应用层协议,用于将人类可读的名称映射到互联网地址。

9.8.1 ping

ping (见 https://ftp.arl.army.mil/~mike/ping.html)是最基本的网络调试工具之一。它向主机发送 ICMP echo 请求数据包,要求接收主机将数据包返回给发送方。如果收件人主机收到数据包并配置为回复,它就会发送一个 ICMP echo 响应数据包作为回报。

例如,运行 ping 10.23.2.1 会得到以下输出:

$ ping 10.23.2.1
PING 10.23.2.1 (10.23.2.1) 56(84) bytes of data.
64 bytes from 10.23.2.1: icmp_req=1 ttl=64 time=1.76 ms
64 bytes from 10.23.2.1: icmp_req=2 ttl=64 time=2.35 ms
64 bytes from 10.23.2.1: icmp_req=4 ttl=64 time=1.69 ms
64 bytes from 10.23.2.1: icmp_req=5 ttl=64 time=1.61 ms

第一行显示您正在向 10.23.2.1 发送 56 字节的数据包(如果包括报头,则为 84 字节)(默认情况下每秒发送一个数据包),其余各行显示来自 10.23.2.1 的响应。输出中最重要的部分是序列号 (icmp_req) 和往返时间 (time)。返回的字节数是发送数据包的大小加 8(数据包的内容对您来说并不重要)。

序列号中的间隙(如 2 和 4 之间的间隙)通常意味着存在某种连接问题。由于 ping 一秒钟只发送一个数据包,因此数据包的到达顺序不应该有偏差。如果一个响应需要超过一秒(1,000 毫秒)才能到达,说明连接速度非常慢。

往返时间是指从请求数据包发出到响应数据包到达之间的总时间。如果无法到达目的地,最后看到数据包的路由器会向 ping 返回一个 ICMP “主机不可达 ”数据包。

在有线局域网中,数据包绝对不会丢失,往返时间也会非常短。(前面的示例输出来自无线网络。)您还应预计从您的网络到 ISP 的数据包不会丢失,往返时间也会相当稳定。

注意:出于安全原因,互联网上的某些主机会禁止响应 ICMP echo 请求数据包,因此您可能会发现,您可以连接到某台主机上的网站,但却得不到 ping 响应。

可以分别使用 -4 和 -6 选项强制 ping 使用 IPv4 或 IPv6。

9.8.2 DNS和主机

IP 地址难以记忆,而且容易更改,因此我们通常使用 www.example.com 等名称来代替。系统上的域名服务 (DNS) 库通常会自动处理这种转换,但有时也需要手动转换名称和 IP 地址。要查找域名背后的 IP 地址,请使用 host 命令:

$ host www.example.com
example.com has address 172.17.216.34
example.com has IPv6 address 2001:db8:220:1:248:1893:25c8:1946

请注意,这个例子既有 IPv4 地址 172.17.216.34,也有更长的 IPv6 地址。一个主机名可能有不止一个地址,输出结果可能包含邮件交换器等附加信息。

您还可以反向使用 host:输入 IP 地址而不是主机名,尝试查找 IP 地址背后的主机名。不过,不要指望这种方法能可靠地工作。一个 IP 地址可能与多个主机名相关联,而 DNS 不知道如何确定哪个主机名应与 IP 地址相对应。此外,该主机的管理员需要手动设置反向查询,而管理员通常不会这样做。

除了 host 命令,DNS 还有很多其他功能。我们将在第 9.15 节介绍基本的客户端配置。

host 有 -4 和 -6 选项,但它们的作用与你想象的不同。它们强制 host 命令通过 IPv4 或 IPv6 获取信息,但由于无论使用何种网络协议,信息都应该是相同的,因此输出可能包括 IPv4 和 IPv6。

9.9 物理层和以太网

了解互联网的一个要点是它是一个软件网络。事实上,互联网成功的原因之一就是它几乎可以在任何类型的计算机、操作系统和物理网络上运行。但是,如果你真的想与另一台计算机通话,你仍然必须在某种硬件上设置一个网络层。这个接口就是物理层。

在本书中,我们将介绍最常见的物理层:以太网网络。IEEE 802 系列标准文件定义了从有线到无线等多种不同类型的以太网网络,但它们都有几个共同点:

以太网上的所有设备都有一个媒体访问控制(MAC)地址,有时也称为硬件地址。该地址独立于主机的 IP 地址,是主机所在以太网网络(但不一定是互联网等更大的软件网络)的唯一地址。MAC 地址示例为 10:78:d2:eb:76:97。
以太网上的设备以帧的形式发送信息,帧是发送数据的包装。一个帧包含源 MAC 地址和目的 MAC 地址。
以太网并没有试图超越单个网络上的硬件。例如,如果你有两个不同的以太网网络,一台主机同时连接到两个网络(以及两个不同的网络接口设备),那么除非建立以太网桥,否则你无法直接将帧从一个以太网网络传输到另一个网络。这就是高级网络层(如互联网层)的作用所在。按照惯例,每个以太网网络通常也是一个互联网子网。虽然帧不能离开一个物理网络,但路由器可以将数据从帧中取出,重新打包,然后发送到不同物理网络上的主机,这正是互联网上发生的事情。

9.10 理解内核网络接口

物理层和互联网层必须连接起来,这样互联网层才能保持与硬件无关的灵活性。Linux 内核自行划分了这两层,并提供了一种连接这两层的通信标准,称为(内核)网络接口。配置网络接口时,要将互联网端的 IP 地址设置与物理设备端的硬件标识联系起来。网络接口的名称通常表示其下的硬件类型,如 enp0s31f6(PCI 插槽中的接口)。这样的名称被称为可预测的网络接口设备名称,因为它在重启后保持不变。启动时,接口有传统的名称,如 eth0(计算机中的第一个以太网卡)和 wlan0(无线接口),但在大多数运行 systemd 的机器上,它们很快就会被重命名。

在第 9.4.1 节中,我们学习了如何使用 ip 地址 show 查看网络接口设置。输出结果按接口排列。下面是我们之前看到的

2: enp0s31f6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state 1 UP group default qlen 1000
    2 link/ether 40:8d:5c:fc:24:1f brd ff:ff:ff:ff:ff:ff
    inet 10.23.2.4/24 brd 10.23.2.255 scope global noprefixroute enp0s31f6
       valid_lft forever preferred_lft forever
    inet6 2001:db8:8500:e:52b6:59cc:74e9:8b6e/64 scope global dynamic noprefixroute 
       valid_lft 86054sec preferred_lft 86054sec
    inet6 fe80::d05c:97f9:7be8:bca/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

每个网络接口都有一个编号,这个是 2。 接口 1 几乎总是第 9.16 节中描述的环回接口。UP 标志表示接口 1 正在工作。除了我们已经介绍过的互联网层信息外,我们还能看到物理层的 MAC 地址,即链路/ether 2。

虽然 ip 显示了一些硬件信息,但它主要是用于查看和配置连接到接口的软件层。要深入研究网络接口背后的硬件和物理层,可以使用 ethtool 命令来显示或更改以太网卡的设置。(我们将在第 9.27 节简要介绍无线网络)。

9.11 网络接口配置简介

现在你已经了解了网络堆栈底层的所有基本要素:物理层、网络(互联网)层和 Linux 内核的网络接口。为了将这些元素组合起来,将 Linux 机器连接到互联网,你或某个软件必须完成以下工作:

  • 连接网络硬件,确保内核有相应的驱动程序。如果存在驱动程序,即使尚未配置,ip 地址显示也会包含设备条目。
  • 执行其他物理层设置,如选择网络名称或密码。
  • 为内核网络接口分配 IP 地址和子网,以便内核的设备驱动程序(物理层)和互联网子系统(互联网层)可以相互通信。
  • 添加其他必要路由,包括默认网关。

当所有机器都是连接在一起的固定大箱子时,这个过程相对简单:内核完成第 1 步,你不需要第 2 步,使用旧的 ifconfig 命令完成第 3 步,使用旧的路由命令完成第 4 步。我们将简要介绍如何使用 ip 命令来实现这一点。

9.11.1 手动配置接口

现在我们来看看如何手动设置接口,但我们不会讲得太详细,因为这样做很少需要,而且容易出错。通常只有在试验系统时才会这样做。即使是在配置时,也不妨使用 Netplan 等工具在文本文件中建立配置,而不是使用下图所示的一系列命令。

使用 ip 命令可以将接口绑定到互联网层。要为内核网络接口添加 IP 地址和子网,可以这样做:

# ip address add address/subnet dev interface

这里,interface 是接口的名称,如 enp0s31f6 或 eth0。这同样适用于 IPv6,只是需要添加参数(例如,用于指示链路本地状态)。如果您想了解所有选项,请参阅 ip-address(8) 手册页面。

9.11.2 手动添加和删除路由

当接口启动后,就可以添加路由了,通常只需设置默认网关,如下所示:

# ip route add default via gw-address dev interface

gw-address 参数是默认网关的 IP 地址;它必须是本地连接的子网中分配给某个网络接口的地址。

要删除默认网关,请运行

# ip route del default

你可以用其他路由轻松覆盖默认网关。例如,你的机器位于子网 10.23.2.0/24,你想访问位于 192.168.45.0/24 的子网,你知道位于 10.23.2.44 的主机可以充当该子网的路由器。运行此命令可将绑定到 192.168.45.0 的流量发送到该路由器:

# ip route add 192.168.45.0/24 via 10.23.2.44

删除路由时无需指定路由器:

# ip route del 192.168.45.0/24

在疯狂使用路由之前,你应该知道路由的配置往往比表面看起来要复杂得多。在这个特殊的例子中,你还必须确保 192.163.45.0/24 上所有主机的路由都能返回 10.23.2.0/24,否则你添加的第一条路由基本上就没用了。

通常情况下,你应该尽量保持简单,设置本地网络,使其主机只需要一个默认路由。如果需要多个子网并在它们之间建立路由,通常最好配置路由器作为默认网关,完成不同本地子网之间的所有路由工作。(您将在第 9.21 节中看到一个示例)。

9.12 启动网络配置

我们已经讨论过手动配置网络的方法,而确保机器网络配置正确性的传统方法是让 init 在启动时运行一个脚本来运行手动配置。这可以归结为在启动事件链的某个环节运行类似 ip 这样的工具。

Linux 曾多次尝试将启动时网络配置文件标准化。例如,启动脚本可以(理论上)运行 ifup eth0 来运行正确的 ip 命令来设置接口。不幸的是,不同的发行版对 ifup 和 ifdown 的实现完全不同,因此它们的配置文件也各不相同。

由于网络配置元素存在于每个不同的网络层中,因此存在更深层次的差异;其后果是,负责实现网络连接的软件分属于内核和用户空间工具的多个部分,由不同的开发人员编写和维护。在 Linux 中,人们普遍同意不同工具套件或库之间不共享配置文件,因为为一个工具所做的更改可能会破坏另一个工具。

在多个不同的地方处理网络配置会给系统管理带来困难。因此,出现了几种不同的网络管理工具,它们都有自己的方法来解决配置问题。不过,这些工具往往是针对 Linux 机器所能发挥的特定作用而专门设计的。一种工具可能适用于台式机,但不适合服务器。

一种名为 Netplan 的工具为解决配置问题提供了一种不同的方法。与其说 Netplan 是管理网络,不如说它是一个统一的网络配置标准,以及一个将配置转换为现有网络管理员使用的文件的工具。目前,Netplan 支持 NetworkManager 和 systemd-networkd,我们将在本章稍后介绍。Netplan 文件采用 YAML 格式,位于 /etc/netplan 中。

在讨论网络配置管理器之前,我们先来看看它们面临的一些问题。

9.13 手动和启动激活网络配置的问题

尽管大多数系统都在启动机制中配置网络,而且许多服务器仍然如此,但现代网络的动态特性意味着大多数机器都没有静态(不变)的 IP 地址。在 IPv4 中,机器不是在机器上存储 IP 地址和其他网络信息,而是在首次连接到本地物理网络时从该网络的某个地方获取这些信息。大多数普通的网络客户端程序并不特别关心机器使用的 IP 地址,只要能正常工作就行。动态主机配置协议(DHCP,详见第 9.19 节)工具会对典型的 IPv4 客户端进行基本的网络层配置。在 IPv6 中,客户端可以在一定程度上进行自我配置;我们将在第 9.20 节中对此进行简要介绍。

不过,故事还不止这些。例如,无线网络为接口配置增加了更多的内容,如网络名称、身份验证和加密技术。退一步来看,你会发现系统需要一种方法来回答以下问题:

  • 如果机器有多个物理网络接口(如带有有线和无线以太网的笔记本电脑),如何选择使用哪个接口?
  • 机器应如何设置物理接口?对于无线网络,这包括扫描网络名称、选择名称和协商验证。
  • 连接物理网络接口后,机器应如何设置软件网络层,如互联网层?
  • 如何让用户选择连接选项?例如,如何让用户选择无线网络?
  • 如果网络接口失去连接,机器该怎么办?
  • 回答这些问题通常不是简单的启动脚本所能胜任的,而且手工操作也非常麻烦。答案是使用系统服务,它可以监控物理网络,并根据一套对用户有意义的规则选择(并自动配置)内核网络接口。该服务还应该能够响应用户的请求,而用户也应该能够在无需成为 root 的情况下更改他们所在的无线网络。

9.14 网络配置管理器

在基于 Linux 的系统中,有几种自动配置网络的方法。在台式机和笔记本电脑上使用最广泛的是 NetworkManager。systemd 有一个名为 systemd-networkd 的附加组件,可以进行基本的网络配置,对于不需要太多灵活性的机器(如服务器)很有用,但它不具备 NetworkManager 的动态功能。其他网络配置管理系统主要针对小型嵌入式系统,如 OpenWRT 的 netifd、Android 的 ConnectivityManager 服务、ConnMan 和 Wicd。

我们将简要讨论 NetworkManager,因为这是你最有可能遇到的系统。不过我们不会讨论太多细节,因为当你了解了基本概念后,NetworkManager 和其他配置系统就更容易理解了。如果你对 systemd-networkd 感兴趣,systemd.network(5) 手册页面会介绍其设置,配置目录为 /etc/systemd/network。

9.14.1 NetworkManager操作

NetworkManager 是系统启动时启动的守护进程。与大多数守护进程一样,它不依赖于正在运行的桌面组件。它的工作是监听来自系统和用户的事件,并根据一组规则更改网络配置。

运行时,NetworkManager 维护两个基本配置级别。第一层是有关可用硬件设备的信息集合,它通常从内核收集这些信息,并通过监控桌面总线(D-Bus)上的 udev 来维护这些信息。第二个配置层是更具体的连接列表:硬件设备和附加的物理层和网络层配置参数。例如,无线网络可以表示为一个连接。

为了激活连接,NetworkManager 通常会将任务委托给其他专门的网络工具和守护进程(如 dhclient),以便从本地连接的物理网络中获取互联网层配置。由于不同发行版的网络配置工具和方案各不相同,NetworkManager 使用插件与它们对接,而不是强加自己的标准。例如,Debian/Ubuntu 和 Red Hat 风格的界面配置都有相应的插件。

启动时,NetworkManager 会收集所有可用的网络设备信息,搜索连接列表,然后决定尝试激活其中一个。对于以太网接口,NetworkManager 是这样决定的:

  • 如果有线连接可用,则尝试使用它进行连接。否则,尝试无线连接。
  • 扫描可用无线网络列表。如果有以前连接过的可用网络,NetworkManager 会再次尝试。
  • 如果有多个以前连接过的无线网络可用,则选择最近连接过的网络。

建立连接后,NetworkManager 将保持该连接,直到连接丢失、出现更好的网络(例如,在无线连接时插入网线)或用户强制更改。

9.14.2 与 NetworkManager 的交互
大多数用户通过桌面上的小程序与 NetworkManager 进行交互;通常是右上角或右下角的一个图标,显示连接状态(有线、无线或未连接)。单击该图标后,你将获得一系列连接选项,如无线网络选择和断开当前网络连接的选项。每个桌面环境都有自己版本的小程序,因此在每个桌面环境中看起来都有些不同。

除了小程序,你还可以使用一些工具从 shell 中查询和控制 NetworkManager。要快速了解当前的连接状态,可以使用 nmcli 命令(不带参数)。你会得到一个接口和配置参数的列表。在某些方面,它与 ip 类似,只是细节更多,尤其是在查看无线连接时。

nmcli 命令允许你通过命令行控制 NetworkManager。事实上,除了常见的 nmcli(1) 手册页面外,还有 nmcli-examples(5) 手册页面。

最后,nm-online 工具会告诉你网络是运行还是关闭。如果网络正常,命令返回的退出代码为 0,否则为非 0。(有关如何在 shell 脚本中使用退出代码的更多信息,请参见第 11 章。)

9.14.3 NetworkManager配置

NetworkManager的常规配置目录通常是/etc/NetworkManager,有几种不同的配置。一般配置文件为 NetworkManager.conf。其格式类似于 XDG 风格的 .desktop 和微软的 .ini 文件,键值参数分为不同的部分。你会发现,几乎每个配置文件都有一个 [main] 部分,用于定义要使用的插件。下面是一个激活 Ubuntu 和 Debian 使用的 ifupdown 插件的简单示例:

[main]
plugins=ifupdown,keyfile

其他特定发行版的插件有 ifcfg-rh(用于 Red Hat 风格的发行版)和 ifcfg-suse(用于 SuSE)。你在这里看到的 keyfile 插件支持 NetworkManager 的本地配置文件支持。使用该插件时,你可以在 /etc/NetworkManager/system-connections 中看到系统的所有已知连接。

在大多数情况下,你不需要修改 NetworkManager.conf,因为在其他文件中可以找到更具体的配置选项。

  • 非托管接口

尽管你可能希望 NetworkManager 管理大部分网络接口,但有时你也会希望它忽略接口。例如,大多数用户都不需要对 localhost(lo,参见第 9.16 节)接口进行任何动态配置,因为它的配置从不改变。你还希望在启动过程中尽早配置该接口,因为基本的系统服务通常依赖于它。大多数发行版都让 NetworkManager 远离 localhost。

通过使用插件,你可以让 NetworkManager 忽略某个接口。如果你正在使用 ifupdown 插件(例如在 Ubuntu 和 Debian 中),请将接口配置添加到 /etc/network/interfaces 文件,然后在 NetworkManager.conf 文件的 ifupdown 部分将 managed 的值设置为 false:

[ifupdown]
managed=false

对于 Fedora 和 Red Hat 使用的 ifcfg-rh 插件,请在包含 ifcfg-* 配置文件的/etc/sysconfig/network-scripts 目录中查找如下一行:

NM_CONTROLLED=yes

如果这一行不存在或值设置为否,NetworkManager 将忽略该接口。就 localhost 而言,你会在 ifcfg-lo 文件中发现它已被停用。你也可以指定要忽略的硬件地址,如下面这样:

HWADDR=10:78:d2:eb:76:97

如果不使用这两种网络配置方案,仍可使用 keyfile 插件,在 NetworkManager.conf 文件中使用 MAC 地址直接指定非托管设备。下面的示例显示了两个非托管设备:

[keyfile]
unmanaged-devices=mac:10:78:d2:eb:76:97;mac:1c:65:9d:cc:ff:b9
  • 调度

NetworkManager 配置的最后一个细节与指定网络接口上升或下降时的附加系统操作有关。例如,某些网络守护进程需要知道何时开始或停止监听接口才能正常工作(如下一章讨论的安全 shell 守护进程)。

当系统的网络接口状态发生变化时,NetworkManager 会运行 /etc/NetworkManager/dispatcher.d 中的所有程序,并在其中加入参数,如 up 或 down。这相对来说比较简单,但许多发行版都有自己的网络控制脚本,因此不会将各个调度程序脚本放在这个目录下。例如,Ubuntu 就只有一个名为 01ifupdown的脚本,该脚本会在 /etc/network 的适当子目录下运行所有内容,如 /etc/network/if-up.d。

与 NetworkManager 配置的其他部分一样,这些脚本的细节相对来说并不重要;你需要知道的只是如何在需要添加或更改时找到适当的位置(或者使用 Netplan,让它为你找出位置)。与以往一样,不要羞于查看系统中的脚本。

9.15 解析主机名

任何网络配置的最后一项基本任务都是使用 DNS 解析主机名。你已经看过主机解析工具,它可以将 www.example.com 这样的名称转换为 10.23.2.132 这样的 IP 地址。

DNS 与我们迄今为止研究过的网络元素不同,因为它位于应用层,完全在用户空间内。因此,从技术上讲,它与本章的互联网和物理层讨论略有出入。但是,如果没有正确的 DNS 配置,你的互联网连接几乎毫无价值。正常人都不会为网站和电子邮件地址公布 IP 地址(更不用说 IPv6 地址了),因为主机的 IP 地址是可以更改的,而且记住一串数字并不容易。

实际上,Linux 系统上的所有网络应用程序都会执行 DNS 查找。解析过程通常是这样展开的:

  • 应用程序调用一个函数来查找主机名背后的 IP 地址。该函数位于系统的共享库中,因此应用程序不需要了解其工作原理的细节,也不需要知道其实现方式是否会发生变化。
  • 当共享库中的函数运行时,它会根据一系列规则(见 /etc/nswitch.conf;第 9.15.4 节)来确定查找的行动计划。例如,这些规则通常规定,即使在使用 DNS 之前,也要检查 /etc/hosts 文件中是否有手动覆盖。
  • 当函数决定使用 DNS 进行名称查询时,它会查阅额外的配置文件来查找 DNS 名称服务器。名称服务器以 IP 地址的形式给出。
  • 函数通过网络向名称服务器发送 DNS 查询请求。
  • 名称服务器回复主机名的 IP 地址,函数将此 IP 地址返回给应用程序。

这是简化版本。在一个典型的现代系统中,有更多的参与者试图加快交易速度或增加灵活性。我们暂且不谈这些,先来看看其中的一些基本部分。与其他类型的网络配置一样,您可能不需要更改主机名解析,但了解其工作原理还是很有帮助的。

9.15.1 /etc/hosts

在大多数系统中,您可以使用 /etc/hosts 文件覆盖主机名查询。文件通常如下所示:

127.0.0.1       localhost
10.23.2.3       atlantic.aem7.net       atlantic
10.23.2.4       pacific.aem7.net         pacific
::1             localhost ip6-localhost

你几乎总能在这里看到 localhost 的条目(或多个条目)(参见第 9.16 节)。此处的其他条目说明了在本地子网中添加主机的简单方法。

注意:在过去,每个人都会复制一个中央主机文件到自己的机器上,以便保持更新(参见 RFC 606、608、623 和 625),但随着 ARPANET/internet 的发展,这种做法很快就失去了控制。

9.15.2 resolv.conf

DNS 服务器的传统配置文件是 /etc/resolv.conf。在情况比较简单的时候,一个典型的例子可能是这样的:ISP 的域名服务器地址是 10.32.45.23 和 10.3.2.3:

search mydomain.example.com example.com
nameserver 10.32.45.23
nameserver 10.3.2.3

搜索行定义了不完整主机名的规则(仅主机名的第一部分,例如,myserver 而不是 myserver.example.com)。在此,解析器库将尝试查找 host.mydomain.example.com 和 host.example.com。

一般来说,名称查询不再如此简单。DNS 配置已做了许多改进和修改。

9.15.3 缓存和零配置 DNS

传统 DNS 配置有两个主要问题。首先,本地计算机不会缓存名称服务器的回复,因此频繁重复的网络访问可能会因名称服务器请求而造成不必要的缓慢。为了解决这个问题,许多机器(如果充当名称服务器,还包括路由器)运行一个中间守护进程,拦截名称服务器请求并缓存回复,然后尽可能使用缓存的答案。这些守护进程中最常见的是 systemd-resolved;你也可能在系统中看到 dnsmasq 或 nscd。您还可以将 BIND(标准 Unix 名称服务器守护进程)设置为缓存。如果在/etc/resolv.conf 文件中看到 127.0.0.53 或 127.0.0.1,或在运行 nslookup -debug host 时将其列为服务器,通常就能判断出运行的是名称服务器缓存守护进程。不过,请仔细看看。如果运行的是 systemd-resolved,你可能会发现 resolv.conf 甚至不是 /etc 文件中的一个文件;它只是一个指向 /run 文件中自动生成的文件的链接。

systemd-resolved 的功能远不止这些,它可以将多个名称查询服务结合起来,并在每个接口上以不同的方式公开。这就解决了传统名称服务器设置的第二个问题:如果你想在本地网络上查找名称,而又不想进行大量的配置,那么这种设置就会显得特别不灵活。例如,如果你在网络上设置了一个网络设备,你就希望能立即通过名称调用它。这就是零配置名称服务系统(如多播 DNS (mDNS) 和链路本地多播名称解析 (LLMNR))的部分理念。如果一个进程想通过名称查找本地网络上的主机,它只需在网络上广播一个请求;如果存在,目标主机会回复其地址。这些协议不仅提供主机名解析,还提供有关可用服务的信息。

你可以使用 resolvectl status 命令检查当前的 DNS 设置(注意,在旧版系统中可能称为 systemd-resolve)。你会看到一个全局设置列表(通常用处不大),然后会看到每个接口的设置。看起来是这样的

Link 2 (enp0s31f6)
      Current Scopes: DNS
       LLMNR setting: yes
MulticastDNS setting: no
      DNSSEC setting: no
    DNSSEC supported: no
         DNS Servers: 8.8.8.8
          DNS Domain: ~.

你可以在这里看到各种支持的名称协议,以及 systemd-resolved 在遇到不知道的名称时会咨询的名称服务器。

由于 DNS 或 systemd-resolved 是一个庞大的话题,我们将不再深入探讨。如果你想更改设置,可以查看 resolved.conf(5) 手册页面,然后修改 /etc/systemd/resolved.conf。不过,您可能需要阅读大量 systemd-resolved 文档,并通过诸如 Cricket Liu 和 Paul Albitz 合著的《DNS 和 BIND》第 5 版(O'Reilly,2006 年)等资料熟悉 DNS 的一般情况。

9.15.4 /etc/nsswitch.conf

在结束名称查询主题之前,还有最后一项设置需要注意。/etc/nsswitch.conf文件是控制系统中与名称相关的若干优先级设置(如用户和密码信息)的传统界面,其中有一个主机查找设置。系统中的文件应该有这样一行:

hosts: files dns

将 files 放在 dns 之前,可以确保在查找主机时,系统在询问任何 DNS 服务器(包括 systemd-resolved)之前,都会先检查 /etc/hosts 文件以查找主机。这通常是个好主意(尤其是在查找 localhost 时,如下文所述),但你的 /etc/hosts 文件应尽可能简短。不要为了提高性能而在文件中添加任何内容;这样做以后会让你焦头烂额。你可以将小型私人局域网内的主机放入 /etc/hosts,但一般的经验法则是,如果某台主机有 DNS 条目,就不能将其放入 /etc/hosts。(/etc/hosts文件还可用于在启动初期(网络可能不可用)解析主机名)。

所有这些都是通过系统库中的标准调用实现的。要记住所有可以进行名称查询的地方可能比较复杂,但如果需要自下而上地跟踪某些内容,可以从 /etc/nsswitch.conf 开始。

9.16 Localhost

运行 ip address show 时,你会注意到 lo 接口:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    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

lo 接口是一个虚拟网络接口,因为它 “环回 ”自身,所以被称为环回接口。其效果是,连接到 127.0.0.1(或 IPv6 中的 ::1)就是连接到当前使用的机器。当传出到 localhost 的数据到达 lo 的内核网络接口时,内核会将其重新打包为传入数据,并通过 lo 发送回来,供任何正在监听的服务器程序使用(默认情况下,大多数服务器程序都在监听)。

lo 回环接口通常是启动时脚本中唯一能看到静态网络配置的地方。例如,Ubuntu的ifup命令会读取/etc/network/interfaces。不过,这通常是多余的,因为 systemd 会在启动时配置环回接口。

环回接口有一个特点,你可能已经注意到了。网络掩码是 /8,以 127 开头的任何地址都分配给环回。这样,你就可以在环回空间的不同 IPv4 地址上运行不同的服务器,而无需配置额外的接口。systemd-resolved 服务器就利用了这一点,它使用 127.0.0.53。这样,它就不会干扰运行在 127.0.0.1 上的另一个名称服务器。到目前为止,IPv6 只定义了一个环回地址,但有人建议改变这种情况。

9.17 传输层: TCP、UDP 和服务

到目前为止,我们只了解了数据包如何在互联网上从一台主机移动到另一台主机--换句话说,也就是本章开头的 “在哪里 ”的问题。现在,让我们开始回答 “传输什么 ”的问题。了解计算机如何将从其他主机接收到的数据包数据呈现给运行中的进程非常重要。对于用户空间程序来说,要像内核那样处理一堆原始数据包既困难又不方便。灵活性尤为重要:多个应用程序应能同时与网络对话(例如,你可能有电子邮件和多个网络客户端在运行)。

传输层协议是互联网层原始数据包与应用程序精细化需求之间的桥梁。最常用的两种传输协议是传输控制协议(TCP)和用户数据报协议(UDP)。我们将集中讨论 TCP,因为它是迄今为止最常用的协议,但我们也会快速了解一下 UDP。

9.17.1 TCP 端口和连接

TCP 通过网络端口在一台机器上提供多个网络应用程序,网络端口只是与 IP 地址结合使用的数字。如果说主机的 IP 地址就像公寓楼的邮政地址,那么端口号就像邮箱号码--是进一步的细分。

使用 TCP 时,应用程序会在自己机器上的一个端口和远程主机上的一个端口之间打开一个连接(不要与 NetworkManager 连接混淆)。例如,网络浏览器等应用程序可以在本机的 36406 端口和远程主机的 80 端口之间打开连接。从应用程序的角度来看,36406 端口是本地端口,80 端口是远程端口。

您可以通过 IP 地址和端口号来识别连接。要查看机器上当前打开的连接,请使用 netstat。下面是一个显示 TCP 连接的示例;-n 选项禁用主机名解析(DNS),-t 将输出限制为 TCP:

$ netstat -nt
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address          Foreign Address         State      
tcp        0      0 10.23.2.4:47626        10.194.79.125:5222      ESTABLISHED
tcp        0      0 10.23.2.4:41475        172.19.52.144:6667      ESTABLISHED
tcp        0      0 10.23.2.4:57132        192.168.231.135:22      ESTABLISHED

Local Address(本地地址)和 Foreign Address(外来地址)字段指的是从机器角度看的连接,因此这里的机器配置的接口是 10.23.2.4,本地端的端口 47626、41475 和 57132 都已连接。第一个连接显示端口 47626 连接到 10.194.79.125 的端口 5222。

要只显示 IPv6 连接,请在 netstat 选项中添加 -6。

  • 建立 TCP 连接

要建立传输层连接,一台主机上的进程会通过一系列特殊的数据包,将其本地端口连接到另一台主机上的端口。为了识别传入的连接并做出响应,第二台主机必须有一个进程在正确的端口上监听。通常,连接进程被称为客户端,监听进程被称为服务器(更多内容请参见第 10 章)。

关于端口,最重要的一点是,客户端会选择一个当前未使用的端口,并几乎总是连接到服务器端的某个知名端口。请回顾上一节中 netstat 命令的输出结果:

Proto Recv-Q Send-Q Local Address          Foreign Address         State      
tcp        0      0 10.23.2.4:47626        10.194.79.125:5222      ESTABLISHED

稍稍了解一下端口号的约定,你就会发现这个连接很可能是由本地客户端发起的,指向远程服务器,因为本地端的端口(47626)看起来像是一个动态分配的号码,而远程端口(5222)则是 /etc/services 中列出的一个众所周知的服务(具体来说是 Jabber 或 XMPP 消息传递服务)。在大多数台式机上,你会看到许多连接到 443 端口(HTTPS 的默认端口)的连接。

注意:动态分配的端口称为短暂端口。

不过,如果输出中的本地端口众所周知,则可能是远程主机发起的连接。在本例中,远程主机 172.24.54.234 已连接到本地主机的 443 端口:

Proto Recv-Q Send-Q Local Address          Foreign Address         State      
tcp        0      0 10.23.2.4:47626        10.194.79.125:5222      ESTABLISHED

远程主机通过一个众所周知的端口连接到您的计算机,这意味着您本地计算机上的服务器正在监听该端口。要确认这一点,请使用 netstat 列出机器正在监听的所有 TCP 端口,这次使用 -l 选项,该选项显示进程正在监听的端口:

$ netstat -ntl
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address          Foreign Address         State          
1 tcp        0      0 0.0.0.0:80             0.0.0.0:*               LISTEN
2 tcp        0      0 0.0.0.0:443            0.0.0.0:*               LISTEN          
3 tcp        0      0 127.0.0.53:53          0.0.0.0:*               LISTEN     
--snip--

以 0.0.0.0:80 作为本地地址的第 1 行显示,本地机器正在 80 端口监听来自任何远程机器的连接;443 端口也是如此(第 2 行)。服务器可以限制对某些接口的访问,如第 3 行所示,在该行中,只有 localhost 接口在监听连接。在这种情况下,它是由 systemd 解析的;我们在第 9.16 节中讨论过为什么它使用 127.0.0.53 而不是 127.0.0.1 进行监听。要了解更多信息,请使用 lsof 来识别正在监听的特定进程(第 10.5.1 节已讨论过)。

  • 端口号和 /etc/services

如何知道某个端口是否众所周知?没有唯一的方法,但可以从 /etc/services 开始。这是一个纯文本文件。你应该能看到这样的条目

ssh             22/tcp              # SSH Remote Login Protocol
smtp            25/tcp
domain          53/udp

第一列是名称,第二列表示端口号和特定传输层协议(可以是 TCP 以外的协议)。

注意:除/etc/services 外,http://www.iana.org/ 上的端口在线注册表也受 RFC 6335 网络标准文件的管理。

在Linux上,只有以超级用户身份运行的进程才能使用端口 1 至 1023,这些端口也称为系统端口、众所周知的端口或特权端口。所有用户进程都可以监听和创建 1024 及以上端口的连接。

  • TCP的特点

TCP 作为传输层协议很受欢迎,因为它对应用方面的要求相对较低。应用程序进程只需知道如何打开(或监听)、读取、写入和关闭连接。对应用程序来说,似乎有数据流进入和流出;整个过程就像处理文件一样简单。

然而,这背后还有很多工作要做。首先,TCP 实现需要知道如何将一个进程的传出数据流分解成数据包。不过,更难的是如何将一系列进入的数据包转换成输入数据流供进程读取,尤其是当进入的数据包不一定以正确的顺序到达时。此外,使用 TCP 的主机必须检查错误:数据包在互联网上发送时可能会丢失或错乱,TCP 实现必须检测并纠正这些情况。图 9-4 显示了主机如何使用 TCP 发送信息的简化过程。

幸运的是,除了知道 Linux TCP 实现主要在内核中,以及使用传输层的实用程序倾向于操作内核数据结构外,你几乎不需要了解这些乱七八糟的东西。第 9.25 节讨论的 iptables 数据包过滤系统就是一个例子。

9.17.2 UDP

UDP 是一种比 TCP 简单得多的传输层。它只定义了单个信息的传输;没有数据流。同时,与 TCP 不同,UDP 不会纠正丢失或失序的数据包。事实上,尽管 UDP 有端口,但它甚至没有连接!一台主机只需从它的一个端口向服务器上的一个端口发送信息,服务器就会根据需要发回信息。不过,UDP 对数据包内的数据有错误检测功能;主机可以检测到数据包是否被弄错,但不必做任何处理。

TCP 就像电话交谈,而 UDP 就像发送信件、电报或即时信息(只不过即时信息更可靠)。使用 UDP 的应用程序通常关注的是速度--尽可能快地发送信息。它们不需要 TCP 的开销,因为它们认为两台主机之间的网络通常是可靠的。它们不需要 TCP 的纠错功能,因为它们要么有自己的错误检测系统,要么根本不在乎错误。

网络时间协议(NTP)就是使用 UDP 的一个应用实例。客户端向服务器发送简短的请求以获取当前时间,服务器的响应也同样简短。如果服务器的响应在网络中丢失,客户端可以重新发送请求或放弃。另一个例子是视频聊天。在这种情况下,图片通过 UDP 发送,如果途中丢失了某些片段,接收端的客户端会尽力补偿。

本章其余部分将讨论更高级的网络主题,如网络过滤和路由器,因为它们与我们已经了解的较低网络层(物理层、网络层和传输层)有关。如果你愿意,可以跳到下一章,看看在用户空间中汇聚一切的应用层。你将看到真正使用网络的进程,而不仅仅是一堆地址和数据包。

9.18 重温简单本地网络

现在我们来看看第 9.4 节中介绍的简单网络的其他组件。该网络由一个作为子网的局域网和一个连接该子网与互联网其他部分的路由器组成。你将学到以下内容:

  • 子网中的主机如何自动获取网络配置
  • 如何设置路由
  • 路由器到底是什么
  • 如何知道子网使用哪些 IP 地址
  • 如何设置防火墙以过滤来自互联网的不需要的流量

在大多数情况下,我们将专注于 IPv4(如果没有其他原因,只是因为地址更容易阅读),但当 IPv6 有所不同时,你就会明白是怎么回事了。

我们先来看看子网中的主机是如何自动获取网络配置的。

9.19 了解DHCP

在 IPv4 环境下,当你设置网络主机从网络自动获取配置时,就是告诉它使用动态主机配置协议(DHCP)来获取 IP 地址、子网掩码、默认网关和 DNS 服务器。除了无需手动输入这些参数外,网络管理员还能利用 DHCP 获得其他优势,如防止 IP 地址冲突和最大限度地减少网络变更的影响。很少看到不使用 DHCP 的网络。

主机要使用 DHCP 获取配置,必须能够向所连接网络上的 DHCP 服务器发送信息。因此,每个物理网络都应该有自己的 DHCP 服务器,在简单的网络中(如第 9.1 节中的网络),路由器通常充当 DHCP 服务器。

在发出初始 DHCP 请求时,主机甚至不知道 DHCP 服务器的地址,因此它会将请求广播给所有主机(通常是其物理网络上的所有主机)。当一台机器请求 DHCP 服务器为其分配 IP 地址时,它实际上是请求在一定时间内租用一个地址。当租期到期时,客户端可以要求续租。

9.19.1 Linux DHCP 客户端

虽然有许多不同种类的网络管理系统,但只有两种 DHCP 客户端可以完成获取租约的实际工作。传统的标准客户端是 Internet Software Consortium (ISC) dhclient 程序。不过,systemd-networkd 现在也内置了 DHCP 客户端。

启动时,dhclient 会将进程 ID 保存在 /var/run/dhclient.pid 中,将租约信息保存在 /var/lib/dhcp/dhclient.leases 中。

您可以通过命令行手动测试 dhclient,但在此之前必须删除任何默认网关路由(参见第 9.11.2 节)。要运行测试,只需指定网络接口名称(此处为 enp0s31f6):

# dhclient enp0s31f6

与 dhclient 不同,systemd-networkd DHCP 客户端不能在命令行上手动运行。systemd.network(5)手册中描述的配置位于 /etc/systemd/network,但与其他类型的网络配置一样,可以由 Netplan 自动生成。

9.19.2 Linux DHCP 服务器

你可以让 Linux 机器运行 DHCP 服务器,这样就可以很好地控制它所提供的地址。不过,除非你管理的是一个有许多子网的大型网络,否则你最好使用内置 DHCP 服务器的专用路由器硬件。

关于 DHCP 服务器,最重要的一点可能是,在同一子网中只运行一个 DHCP 服务器,以避免出现 IP 地址冲突或配置错误的问题。

9.20 自动 IPv6 网络配置

DHCP 在实践中运行良好,但它依赖于某些假设,包括 DHCP 服务器的可用性、服务器的正确实施和稳定性,以及它可以跟踪和维护租约。虽然有一个针对 IPv6 的 DHCP 版本叫做 DHCPv6,但还有一个更常见的替代方案。

IETF 利用庞大的 IPv6 地址空间,设计了一种不需要中央服务器的新网络配置方式。这就是所谓的无状态配置,因为客户端不需要存储租约分配等任何数据。

无状态 IPv6 网络配置从链路本地网络开始。记得这个网络包括前缀为 fe80::/64 的地址。由于链路本地网络上有大量可用地址,主机可以生成一个不太可能在网络上重复的地址。此外,网络前缀已经固定,因此主机可以向网络广播,询问网络上是否有其他主机正在使用该地址。

主机获得链路本地地址后,就可以确定全局地址。它可以通过监听路由器偶尔在链路本地网络上发送的路由器广告(RA)信息来确定全局地址。RA 信息包括全局网络前缀、路由器 IP 地址以及可能的 DNS 信息。有了这些信息,主机就可以尝试填写全局地址的接口 ID 部分,就像填写链路本地地址一样。

无状态配置依赖于长度不超过 64 位的全局网络前缀(换句话说,其网络掩码为 /64 或更低)。

注意
路由器也会发送 RA 消息来响应主机的路由器请求消息。这些信息以及其他一些信息是 IPv6 的 ICMP 协议(ICMPv6)的一部分。

9.21 将Linux配置为路由器

路由器就是拥有多个物理网络接口的计算机。你可以轻松地将 Linux 机器配置成路由器。

让我们来看一个例子。假设你有两个局域网子网:10.23.2.0/24 和 192.168.45.0/24。为了连接这两个子网,你需要一台带有三个网络接口的 Linux 路由器:两个用于 LAN 子网,一个用于互联网上行链路,如图 9-5 所示。

如你所见,这与本章其余部分使用的简单网络示例并无太大区别。路由器局域网子网的 IP 地址是 10.23.2.1 和 192.168.45.1。配置好这些地址后,路由表如下所示(接口名称在实践中可能会有所不同;暂时忽略互联网上行链路):

# ip route show
10.23.2.0/24 dev enp0s31f6 proto kernel scope link src 10.23.2.1 metric 100
192.168.45.0/24 dev enp0s1 proto kernel scope link src 192.168.45.1 metric 100

假设每个子网的主机都将路由器作为默认网关(10.23.2.0/24 为 10.23.2.1,192.168.45.0/24 为 192.168.45.1)。如果 10.23.2.4 要向 10.23.2.0/24 以外的任何地方发送数据包,它会将数据包传给 10.23.2.1。例如,要从 10.23.2.4(主机 A)向 192.168.45.61(主机 E)发送数据包,数据包会通过 10.23.2.1(路由器)的 enp0s31f6 接口进入 10.23.2.1,然后通过路由器的 enp0s1 接口返回。

不过,在某些基本配置中,Linux 内核不会自动将数据包从一个子网转移到另一个子网。要启用这一基本路由功能,需要使用此命令在路由器内核中启用 IP 转发功能:

# sysctl -w net.ipv4.ip_forward=1

输入这条命令后,机器就会开始在子网之间路由数据包,前提是这些子网上的主机知道要将数据包发送到你刚刚创建的路由器。

注意:您可以使用 sysctl net.ipv4.ip_forward 命令检查 IP 转发的状态。

若要在重启时将此更改永久保存,可将其添加到 /etc/sysctl.conf 文件中。根据发行版的不同,你可以选择将其放入 /etc/sysctl.d 文件中,这样发行版的更新就不会覆盖你的更改。

当路由器的第三个网络接口也带有互联网上行链路时,同样的设置可让两个子网中的所有主机都能访问互联网,因为它们被配置为将路由器作为默认网关。但问题就在这里,事情变得更加复杂了。问题在于,某些 IPv4 地址(如 10.23.2.4)实际上并不对整个互联网可见;它们位于所谓的专用网络中。为了提供互联网连接,你必须在路由器上设置一个名为网络地址转换(NAT)的功能。几乎所有专用路由器上的软件都能做到这一点,所以这里没有什么特别之处,但让我们更详细地研究一下专用网络的问题。

9.22 专用网络(IPv4)

假设你决定建立自己的网络。你已经准备好了机器、路由器和网络硬件。鉴于你目前对简单网络的了解,你的下一个问题是:"我应该使用哪个 IP 子网?

如果你想要一个互联网上每台主机都能看到的互联网地址块,你可以从 ISP 购买一个。但是,由于 IPv4 地址的范围非常有限,因此成本很高,而且除了运行一个互联网上其他主机都能看到的服务器外,并没有其他用处。大多数人并不需要这种服务,因为他们是以客户端身份访问互联网的。

传统的廉价替代方法是从 RFC 1918/6761 互联网标准文件中的地址中选择一个专用子网,如表 9-2 所示。

表 9-2:RFC 1918 和 6761 定义的专用网络

您可以随意划分专用子网。除非你计划在单个网络上拥有超过 254 台主机,否则请选择一个像 10.23.2.0/24 这样的小子网,就像我们在本章中一直使用的那样。(使用这种掩码的网络有时被称为 C 类子网。虽然这个术语在技术上已经过时,但仍然有用)。

有什么问题?真实互联网上的主机对私有子网一无所知,也不会向它们发送数据包,因此,如果没有帮助,私有子网上的主机就无法与外界通信。连接到互联网的路由器(具有真正的非私有地址)需要有某种方法来填补该连接与私有网络上的主机之间的空白。

9.23 Network Address Translation(IP 伪装)

NAT 是与专用网络共享单个 IP 地址的最常用方法,在家庭和小型办公网络中几乎普遍使用。在 Linux 中,大多数人使用的 NAT 变体被称为 IP 伪装。

NAT 背后的基本思想是,路由器不只是将数据包从一个子网移到另一个子网,而是在移动过程中对数据包进行转换。互联网上的主机知道如何连接路由器,但它们对路由器后面的专用网络一无所知。专用网络上的主机无需特殊配置,路由器就是它们的默认网关。

系统大致是这样工作的:

  • 内部专用网络上的主机希望与外部世界建立连接,因此它通过路由器发送连接请求数据包。
  • 路由器拦截连接请求数据包,而不是把它传到互联网上(在互联网上会丢失,因为公共互联网对专用网络一无所知)。
  • 路由器确定连接请求数据包的目的地,并打开自己与目的地的连接。
  • 路由器获得连接后,会伪造一条 “连接已建立 ”的信息返回给原内部主机。
  • 路由器现在是内部主机和目的地之间的中间人。目的地对内部主机一无所知;远程主机上的连接看起来像是来自路由器。

这并不像听起来那么简单。正常的 IP 路由只知道互联网层的源 IP 地址和目的 IP 地址。但是,如果路由器只处理互联网层,那么内部网络上的每台主机一次只能建立一个连接到一个目的地(还有其他限制),因为数据包的互联网层部分没有信息来区分同一主机对同一目的地的多个请求。因此,NAT 必须超越互联网层,分解数据包以获取更多识别信息,特别是传输层的 UDP 和 TCP 端口号。UDP 比较简单,因为有端口但没有连接,但 TCP 传输层很复杂。

要将 Linux 机器设置为 NAT 路由器,必须在内核配置中激活以下所有功能:网络数据包过滤(“防火墙支持”)、连接跟踪、iptables 支持、完全 NAT 和 MASQUERADE 目标支持。大多数发行版内核都支持这些功能。

接下来你需要运行一些看起来很复杂的 iptables 命令,让路由器为其私有子网执行 NAT。下面的示例适用于 enp0s2 上的内部以太网网络,它共享 enp0s31f6 上的外部连接(第 9.25 节将详细介绍 iptables 语法):

# sysctl -w net.ipv4.ip_forward=1
# iptables -P FORWARD DROP
# iptables -t nat -A POSTROUTING -o enp0s31f6 -j MASQUERADE
# iptables -A FORWARD -i enp0s31f6 -o enp0s2 -m state --state ESTABLISHED,RELATED -j ACCEPT
# iptables -A FORWARD -i enp0s2 -o enp0s31f6 -j ACCEPT

除非您在开发自己的软件,否则很可能不需要手动输入这些命令,尤其是在有这么多专用路由器硬件可用的情况下。不过,各种虚拟化软件都可以设置 NAT,用于虚拟机和容器的联网。

虽然 NAT 在实践中运行良好,但请记住,它本质上是一种延长 IPv4 地址空间寿命的黑客手段。由于第 9.7 节所述的地址空间更大、更复杂,IPv6 不需要 NAT。

9.24 路由器和Linux

在宽带发展初期,需求不高的用户只需将自己的机器直接连接到互联网。但没过多久,许多用户就希望与自己的网络共享一条宽带连接,尤其是 Linux 用户,他们经常会额外安装一台机器作为路由器运行 NAT。

针对这一新市场,制造商们推出了专门的路由器硬件,包括一个高效的处理器、一些闪存和几个网络端口--有足够的能力管理一个典型的简单网络、运行重要的软件(如 DHCP 服务器)和使用 NAT。在软件方面,许多制造商转而使用 Linux 为路由器提供动力。他们增加了必要的内核功能,精简了用户空间软件,并创建了基于图形用户界面的管理界面。

几乎就在第一批路由器出现的同时,许多人开始对深入挖掘硬件感兴趣。其中一家制造商,Linksys,被要求根据其一个组件的许可条款发布软件源代码,很快就出现了专门针对路由器的 Linux 发行版,如 OpenWRT。(这些名称中的 “WRT ”来自 Linksys 的型号)。

除了业余爱好之外,在路由器上安装这些发行版也有很好的理由。它们通常比制造商的固件更稳定,尤其是在较旧的路由器硬件上,而且它们通常还提供额外的功能。例如,要通过无线连接桥接网络,许多制造商都要求你购买匹配的硬件,但如果安装了 OpenWRT,硬件的制造商和使用年限就不重要了。这是因为你在路由器上使用的是一个真正开放的操作系统,只要支持你的硬件,它就不在乎你使用的是什么硬件。

你可以使用本书中的许多知识来检查自定义 Linux 固件的内部结构,不过你会遇到一些差异,尤其是在登录时。与许多嵌入式系统一样,开放固件倾向于使用 BusyBox 来提供许多 shell 功能。BusyBox 是一个单一的可执行程序,为许多 Unix 命令(如 shell、ls、grep、cat 等)提供有限的功能。(这可以节省大量内存。)此外,在嵌入式系统上,启动时的 init 往往非常简单。不过,你通常不会发现这些限制是个问题,因为定制的 Linux 固件通常包含一个类似于制造商提供的网络管理界面。

参考资料

9.25 防火墙

路由器应始终包含某种防火墙,以防止不良流量进入网络。防火墙是一种软件和/或硬件配置,通常安装在互联网和小型网络之间的路由器上,试图确保互联网上的 “坏 ”东西不会对小型网络造成危害。你也可以在任何主机上设置防火墙功能,在数据包层(而不是应用层,在应用层服务器程序通常会尝试执行一些自己的访问控制)筛查所有进出数据。单个机器上的防火墙有时称为 IP 过滤。

系统可以在接收数据包、发送数据包或将数据包转发(路由)到另一台主机或网关时过滤数据包。

在没有安装防火墙的情况下,系统只会处理数据包并将其发送出去。防火墙会在刚刚确定的数据传输点设置数据包检查点。检查点通常根据以下标准丢弃、拒绝或接受数据包:

源或目标 IP 地址或子网
源端口或目标端口(在传输层信息中)
防火墙的网络接口
防火墙提供了一个与处理 IP 数据包的 Linux 内核子系统合作的机会。现在让我们来了解一下。

9.25.1 Linux 防火墙基础知识

在 Linux 中,防火墙规则是以一系列称为链的方式创建的。一组链组成一个表。当数据包通过 Linux 网络子系统的各个部分时,内核会对数据包应用某些链中的规则。例如,从物理层到达的新数据包被内核归类为 “输入”,因此它会激活与输入相对应的链中的规则。

所有这些数据结构都由内核维护。整个系统称为 iptables,用户空间命令 iptables 用于创建和操作规则。

有一个更新的系统叫 nftables,它的目的是取代 iptables,但截至本文撰写时,iptables 仍是使用最广泛的系统。管理 nftables 的命令是 nft,本书中显示的 iptables 命令有一个 iptables 到 nftables 的翻译器,名为 iptables-translate。为了让事情变得更复杂,最近推出了一个名为 bpfilter 的系统,它采用了不同的方法。尽量不要纠结于命令的细节,重要的是效果。

因为可能有很多表,每个表都有自己的链组,而链组中又可能包含很多规则,所以数据包流可能会变得相当复杂。不过,你通常主要使用一个名为 filter 的表来控制基本的数据包流。过滤表中有三个基本链: INPUT 用于输入数据包,OUTPUT 用于输出数据包,FORWARD 用于路由数据包。

图 9-6 和 9-7 显示了简化流程图,说明了规则在过滤器表中应用于数据包的位置。之所以有两个图,是因为数据包既可以从网络接口进入系统(图 9-6),也可以由本地进程生成(图 9-7)。

图 9-6: 对来自网络的数据包进行链式处理的顺序

图 9-7:本地进程传入数据包的链式处理顺序

正如您所看到的,从网络传入的数据包可能会被用户进程消耗,并且可能不会到达 FORWARD 链或 OUTPUT 链。用户进程生成的数据包不会到达 INPUT 链或 FORWARD 链。

除了这三个链之外,沿途还有许多其他步骤,因此情况会变得更加复杂。例如,数据包需要经过 PREROUTING 和 POSTROUTING 链,链处理也可能发生在三个较低网络层中的任何一个。如需了解所有情况的大图表,请在互联网上搜索 “Linux netfilter 数据包流”,但请记住,这些图表试图包含数据包输入和流动的所有可能情况。通常,按数据包来源对图表进行细分会有所帮助,如图 9-6 和图 9-7。

9.25.2 设置防火墙规则

让我们看看 iptables 系统是如何实际工作的。首先使用此命令查看当前配置:

# iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

每个防火墙链都有一个默认策略,规定在没有规则匹配数据包的情况下如何处理该数据包。本例中所有三个链的策略都是 ACCEPT,这意味着内核允许数据包通过数据包过滤系统。DROP 策略则告诉内核丢弃数据包。要在链上设置策略,请像这样使用 iptables -P:

# iptables -P FORWARD DROP

警告:在通读本节其余部分之前,不要对机器上的策略轻举妄动。

假设有人在 192.168.34.63 上骚扰你。为了防止他们与你的机器通话,请运行以下命令:

# iptables -A INPUT -s 192.168.34.63 -j DROP

-A INPUT 参数将一条规则添加到 INPUT 链中。-s 192.168.34.63 部分指定了规则中的源 IP 地址,而 -j DROP 则告诉内核丢弃任何符合规则的数据包。因此,机器将丢弃任何来自 192.168.34.63 的数据包。

要查看已执行的规则,请再次运行 iptables -L:

Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       all  --  192.168.34.63        anywhere

不幸的是,你在 192.168.34.63 的朋友已经告诉他子网中的所有人打开与你的 SMTP 端口(TCP 25 端口)的连接。要消除这些流量,请运行

# iptables -A INPUT -s 192.168.34.0/24 -p tcp -destination-port 25 -j DROP

该示例在源地址中添加了 netmask 限定符,并添加了 -p tcp,以指定只接收 TCP 数据包。另一个限制条件 --destination-port 25 表示该规则只适用于端口 25 的流量。现在,INPUT 的 IP 表列表如下所示:

Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       all  --  192.168.34.63        anywhere
DROP       tcp  --  192.168.34.0/24      anywhere           tcp dpt:smtp

一切都很顺利,直到你从 192.168.34.37 的熟人那里得知,由于你屏蔽了她的机器,她无法向你发送电子邮件。你认为这是一个快速解决方案,于是运行了这条命令:

# iptables -A INPUT -s 192.168.34.37 -j ACCEPT

但是,它不起作用。要了解原因,请看新的链:

Chain INPUT (policy ACCEPT)
target     prot opt source               destination
DROP       all  --  192.168.34.63        anywhere
DROP       tcp  --  192.168.34.0/24      anywhere           tcp dpt:smtp
ACCEPT     all  --  192.168.34.37        anywhere

内核从上到下读取链,使用第一条匹配的规则。

第一条规则不匹配 192.168.34.37,但第二条规则匹配,因为它适用于从 192.168.34.1 到 192.168.34.254 的所有主机,而第二条规则要求丢弃数据包。当一条规则匹配时,内核就会执行该操作,而不会再向下查找。(您可能会注意到,192.168.34.37 可以向机器上除 25 端口外的任何端口发送数据包,因为第二条规则只适用于 25 端口)。

解决方法是将第三条规则移到顶层。首先,用这条命令删除第三条规则:

# iptables -D INPUT 3

然后用 iptables -I 将该规则插入链的顶端:

# iptables -I INPUT -s 192.168.34.37 -j ACCEPT

要在链的其他位置插入规则,请在链名后面加上规则编号(例如,iptables -I INPUT 4 ... )。

9.25.3 防火墙策略

虽然前面的教程向我们展示了如何插入规则以及内核如何处理 IP 链,但我们还没有看到真正有效的防火墙策略。现在让我们来谈谈这个问题。

有两种基本的防火墙方案:一种用于保护单台机器(在每台机器的 INPUT 链中设置规则),另一种用于保护机器网络(在路由器的 FORWARD 链中设置规则)。在这两种情况下,如果使用默认的 “接受 ”策略,并不断插入规则,从开始发送不良信息的源头丢弃数据包,就不可能有很高的安全性。你必须只允许你信任的数据包,拒绝其他一切。

例如,你的机器在 TCP 22 端口上有一个 SSH 服务器。任何随机主机都没有理由向你机器上的任何其他端口发起连接,你也不应该给这样的主机任何机会。为此,首先要将 INPUT 链策略设置为 DROP:

# iptables -P INPUT DROP

要启用 ICMP 流量(用于 ping 和其他实用程序),请使用这一行:

# iptables -A INPUT -p icmp -j ACCEPT

确保您能接收到发送到自己网络 IP 地址和 127.0.0.1(localhost)的数据包。假设主机的 IP 地址是 my_addr,请执行以下操作:

# iptables -A INPUT -s 127.0.0.1 -j ACCEPT
# iptables -A INPUT -s my_addr -j ACCEPT

警告:不要在只有远程访问权限的机器上逐个运行这些命令。第一条 DROP 命令会立即阻止您的访问,除非您进行干预(例如重启机器),否则无法重新获得访问权。

如果你控制着整个子网(并信任子网中的一切),可以用子网地址和子网掩码替换 my_addr,例如 10.23.2.0/24。

现在,尽管您仍想拒绝传入的 TCP 连接,但仍需确保您的主机能与外界建立 TCP 连接。因为所有 TCP 连接都以 SYN(连接请求)数据包开始,所以如果让所有非 SYN 数据包的 TCP 数据包通过,就不会有问题:

# iptables -A INPUT -p tcp '!' --syn -j ACCEPT

符号!表示否定,因此!--syn匹配任何非SYN数据包。

接下来,如果使用的是基于 UDP 的远程 DNS,则必须接受来自域名服务器的流量,这样机器才能使用 DNS 查找域名。对 /etc/resolv.conf 中的所有 DNS 服务器都要这样做。使用此命令(其中名称服务器地址为 ns_addr):

# iptables -A INPUT -p udp --source-port 53 -s ns_addr -j ACCEPT

最后,允许来自任何地方的 SSH 连接:

# iptables -A INPUT -p tcp -destination-port 22 -j ACCEPT

前面的 iptables 设置适用于多种情况,包括任何直接连接(尤其是宽带),在这种情况下,入侵者更有可能对你的机器进行端口扫描。你也可以通过使用 FORWARD 链(而不是 INPUT),并在适当的地方使用源子网和目标子网,将这些设置用于防火墙路由器。对于更高级的配置,您可能会发现 Shorewall 等配置工具很有帮助。

以上讨论仅涉及安全策略。请记住,关键在于只允许你认为可以接受的东西,而不是试图找出并排除不好的东西。此外,IP 防火墙只是安全问题的一个方面。(下一章将介绍更多内容)。

9.26 以太网、IP、ARP 和 NDP

在通过以太网实现 IP 的过程中,有一个基本细节我们尚未涉及。回想一下,主机必须将 IP 数据包放在以太网帧中,才能将数据包通过物理层传输到另一台主机。另外,请注意,帧本身并不包含 IP 地址信息;帧使用的是 MAC(硬件)地址。问题是这样的: 在为 IP 数据包构建以太网帧时,主机如何知道目标 IP 地址对应的 MAC 地址?

我们通常不会过多地考虑这个问题,因为网络软件包含一个自动查找 MAC 地址的系统。在 IPv4 中,这被称为地址解析协议(ARP)。使用以太网作为物理层、IP 作为网络层的主机会维护一个称为 ARP 缓存的小表,该表将 IP 地址映射到 MAC 地址。在 Linux 中,ARP 缓存位于内核中。要查看机器的 ARP 缓存,请使用 ip neigh 命令。(当你看到与 IPv6 相当的命令时,“neigh ”部分就有意义了。查看 ARP 缓存的旧命令是 arp)。

$ ip -4 neigh
10.1.2.57 dev enp0s31f6 lladdr 1c:f2:9a:1e:88:fb REACHABLE
10.1.2.141 dev enp0s31f6 lladdr 00:11:32:0d:ca:82 STALE
10.1.2.1 dev enp0s31f6 lladdr 24:05:88:00:ca:a5 REACHABLE

我们使用 -4 选项将输出限制为 IPv4。你可以看到内核知道的主机的 IP 地址和硬件地址。最后一个字段显示的是缓存中的条目状态。REACHABLE(可到达)表示最近与主机进行了一些通信,STALE(陈旧)表示已经过了一段时间,应该刷新条目。

机器启动时,ARP 缓存是空的。那么这些 MAC 地址是如何进入缓存的呢?这要从机器要向另一台主机发送数据包时说起。如果目标 IP 地址不在 ARP 缓存中,就会发生以下步骤:

  • 始发主机创建一个特殊的以太网帧,其中包含与目标 IP 地址对应的 MAC 地址的 ARP 请求数据包。
  • 起源主机向目标子网的整个物理网络广播该帧。
  • 如果子网中的其他主机知道正确的 MAC 地址,就会创建一个包含该地址的回复数据包和帧,并将其发回源主机。通常情况下,回复的主机就是目标主机,它只是用自己的 MAC 地址进行回复。
  • 源主机将 IP-MAC 地址对添加到 ARP 缓存中,然后继续发送。

注意:请记住,ARP 仅适用于本地子网中的机器。要到达子网外的目的地,您的主机会将数据包发送到路由器,之后就是别人的问题了。当然,主机仍然需要知道路由器的 MAC 地址,它可以使用 ARP 来找到它。

使用 ARP 可能遇到的唯一真正问题是,当你把一个 IP 地址从一个网络接口卡移到另一个网络接口卡时,系统的缓存可能会过时,因为这些卡的 MAC 地址不同(例如,在测试一台机器时)。如果一段时间后没有任何活动,Unix 系统会使 ARP 缓存条目失效,因此除了失效数据会有少许延迟外,应该不会有其他问题,但您可以使用此命令立即删除 ARP 缓存条目:

# ip neigh del host dev interface

ip-neighbour(8) 手册页解释了如何手动设置 ARP 缓存条目,但应该不需要这样做。注意拼写。

注意不要混淆 ARP 与反向地址解析协议 (RARP)。RARP 将 MAC 地址转换回主机名或 IP 地址。在 DHCP 流行之前,一些无盘工作站和其他设备使用 RARP 获取配置,但现在 RARP 已经很少见了。

IPV6:NDP:你可能想知道为什么操作 ARP 缓存的命令中没有 “arp”(或者,如果你以前隐约见过这种东西,你可能会想为什么我们不使用 arp)。在 IPv6 中,在链路本地网络上使用了一种名为 “邻居发现协议”(NDP)的新机制。ip 命令统一了 IPv4 的 ARP 和 IPv6 的 NDP。NDP 包括以下两种信息:

  • Neighbor solicitation 用于获取链路本地主机的信息,包括主机的硬件地址。
  • Neighbor advertisemen 用于回应邻居请求报文。
    NDP 还有其他几个组成部分,包括第 9.20 节中介绍的 RA 消息。

9.27 无线以太网

原则上,无线以太网(“Wi-Fi”)网络与有线网络并无太大区别。与任何有线硬件一样,它们都有 MAC 地址,并使用以太网帧来传输和接收数据,因此 Linux 内核可以像与有线网络接口一样与无线网络接口对话。网络层及以上的所有内容都是一样的;主要区别在于物理层的附加组件,如频率、网络 ID 和安全功能。

有线网络硬件能根据物理设置中的细微差别自动调整,无需大费周章,而无线网络配置则不同,它更具有开放性。要让无线接口正常工作,Linux 需要额外的配置工具。

让我们快速了解一下无线网络的附加组件。

  • 传输细节 这些是物理特性,如无线电频率。
  • 网络标识 因为多个无线网络可以共享相同的基本媒介,所以你必须能够区分它们。服务集标识符(SSID,也称为 “网络名称”)就是无线网络标识符。
  • 管理 虽然可以配置无线网络,让主机之间直接对话,但大多数无线网络都是由一个或多个接入点管理的,所有流量都要通过接入点。接入点通常将无线网络与有线网络桥接起来,使两者看起来像一个网络。
  • 身份验证 你可能想限制对无线网络的访问。为此,你可以对接入点进行配置,使其在与客户端通话前要求输入密码或其他验证密钥。
  • 加密 除了限制对无线网络的初始访问外,你通常还希望对所有通过无线电波传输的信息进行加密。

处理这些组件的 Linux 配置和实用程序分布在多个区域。有些在内核中;Linux 有一套无线扩展功能,可将用户空间对硬件的访问标准化。就用户空间而言,无线配置可能会变得复杂,因此大多数人更喜欢使用图形用户界面前端(如 NetworkManager 的桌面小程序)来处理问题。不过,我们还是应该了解一下幕后的一些情况。

9.27.1 iw

你可以使用名为 iw 的实用程序查看和更改内核空间的设备和网络配置。要使用 iw,通常需要知道设备的网络接口名称,如 wlp1s0(可预测的设备名称)或 wlan0(传统名称)。下面是一个扫描可用无线网络的示例。(如果你在市区,可能会有很多输出)。

# iw dev wlp1s0 scan

注意:网络接口必须正常运行才能执行该命令(如果没有正常运行,请运行 ifconfig wlp1s0 up),但由于该命令仍处于物理层,因此不需要配置任何网络层参数,如 IP 地址。

如果网络接口已加入无线网络,则可以像下面这样查看网络详细信息:

# iw dev wlp1s0 link

该命令输出中的 MAC 地址来自当前正在通话的接入点。

注意: iw 命令区分物理设备名称(如 phy0)和网络接口名称(如 wlp1s0),并允许你更改每种名称的各种设置。您甚至可以为一个物理设备创建多个网络接口。不过,在几乎所有基本情况下,你都只需使用网络接口名称。

使用 iw 将网络接口连接到不安全的无线网络,如下所示:

# iw wlp1s0 connect network_name

连接安全网络则另当别论。对于相当不安全的有线等效保密(WEP)系统,可以在 iw connect 命令中使用密钥参数。不过,您不应该使用 WEP,因为它并不安全,而且支持 WEP 的网络也不多。

9.27.2 无线安全

对于大多数无线安全设置,Linux 都依赖 wpa_supplicant 守护进程来管理无线网络接口的身份验证和加密。该守护进程可以处理 WPA2 和 WPA3(WiFi 保护访问;请勿使用较早的、不安全的 WPA)身份验证方案,以及无线网络上使用的几乎所有加密技术。当守护进程首次启动时,它会读取一个配置文件(默认为 /etc/wpa_supplicant.conf),并尝试向接入点表明自己的身份,然后根据给定的网络名称建立通信。该系统文档详尽,尤其是 wpa_supplicant(8) 手册页面非常详细。

每次要建立连接时,手动运行守护进程是一项繁重的工作。事实上,由于可能的选项太多,光是创建配置文件就很乏味。更糟糕的是,运行 iw 和 wpa_supplicant 的所有工作都只是让系统加入无线物理网络,甚至没有设置网络层。而这正是 NetworkManager 等自动网络配置管理器的优势所在。虽然它们自己不做任何工作,但它们知道正确的顺序和无线网络运行每一步所需的配置。

9.28 小结

正如您所看到的,了解各网络层的位置和作用对于理解 Linux 网络如何运行以及如何进行网络配置至关重要。虽然我们只涉及了基础知识,但物理层、网络层和传输层中更高级的主题与你在这里看到的类似。层本身通常是细分的,就像你刚才看到的无线网络物理层的各个部分。

你在本章中看到的大量操作都发生在内核中,一些基本的用户空间控制实用程序用于操作内核的内部数据结构(如路由表)。这是处理网络的传统方式。然而,与本书讨论的许多主题一样,有些任务由于其复杂性和对灵活性的需求,并不适合由内核来完成,这时就需要用户空间实用程序来接管。其中,NetworkManager 监控和查询内核,然后操作内核配置。另一个例子是支持动态路由协议,如大型互联网路由器使用的边界网关协议(BGP)。

不过,你现在可能已经对网络配置有点厌烦了。下面我们来谈谈网络的使用--应用层。

热门相关:都市之九天大帝   隐婚99天:首长,请矜持   99次出墙:老公,情难自禁   重生嫡女谋天下   绝天武帝