第9章:理解你的网络及其配置

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

  • 发送数据的计算机如何知道将数据发送到哪里?
  • 当目标计算机接收到数据时,它如何知道自己刚刚收到的是什么?

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

由于每一层往往是独立的,因此可以用许多不同的组件组合来构建网络。这就是网络配置可能变得非常复杂的地方。因此,我们将从查看非常简单网络中的层开始本章。你将学习如何查看自己的网络设置,当你理解每一层的基本工作原理后,就可以准备学习如何自行配置这些层。最后,你将进入更高级的主题,比如构建自己的网络和配置防火墙。(如果这些内容让你感到眼花缭乱,可以先跳过;你随时可以回来再看。)

9.1 网络基础

在深入探讨网络层理论之前,先看一下图9-1中所示的简单网络。

+---------+     +---------+
| 主机 A  |     | 主机 B  |
+---------+     +---------+
      |               |
      |               |
      +---------------+
              |
         +---------+
         |  路由器  |----> 互联网 (上行链路)
         +---------+
              |
         +---------+
         | 主机 C  |
         +---------+

图9-1:一个典型的局域网,配有提供互联网访问的路由器

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

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

你的初始视角将来自一个基于Linux的机器,例如图9-1中LAN上的主机A。

9.2 数据包

计算机以称为数据包的小块数据通过网络传输数据,每个数据包由两部分组成:头部负载。头部包含标识信息,例如源主机和目标主机以及基本协议。而负载是计算机想要发送的实际应用程序数据(例如HTML或图像数据)。

一台主机可以以任何顺序发送、接收和处理数据包,无论它们来自何处或去往何处,这使得多台主机可以“同时”通信。例如,如果一台主机需要同时向另外两台主机传输数据,它可以在传出数据包中交替发送到不同的目的地。将消息分解成更小的单元也使得检测和补偿传输错误更加容易。

大多数情况下,你无需担心在数据包和应用程序使用的数据之间进行转换,因为操作系统会为你完成这项工作。但是,了解数据包在你即将看到的网络层中的作用是有帮助的。

9.3 网络层

一个功能完整的网络包括一组称为网络栈的网络层。任何功能正常的网络都有一个栈。典型的互联网栈,从上到下依次为:

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

  • 传输层:定义应用层的数据传输特性。该层包括数据完整性检查、源端口和目标端口,以及在主机端将应用程序数据分解为数据包(如果应用层尚未这样做)并在目标端重新组合的规范。传输控制协议(TCP)和用户数据报协议(UDP)是最常见的传输层协议。传输层有时也称为协议层。在Linux中,传输层及其以下所有层主要由内核处理,但也有一些例外,数据包会被发送到用户空间进行处理。

  • 网络层或互联网层:定义如何将数据包从源主机移动到目标主机。用于互联网的特定数据包传输规则集称为互联网协议(IP)。由于本书中我们只讨论互联网网络,实际上我们只谈论互联网层。然而,由于网络层设计为硬件无关,你可以在同一台主机上同时配置多个独立的网络层——例如IP(IPv4)、IPv6、IPX和AppleTalk。

  • 物理层:定义如何通过物理介质(如以太网或调制解调器)发送原始数据。这有时称为链路层主机到网络层

理解网络栈的结构很重要,因为你的数据在到达目标主机上的程序之前,必须至少两次穿过这些层。例如,如果你要从主机A向主机B发送数据(如图9-1所示),你的字节离开主机A的应用层,穿过主机A的传输层和网络层;然后下降到物理介质,穿过介质,再以大致相同的方式通过各个较低级别上升至主机B的应用层。如果你要通过路由器向互联网上的主机发送内容,它将穿过路由器以及中间其他设备上的部分(但通常不是全部)层。

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

我们将首先查看Linux机器如何连接到网络,以回答本章开头提出的“哪里”的问题。这是栈的较低部分——物理层和网络层。稍后,我们将查看回答“什么”问题的上面两层。

NOTE

你可能听说过另一种称为开放系统互连(OSI)参考模型的层。这是一种七层网络模型,常用于教学和网络设计,但我们不会涉及OSI模型,因为你将直接使用此处描述的四个层。要了解有关层(以及一般网络)的更多信息,请参阅Andrew S. Tanenbaum和David J. Wetherall的《计算机网络》第5版(Prentice Hall, 2010)。

9.4 互联网层

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

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

互联网的拓扑结构是去中心化的;它由称为子网的更小网络组成。其思想是所有子网都以某种方式相互连接。例如,在图9-1中,LAN通常是一个单一子网。

一台主机可以连接到多个子网。正如你在9.1节中看到的,如果这种主机能够将数据从一个子网传输到另一个子网,则称为路由器(路由器的另一个术语是网关)。图9-2细化了图9-1,将LAN标识为一个子网,并为每台主机和路由器标注了互联网地址。图中的路由器有两个地址:本地子网10.23.2.1和到互联网的链接(互联网链接的地址现在不重要,因此仅标记为“上行链路地址”)。我们将首先查看地址,然后查看子网表示法。

                   互联网
                      |
          +-----------+-----------+
          |    上行链路地址         |
          |   (路由器)             |
          +-----------+-----------+
                      |
         +-----------+-----------+
         |    子网 10.23.2.0/24   |
         |        (LAN)           |
         |                       |
     +---+---+              +---+
     | 主机 A |              | 主机 B |
     |10.23.2.4|            |10.23.2.37|
     +---+---+              +---+
         |                     |
         +---------+-----------+
                   |
              +----+----+
              | 主机 C  |
              |10.23.2.132|
              +---------+

图9-2:带有IP地址的网络

每个互联网主机至少有一个数字IP地址。对于IPv4,形式为a.b.c.d,例如10.23.2.37。这种表示法中的地址称为点分四元组。如果一台主机连接到多个子网,则每个子网至少有一个IP地址。每台主机的IP地址在整个互联网中应该是唯一的,但正如你稍后将看到的,专用网络和网络地址转换(NAT)可能会使这一点有些令人困惑。

暂时不要担心图9-2中的子网表示法;我们稍后将讨论它。

NOTE

从技术上讲,一个IP地址由4个字节(即32位)组成,即a.b.c.d。字节ad是1到254之间的数字,bc是0到255之间的数字。计算机将IP地址作为原始字节处理。然而,对于人类来说,阅读和书写像10.23.2.37这样的点分四元组地址要比像丑陋的十六进制0x0A170225容易得多。

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

第9章:理解网络配置

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
    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地址所属的子网。下面来看看它的工作原理。

NOTE

ip 命令是当前标准的网络配置工具。在其他文档中,你可能会看到 ifconfig 命令。这个较老的命令在Unix其他版本中已使用数十年,但功能较弱。为了与当代推荐实践保持一致(以及某些发行版可能默认不包含 ifconfig),我们将使用 ipip 所取代的其他工具包括 routearp

9.4.2 子网

子网(先前定义过)是一组连接的具有特定范围IP地址的主机。例如,10.23.2.110.23.2.254 范围内的主机可以构成一个子网,同样,10.23.1.110.23.255.254 之间的所有主机也可构成子网。通常,子网主机位于同一物理网络上,如图9-2所示。

定义子网需要两个要素:网络前缀(也称路由前缀)和子网掩码(有时称为网络掩码或路由掩码)。假设要创建一个包含 10.23.2.110.23.2.254 之间IP地址的子网。网络前缀是子网中所有地址共同的部分;在本例中,它为 10.23.2.0,子网掩码为 255.255.255.0。下面看看这些数字从何而来。

要了解前缀和掩码如何共同给出子网上所有可能的IP地址,我们来看二进制形式。掩码标记IP地址中属于子网公共部分的比特位。例如,下面是 10.23.2.0255.255.255.0 的二进制形式:

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

现在,用粗体标记 10.23.2.0 中与 255.255.255.0 的1位相对应的比特位置:

10.23.2.0:        00001010 00010111 00000010 00000000

任何包含粗体位配置的地址都在该子网中。观察非粗体位(最后一组8个0),将这些位中的任意数量设置为1,都会得到该子网中的一个有效IP地址,但全0或全1除外。

综上所述,可以看出一个IP地址为 10.23.2.1、子网掩码为 255.255.255.0 的主机,与任何以 10.23.2 开头的IP地址的计算机都处于同一子网中。你可以将整个子网表示为 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节中将看到的其中一个私有网络结合使用。

表9-1:子网掩码

长形式CIDR形式
255.0.0.0/8
255.255.0.0/16
255.240.0.0/12
255.255.255.0/24
255.255.255.192/26

NOTE

如果你不熟悉十进制、二进制和十六进制格式之间的转换,可以使用 bcdc 等计算器工具在不同的基数表示之间进行转换。例如,在 bc 中,运行命令 obase=2; 240 可以打印数字240的二进制(基数为2)形式。

更进一步,你可能已经注意到,如果你有IP地址和子网掩码,就根本不需要单独的网络定义。你可以将它们组合在一起,就像在9.4.1节中看到的那样;ip address 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

NOTE

查看路由的传统工具是 route 命令,运行方式为 route -n-n 选项告诉 route 显示IP地址,而不是尝试按名称显示主机和网络。这是一个重要的选项,因为你可以将其用于其他与网络相关的命令,如 netstat

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

遇到的第一个字段是 10.23.2.0/24,这是一个目标网络。与之前的例子一样,这是主机的本地子网。这条规则表明主机可以通过其网络接口直接到达本地子网,由目标后的 dev enp0s31f6 机制标签指示。(该字段之后是关于路由的更多细节,包括它是如何被放置的。现在不必担心这一点。)

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

9.6 默认网关

路由表中的 default 条目具有特殊意义,因为它匹配互联网上的任何地址。在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

表示形式是十六进制,每个数字范围从0到f。有几种常用的缩写方法。首先,可以省略前导零(例如,0db8变为db8),并且一组——且仅一组——连续的零组可以变为::(两个冒号)。因此,你可以将上述地址写为:

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

子网仍然使用CIDR表示法表示。对于最终用户而言,它们通常覆盖地址空间可用位的一半(/64),但也存在使用更少位的情况。每个主机独有的地址空间部分称为接口ID。图9-3展示了一个具有64位子网的示例地址的分解。

子网(64位)             接口ID(64位)
2001:db80:8500:e000    52b6:59cc:74e9:8b6e

图9-3:典型IPv6地址的子网与接口ID

NOTE

在本书中,我们通常关注普通用户的视角。对于服务提供商来说情况略有不同,子网会进一步划分为路由前缀和另一个网络ID(有时也称为子网)。现在不必担心这一点。

目前关于IPv6需要了解的最后一点是:主机通常至少有两个地址。第一个地址在整个互联网上有效,称为全局单播地址。第二个地址用于本地网络,称为链路本地地址。链路本地地址总是以fe80::/10为前缀,后跟一个全0的54位网络ID,并以一个64位接口ID结尾。结果是,当你在系统上看到链路本地地址时,它将在fe80::/64子网内。

NOTE

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

第9章:理解网络配置

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行是链路本地子网,情况类似。对于默认路由(在IPv6中也写作 ::/0;记住它代表所有非直连目标)第3行,此配置将流量引导至位于链路本地地址 fe80::800d:7bff:feb8:14a0 的路由器,而不是其在全局子网上的地址。稍后你会看到,路由器通常不关心流量如何到达它,只关心流量要去哪里。使用链路本地地址作为默认网关的优点是,如果全局IP地址空间发生变化,它无需更改。

9.7.2 配置双栈网络

你可能已经猜到了,可以配置主机和网络同时运行IPv4和IPv6。这有时称为双栈网络,尽管“栈”这个词的使用值得商榷,因为在这种情形下,典型的网络栈中实际上只有一层被复制(真正的双栈应该像IP+IPX这样)。抛开吹毛求疵不谈,IPv4和IPv6协议彼此独立,可以同时运行。在这样的主机上,由应用程序(如Web浏览器)自行选择使用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回显请求包,要求接收方主机将数据包返回给发送方。如果接收方主机收到该数据包并配置为回复,它就会发回一个ICMP回显应答包。

例如,假设你运行 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秒(1000毫秒),说明连接极慢。

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

在有线局域网上,你应该预期完全没有丢包,并且往返时间非常短(上面的示例输出来自无线网络)。你同样应该期望从你的网络到你的ISP之间没有丢包,并且往返时间相当稳定。

NOTE

出于安全原因,互联网上有些主机禁用了对ICMP回显请求数据包的响应,所以你可能会发现你能连接到一台主机上的网站,但得不到 ping 响应。

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

9.8.2 DNS和host

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地址。此外,该主机的管理员需要手动设置反向查找,而管理员往往不会这样做。

关于DNS,host 命令之外还有更多内容。我们将在9.15节介绍基本的客户端配置。

host 也有 -4-6 选项,但它们的工作方式与你可能期望的不同。它们强制 host 命令通过IPv4或IPv6获取信息,但由于该信息应不论网络协议如何都相同,因此输出中可能会同时包含IPv4和IPv6地址。

9.9 物理层和以太网

关于互联网,需要理解的关键点之一是它是一个软件网络。到目前为止我们讨论的所有内容都与硬件无关,事实上,互联网成功的原因之一就是它几乎可以在任何类型的计算机、操作系统和物理网络上工作。然而,如果你真的想与另一台计算机通信,你仍然必须在某种硬件之上实现一个网络层。这个接口就是物理层

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

  • 以太网络上的所有设备都有一个媒体访问控制(MAC)地址,有时也称为硬件地址。该地址独立于主机的IP地址,并且对主机的以太网络来说是唯一的(但对于更大的软件网络(如互联网)则不一定是唯一的)。一个典型的MAC地址是 10:78:d2:eb:76:97
  • 以太网络上的设备以的形式发送消息,帧是围绕所发送数据的外壳。一个帧包含源和目的MAC地址。

以太网实际上并不试图超越单网络上的硬件。例如,如果你有两个不同的以太网络,且有一台主机同时连接到这两个网络(并有两个不同的网络接口设备),那么除非你设置了一个以太网桥,否则你无法直接将一个帧从一个以太网络传输到另一个以太网络。这正是更高网络层(如互联网层)介入的地方。按照惯例,每个以太网通常也是一个互联网子网。尽管一个帧不能离开一个物理网络,但路由器可以将数据从帧中取出、重新封装,并发送到不同物理网络上的主机——这就是互联网上实际发生的事情。

第9章:理解网络配置

9.10 理解内核网络接口

物理层和互联网层必须连接起来,使得互联网层能够保持其与硬件无关的灵活性。Linux内核自身维护着这两层之间的划分,并提供了一个连接它们的通信标准,称为**(内核)网络接口**。当你配置一个网络接口时,你便将互联网侧的IP地址设置与物理设备侧的硬件标识关联起来。

网络接口通常具有表示底层硬件类型的名称,例如 enp0s31f6(一个PCI插槽中的接口)。这样的名称被称为可预测的网络接口设备名,因为它在重启后保持不变。在启动时,接口会使用传统名称,如 eth0(计算机中的第一个以太网卡)和 wlan0(无线接口),但在大多数运行 systemd 的机器上,它们很快会被重命名。

在第9.4.1节中,你学习了如何使用 ip address 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
理解你的网络及其配置   237
    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地址:link/ether 2。

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

9.11 网络接口配置简介

现在你已经了解了网络栈底层所有的基本元素:物理层、网络(互联网)层以及Linux内核的网络接口。为了将这些部分组合起来,将一台Linux机器连接到互联网,你或某个软件必须执行以下步骤:

  1. 连接网络硬件,并确保内核拥有其驱动程序。如果驱动程序存在,ip address show 会包含该设备的条目,即使它尚未配置。
  2. 执行任何额外的物理层设置,例如选择网络名称或密码。
  3. 为内核网络接口分配IP地址和子网,以便内核的设备驱动程序(物理层)和互联网子系统(互联网层)能够相互通信。
  4. 添加任何额外的必要路由,包括默认网关。

当所有机器都是大型固定式有线连接时,这一过程相对简单:内核完成步骤1,你不需要步骤2,你可以用旧的 ifconfig 命令完成步骤3,用旧的 route 命令完成步骤4。我们将简要介绍如何使用 ip 命令完成这些操作。

9.11.1 手动配置接口

现在我们来看看如何手动设置接口,但不会深入太多细节,因为这样做很少需要且容易出错。这通常只在实验系统时才会用到。即使在配置时,你可能也希望使用诸如 Netplan 之类的工具,通过文本文件构建配置,而不是像下面这样使用一系列命令。

# ip address add address/subnet dev interface

在这里,interface 是接口的名称,例如 enp0s31f6eth0。这也适用于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

WARNING

在随意配置路由之前,你应该知道配置路由通常比看起来复杂得多。就这个例子而言,你还必须确保 192.168.45.0/24 上所有主机的路由能够回指到 10.23.2.0/24,否则你添加的第一条路由基本上是无用的。

通常,你应该尽可能保持简单,设置本地网络时仅让主机需要一条默认路由即可。如果你需要多个子网并在它们之间路由,通常最好将充当默认网关的路由器配置为完成所有本地子网之间的路由工作。(你将在第9.21节看到一个例子。)

9.12 启动激活的网络配置

我们已经讨论了手动配置网络的方法,而确保机器网络配置正确性的传统方式是在启动时让 init 运行一个脚本来执行手动配置。这本质上就是在启动事件链中的某个地方运行像 ip 这样的工具。

在Linux中,曾有许多尝试来标准化启动时网络的配置文件。ifupifdown 工具就是其中之一;例如,一个启动脚本可以(理论上)运行 ifup eth0 来执行正确的 ip 命令以设置接口。不幸的是,不同发行版对 ifupifdown 的实现完全不同,因此它们的配置文件也不同。

由于网络配置元素存在于不同的网络层中,导致了一个更深的差异:负责实现网络功能的软件分布在多个内核和用户空间工具中,由不同的开发人员编写和维护。在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 维护两个基本级别的配置。第一个是可用硬件设备的信息集合,它通常从内核收集,并通过监控 udev 的桌面总线 (D-Bus) 来维护。第二个配置级别是一个更具体的连接列表:硬件设备以及额外的物理层和网络层配置参数。例如,无线网络可以表示为一个连接。

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

启动时,NetworkManager 收集所有可用的网络设备信息,搜索其连接列表,然后决定尝试激活一个。对于以太网接口,它的决策过程如下:

  1. 如果有线连接可用,尝试使用它进行连接。否则,尝试无线连接。
  2. 扫描可用的无线网络列表。如果有一个你之前连接过的网络可用,NetworkManager 将再次尝试。
  3. 如果多个之前连接过的无线网络可用,选择最近连接的那个。

建立连接后,NetworkManager 会维持该连接,直到连接断开、出现更好的网络(例如,在通过无线连接时插入网线),或者用户强制更改。

9.14.2 NetworkManager 交互

大多数用户通过桌面上的小程序与 NetworkManager 交互;它通常是一个位于右上角或右下角的图标,指示连接状态(有线、无线或未连接)。点击该图标时,你会获得多个连接选项,例如选择无线网络以及从当前网络断开连接的选项。每个桌面环境都有自己的小程序版本,因此外观略有不同。

除了小程序之外,还有一些工具可用于从 shell 查询和控制 NetworkManager。要快速了解当前连接状态,请使用不带参数的 nmcli 命令。

你会获得一个接口和配置参数的列表。在某些方面,这类似于 ip,但提供了更多细节,尤其是在查看无线连接时。

nmcli 命令允许你从命令行控制 NetworkManager。这是一个相当广泛的命令;实际上,除了通常的 nmcli(1) 手册页之外,还有一个 nmcli-examples(5) 手册页。

最后,实用程序 nm-online 会告诉你网络是否已启动或关闭。如果网络已启动,该命令返回 0 作为退出代码;否则返回非零值。(关于如何在 shell 脚本中使用退出代码的更多信息,请参见第 11 章。)

9.14.3 NetworkManager 配置

NetworkManager 的通用配置目录通常是 /etc/NetworkManager,并且有几种不同类型的配置。通用配置文件是 NetworkManager.conf。其格式类似于 XDG 风格的 .desktop 和 Microsoft .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 管理大多数网络接口,但有时你会希望它忽略某些接口。例如,大多数用户不需要在本地回环(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

如果该行不存在或值设置为 no,则 NetworkManager 会忽略该接口。对于 localhost,你会在 ifcfg-lo 文件中发现它已被停用。你也可以指定一个硬件地址来忽略,如下所示:

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

如果你不使用这两种网络配置方案,你仍然可以使用 keyfile 插件,直接通过 MAC 地址在 NetworkManager.conf 文件中指定未管理的设备。以下是一个示例,显示两个未管理的设备:

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

调度

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

当系统网络接口状态发生变化时,NetworkManager 会运行 /etc/NetworkManager/dispatcher.d 中的所有内容,并传递诸如 updown 之类的参数。这相对简单,但许多发行版都有自己的网络控制脚本,因此它们不会将单个调度脚本放置在此目录中。例如,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 查找。解析过程通常如下展开:

  1. 应用程序调用一个函数来查找主机名背后的 IP 地址。该函数位于系统的共享库中,因此应用程序无需了解其工作原理的细节或实现是否会改变。
  2. 当共享库中的函数运行时,它会根据一组规则(位于 /etc/nsswitch.conf 中;参见第 9.15.4 节)来确定查找的行动计划。例如,规则通常说,在转到 DNS 之前,先检查 /etc/hosts 文件中是否有手动覆盖。
  3. 当函数决定使用 DNS 进行名称查找时,它会查阅另一个配置文件以查找 DNS 名称服务器。名称服务器以 IP 地址形式给出。
  4. 该函数向名称服务器发送一个 DNS 查找请求(通过网络)。
  5. 名称服务器用主机名的 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 节)。这里的其他条目说明了在本地子网上添加主机的简单方法。

NOTE

在过去的糟糕日子里,有一个中央 hosts 文件,每个人都复制到自己的机器上以保持更新(参见 RFC 606、608、623 和 625),但随着 ARPANET/互联网的发展,这很快就变得难以控制。

9.15.2 resolv.conf

DNS 服务器的传统配置文件是 /etc/resolv.conf。在过去更简单的时期,一个典型的示例如下所示,其中 ISP 的名称服务器地址是 10.32.45.2310.3.2.3

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

search 行定义了不完整主机名(仅主机名的第一部分——例如 myserver 而不是 myserver.example.com)的规则。在这里,解析器库会尝试查找 host.mydomain.example.comhost.example.com

通常,名称查找不再如此简单。DNS 配置已经有了许多增强和改进。

9.15.3 缓存与零配置DNS

传统的DNS配置存在两个主要问题。首先,本地机器不会缓存名称服务器的响应,因此由于名称服务器请求,频繁重复的网络访问可能会不必要地变慢。为了解决这个问题,许多机器(以及路由器,如果充当名称服务器)会运行一个中间守护进程来拦截名称服务器请求并缓存响应,然后尽可能使用缓存的答案。最常见的此类守护进程是systemd-resolved;你也可能在系统上看到dnsmasqnscd。你也可以将BIND(标准Unix名称服务器守护进程)设置为缓存。如果你在/etc/resolv.conf文件中看到127.0.0.53127.0.0.1,或者当你运行nslookup -debug host时该地址被列为服务器,你通常可以判断自己正在运行一个名称服务器缓存守护进程。不过,仔细检查一下。如果你正在运行systemd-resolved,你可能会注意到resolv.conf甚至不是/etc下的一个文件;它是一个指向/run下自动生成的文件的链接。

systemd-resolved的功能远不止于此,它能够组合多个名称查找服务,并为每个接口以不同的方式暴露这些服务。这解决了传统名称服务器设置的第二个问题:如果你希望能够在不进行大量配置的情况下查找本地网络上的名称,传统设置可能特别不灵活。例如,如果你在网络中设置了一个网络设备,你会希望立即通过名称来调用它。这是组播DNS(mDNS)和链路本地多播名称解析(LLMNR)等零配置名称服务系统背后的部分理念。如果某个进程想要在本地网络上通过名称查找主机,它只需通过网络广播一个请求;如果目标主机存在,它会用自己的地址回复。这些协议不仅提供主机名解析,还提供有关可用服务的信息。

246 第9章

你可以使用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文档,并熟悉DNS的一般概念,例如参考**《DNS and BIND》第5版**(Cricket Liu 和 Paul Albitz 著,O’Reilly,2006年)。

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开始。

理解网络及其配置 247

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。

248 第9章

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 AddressForeign Address字段表示从机器角度看到的连接,因此这台机器上有一个配置为10.23.2.4的接口,本地侧的端口476264147557132都已连接。这里的第一个连接显示端口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

理解网络及其配置 249

对端口编号约定稍作了解后,你可以看出这个连接很可能是由本地客户端向远程服务器发起的,因为本地侧的端口(47626)看起来像一个动态分配的数字,而远程端口(5222)是一个在/etc/services中列出的知名服务(具体来说是Jabber或XMPP消息服务)。在大多数桌面机器上,你会看到许多连接到端口443(HTTPS的默认端口)的连接。

NOTE

动态分配的端口称为临时端口

但是,如果输出中的本地端口是知名端口,那么很可能是远程主机发起了连接。在以下示例中,远程主机

第9章:理解网络配置

9.17 传输层协议(续)

9.17.1 TCP(续)

Proto Recv-Q Send-Q Local Address          Foreign Address         State      
tcp        0      0 10.23.2.4:443          172.24.54.234:43035     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--

第 1 行中本地地址为 0.0.0.0:80,表明本地机器正在监听来自任何远程机器的端口 80 连接;端口 443 同样如此(第 2 行)。服务器可以将访问限制在特定接口上,如第 3 行所示,该行中某个进程只监听 localhost 接口的连接。这里监听的是 systemd-resolved;我们在第 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 以外的协议)。

NOTE

除了 /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 的错误纠正,要么因为它们有自己的错误检测系统,要么压根不在意错误。

graph TD
    A[待发送消息:Hi! How are you today?] --> B[原始主机将消息分解成数据包]
    B --> C[数据包:Hi! H Seq:1, ow ar Seq:2, e you t Seq:3, oday Seq:4, ? Seq:5]
    C --> D[互联网将数据包传送到目标]
    D --> E[数据包到达目标主机(可能与发送顺序不同)]
    E --> F[目标主机从数据包重建消息]
    F --> G[重建消息:Hi! How are you today?]
    
    style A fill:#e6f3ff,stroke:#333,stroke-width:2px
    style G fill:#e6ffe6,stroke:#333,stroke-width:2px

图 9-4:使用 TCP 发送消息

一个使用 UDP 的应用程序例子是网络时间协议(NTP)。客户端向服务器发送一个简短简单的请求以获取当前时间,服务器的响应也同样简短。因为客户端希望尽快得到响应,所以 UDP 适合该应用;如果服务器的响应在网络中丢失,客户端可以重新发送请求或放弃。另一个例子是视频聊天。在这种情况下,图片使用 UDP 发送,如果某些片段在传输过程中丢失,接收端的客户端会尽力补偿。

NOTE

本章剩余部分涉及更高级的网络主题,如网络过滤和路由器,它们与我们已了解的较低网络层(物理层、网络层和传输层)相关。如果您愿意,可以跳到下一章,了解应用层——所有内容在用户空间汇聚的地方。您将看到实际使用网络的进程,而不仅仅是抛出一堆地址和数据包。

9.18 重新审视简单的本地网络

现在我们将研究第 9.4 节中介绍的简单网络的其他组成部分。该网络由一个作为子网的 LAN 和一个将子网连接到互联网其余部分的路由器组成。您将了解以下内容:

  • 子网上的主机如何自动获取其网络配置
  • 如何设置路由
  • 路由器的真正含义
  • 如何知道子网应使用哪些 IP 地址
  • 如何设置防火墙以过滤来自互联网的不需要的流量

大多数情况下,我们将专注于 IPv4(原因之一是地址更容易阅读),但当 IPv6 有不同之处时,您会看到如何操作。

让我们从子网上的主机如何自动获取其网络配置开始。

9.19 理解 DHCP

在 IPv4 下,当您将网络主机设置为从网络自动获取配置时,您实际上是在告诉它使用 动态主机配置协议(DHCP) 来获取 IP 地址、子网掩码、默认网关和 DNS 服务器。除了不必手动输入这些参数外,网络管理员还能从 DHCP 中获得其他优势,例如防止 IP 地址冲突和最小化网络变更的影响。几乎看不到不使用 DHCP 的网络。

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

NOTE

发起初始 DHCP 请求时,主机甚至不知道 DHCP 服务器的地址,因此它会向所有主机(通常是其物理网络上的所有主机)广播该请求。

当机器请求 DHCP 服务器为其分配 IP 地址时,它实际上是在请求一个地址的租约,租期固定。当租约到期时,客户端可以请求续租。

9.19.1 Linux DHCP 客户端

尽管有多种不同类型的网络管理器系统,但实际执行获取租约工作的 DHCP 客户端只有两种。传统标准客户端是 互联网软件联盟(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 服务器、服务器实现正确且稳定、能够跟踪和维护租约。尽管存在一个名为 DHCPv6 的 IPv6 版本,但还有一种更常见的替代方案。

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

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

一旦主机拥有链路本地地址,它就可以确定一个全球地址。它通过监听路由器发送的 路由器通告(RA) 消息来实现这一点,路由器偶尔会在链路本地网络上发送 RA 消息。RA 消息包含全球网络前缀、路由器 IP 地址以及可能的 DNS 信息。有了这些信息,主机可以尝试填充全球地址的接口标识部分,类似于它对链路本地地址所做的操作。

无状态配置依赖于至多 64 位长的全球网络前缀(也就是说,其网络掩码为 /64 或更小)。

NOTE

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

9.21 配置Linux作为路由器

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

让我们看一个例子。假设你有两个局域网子网:10.23.2.0/24 和 192.168.45.0/24。为了连接它们,你有一台Linux路由器机器,带有三个网络接口:两个用于局域网子网,一个用于互联网上行链路,如图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),数据包首先通过其 enp0s31f6 接口到达 10.23.2.1(路由器),然后通过路由器的 enp0s1 接口发送出去。

理解你的网络及其配置 255

Transclude of 图9-5:用路由器连接的两个子网

图9-5:用路由器连接的两个子网

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

# sysctl -w net.ipv4.ip_forward=1

一旦输入此命令,机器应立即开始在不同子网之间路由数据包,前提是这些子网上的主机知道将数据包发送给你刚刚创建的路由器。

NOTE

你可以使用 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 定义的私有网络

网络子网掩码CIDR 形式
10.0.0.0255.0.0.010.0.0.0/8
192.168.0.0255.255.0.0192.168.0.0/16
172.16.0.0255.240.0.0172.16.0.0/12

你可以按需划分私有子网。除非你计划在单个网络上拥有超过254台主机,否则选择一个小的子网,比如我们本章一直使用的 10.23.2.0/24。(具有此子网掩码的网络有时称为 C 类子网。虽然该术语在技术上已过时,但仍然有用。)

代价是什么?真实互联网上的主机不知道私有子网的存在,也不会向其发送数据包,因此在没有帮助的情况下,私有子网上的主机无法与外界通信。连接到互联网(具有真实的、非私有地址)的路由器需要某种方式来填补该连接与私有网络上主机之间的缺口。


9.23 网络地址转换(IP 伪装)

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

NAT 的基本思想是,路由器不仅仅将数据包从一个子网移动到另一个子网;它在移动数据包的同时对其进行转换。互联网上的主机知道如何连接到路由器,但对它背后的私有网络一无所知。私有网络上的主机不需要特殊配置;路由器就是它们的默认网关。

该系统的工作方式大致如下:

  1. 内部私有网络上的主机想要与外部世界建立连接,因此它将连接请求数据包发送到路由器。
  2. 路由器截获连接请求数据包,而不是将其发送到互联网(在那里会丢失,因为公共互联网不知道私有网络)。
  3. 路由器确定连接请求数据包的目标,并打开自己到目标的连接。
  4. 当路由器获得连接后,它向原始内部主机伪造一条“连接已建立”的消息。
  5. 路由器现在成为内部主机和目标之间的中介。目标不知道内部主机的存在;远程主机上的连接看起来像是来自路由器。

这并不像听起来那么简单。正常的 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 地址空间寿命的黑客手段。IPv6 不需要 NAT,这得益于其在9.7节中描述的更大、更复杂的地址空间。


9.24 路由器与 Linux

在宽带早期,需求不高的用户只是将他们的机器直接连接到互联网。但没过多久,许多用户就希望将单个宽带连接共享给他们的网络,Linux 用户尤其经常设置一台额外的机器作为运行 NAT 的路由器。

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

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

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

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

9.25 防火墙

路由器通常应该包含某种防火墙,以防止不受欢迎的流量进入你的网络。防火墙是一种软件和/或硬件配置,通常位于互联网与较小网络之间的路由器上,旨在确保来自互联网的“不良”内容不会危害较小的网络。你也可以在任何主机上设置防火墙功能,以便在数据包级别(而非应用层,在应用层服务器程序通常会自行尝试执行一些访问控制)筛选所有传入和传出的数据。单机上的防火墙有时被称为IP过滤

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

在没有任何防火墙的情况下,系统只会处理数据包并将其发送出去。防火墙会在上述数据传输点处为数据包设置检查点。检查点会丢弃、拒绝或接受数据包,通常基于以下一些标准:

  • 源或目标IP地址或子网
  • 源或目标端口(在传输层信息中)
  • 防火墙的网络接口

防火墙提供了一个与Linux内核中处理IP数据包的子系统进行交互的机会。现在让我们来看一下。

9.25.1 Linux防火墙基础

在Linux中,你按一系列称为的顺序创建防火墙规则。一组链构成一个。当数据包在Linux网络子系统的各个部分之间移动时,内核会将某些链中的规则应用于这些数据包。例如,从物理层到达的新数据包会被内核分类为“输入”,因此它会激活与输入相对应的链中的规则。

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

NOTE

有一个较新的系统叫做nftables,旨在取代iptables,但在撰写本文时,iptables仍然是最广泛使用的系统。管理nftables的命令是nft,并且有一个名为iptables-translate的iptables到nftables的转换器,可用于本书中展示的iptables命令。更复杂的是,最近引入了一个名为bpfilter的系统,采用了不同的方法。尽量不要被具体命令的细节所困扰——重要的是其效果。

由于可能存在多个表——每个表都有自己的链集,而链集又可以包含许多规则——数据包流可能变得相当复杂。然而,你通常主要使用一个名为filter的表,它控制基本的数据包流。filter表中有三个基本链:INPUT用于传入数据包,OUTPUT用于传出数据包,FORWARD用于路由数据包。

图9-6和图9-7展示了规则如何应用于filter表中数据包的简化流程图。之所以有两张图,是因为数据包既可以从网络接口进入系统(图9-6),也可以由本地进程生成(图9-7)。

flowchart TD
    A["来自网络的数据包"] --> B{该主机是目标?}
    B -- 是 --> C["本地进程消费"]
    B -- 否 --> D["FORWARD链处理"]
    C --> E["INPUT链处理"]
    D --> F["在前往目的地的路上被发送出去"]
    E --> G["被本地进程消费"]

图9-6:针对来自网络的传入数据包的链处理序列

flowchart LR
    A["本地进程生成的数据包"] --> B["OUTPUT链处理"]
    B --> C["发送到目的地"]

图9-7:针对来自本地进程的传入数据包的链处理序列

如你所见,来自网络的传入数据包可以被用户进程消费,并且可能不会到达FORWARD链或OUTPUT链。由用户进程生成的数据包不会到达INPUT链或FORWARD链。

这变得更复杂,因为沿途除了这三个链之外还有很多步骤。例如,数据包还会受到PREROUTING和POSTROUTING链的处理,并且链处理还可能发生在三个较低网络层中的任何一个。要了解所有发生的情况的大图,可以在互联网上搜索“Linux netfilter packet flow”,但请记住,这些图试图包含数据包输入和流的每一种可能场景。通常,像图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

WARNING

在阅读完本节的其余部分之前,不要对机器上的策略做出任何鲁莽的操作。

假设有人从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

此示例在源地址中添加了子网掩码限定符,并添加了-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.1192.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链中设置规则)。在这两种情况下,如果你使用默认的ACCEPT策略并不断插入规则来丢弃来自开始发送不良内容的源的数据包,那么你就无法获得严肃的安全性。你必须只允许你信任的数据包,并拒绝其他所有数据包。

例如,假设你的机器有一个运行在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

WARNING

不要在一台你只有远程访问权限的机器上逐条运行这些命令。第一个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 over Ethernet 的实现,还有一个基本细节尚未提及。回想一下,主机必须将 IP 数据包封装到以太网帧中,才能通过物理层将其传输到另一台主机。另外需要注意的是,帧本身不包含 IP 地址信息;它们使用 MAC(硬件)地址。问题是:在为 IP 数据包构建以太网帧时,主机如何知道哪个 MAC 地址对应目标 IP 地址?

我们通常不会过多考虑这个问题,因为网络软件包含一个自动查找 MAC 地址的系统。在 IPv4 中,这被称为地址解析协议(ARP,Address Resolution Protocol)。使用以太网作为物理层、IP 作为网络层的主机维护着一个称为 ARP 缓存 的小型表格,该表格将 IP 地址映射到 MAC 地址。在 Linux 中,ARP 缓存位于内核中。要查看机器的 ARP 缓存,请使用 ip neigh 命令。(“neigh” 部分在看到 IPv6 等效命令时会变得合理。处理 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 表示已经过了一段时间,应刷新该条目。

理解你的网络及其配置 265

当一台机器启动时,其 ARP 缓存为空。那么这些 MAC 地址是如何进入缓存的呢?这一切始于机器想要向另一台主机发送数据包时。如果目标 IP 地址不在 ARP 缓存中,将执行以下步骤:

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

NOTE

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

ARP 可能带来的唯一实际问题是,如果你将 IP 地址从一个网络接口卡移动到另一个网络接口卡(例如在测试机器时),由于不同的网卡具有不同的 MAC 地址,你的系统缓存可能会过时。Unix 系统会在活动停止一段时间后使 ARP 缓存条目失效,因此除了失效数据造成的小延迟外,不会有任何麻烦,但你可以使用以下命令立即删除 ARP 缓存条目:

# ip neigh del host dev interface

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

NOTE

不要将 ARP 与反向地址解析协议(RARP,Reverse Address Resolution Protocol) 混淆。RARP 将 MAC 地址转换回主机名或 IP 地址。在 DHCP 普及之前,一些无盘工作站和其他设备使用 RARP 来获取其配置,但如今 RARP 已很少见。

IPv6:NDP

你可能想知道为什么操作 ARP 缓存的命令不包含“arp”(或者,如果你之前模糊地见过这些内容,你可能想知道我们为什么不使用 arp)。在 IPv6 中,有一种称为邻居发现协议(NDP,Neighbor Discovery Protocol) 的新机制,用于链路本地网络。ip 命令统一了 IPv4 的 ARP 和 IPv6 的 NDP。NDP 包括以下两类消息:

  • 邻居请求(Neighbor solicitation):用于获取链路本地主机的信息,包括主机的硬件地址。
  • 邻居通告(Neighbor advertisement):用于响应邻居请求消息。

NDP 还有其他几个组成部分,包括你在第 9.20 节中看到的 RA 消息。

9.27 无线以太网

原则上,无线以太网(“Wi-Fi”)网络与有线网络没有太大区别。与许多有线硬件一样,它们具有 MAC 地址,并使用以太网帧来发送和接收数据,因此 Linux 内核可以与无线网络接口进行通信,就像与有线网络接口通信一样。网络层及以上的一切都是相同的;主要区别在于物理层的附加组件,例如频率、网络 ID 和安全特性。

与有线网络硬件不同(有线网络硬件能很好地自动适应物理设置中的细微差别,无需太多麻烦),无线网络配置的开放性要大得多。要使无线接口正常工作,Linux 需要额外的配置工具。

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

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

处理这些组件的 Linux 配置和实用程序分布在多个领域。有些在内核中;Linux 具有一组无线扩展,用于标准化用户空间对硬件的访问。在用户空间方面,无线配置可能变得复杂,因此大多数人更喜欢使用 GUI 前端(例如 NetworkManager 的桌面小程序)来使其工作。尽管如此,还是值得了解一些幕后的情况。

9.27.1 iw

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

# iw dev wlp1s0 scan

NOTE

网络接口必须处于 up 状态此命令才能工作(如果没有,请运行 ifconfig wlp1s0 up),但由于这仍在物理层,你无需配置任何网络层参数,例如 IP 地址。

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

# iw dev wlp1s0 link

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

NOTE

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

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

# iw wlp1s0 connect network_name

连接到安全网络则是另一回事。对于相当不安全的有线等效保密(WEP,Wired Equivalent Privacy) 系统,你可以在 iw connect 命令中使用 keys 参数。但是,你不应该使用 WEP,因为它不安全,而且你也不会找到太多支持它的网络。

9.27.2 无线安全

对于大多数无线安全设置,Linux 依赖 wpa_supplicant 守护进程来管理无线网络接口的身份验证和加密。该守护进程可以处理 WPA2 和 WPA3(Wi-Fi 保护访问;不要使用旧的、不安全的 WPA)身份验证方案,以及几乎任何用于无线网络的加密技术。守护进程首次启动时,会读取一个配置文件(默认为 /etc/wpa_supplicant.conf),并尝试向接入点标识自身,并基于给定的网络名称建立通信。该系统有很好的文档记录;尤其是 wpa_supplicant(8) 手册页非常详细。

每次要建立连接时手动运行守护进程是一件很繁琐的工作。事实上,仅仅创建配置文件就很繁琐,因为选项数量众多。更糟糕的是,运行 iwwpa_supplicant 的所有工作仅仅允许你的系统加入无线物理网络;它甚至不设置网络层。而这就是自动网络配置管理器(如 NetworkManager)大大减轻痛苦的地方。虽然它们自己不完成任何工作,但它们知道使无线网络运行的每个步骤的正确顺序和所需配置。

9.28 总结

正如你所见,理解各个网络层的位置和作用是理解 Linux 网络如何运行以及如何执行网络配置的关键。虽然我们只介绍了基础知识,但在物理层、网络层和传输层中的更高级主题与你在这里看到的类似。各层本身通常被细分,正如你在无线网络物理层的各个组件中所看到的那样。

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

不过,你现在可能对网络配置有点厌倦了。让我们转向使用网络——应用层。