Linux工作原理10网络应用程序和服务

  • Linux工作原理10网络应用程序和服务已关闭评论
  • 44 次浏览
  • A+
所属分类:linux技术
摘要

本章探讨基本的网络应用–在用户空间运行的客户端和服务器,它们位于应用层。由于这一层位于堆栈的顶层,离最终用户很近,因此你可能会发现这部分内容比第 9 章的内容更容易理解。事实上,你每天都在与网络浏览器等网络客户端应用程序交互。


10 网络应用程序和服务

本章探讨基本的网络应用--在用户空间运行的客户端和服务器,它们位于应用层。由于这一层位于堆栈的顶层,离最终用户很近,因此你可能会发现这部分内容比第 9 章的内容更容易理解。事实上,你每天都在与网络浏览器等网络客户端应用程序交互。

为了完成工作,网络客户端需要连接到相应的网络服务器。Unix 网络服务器有多种形式。服务器程序可以自己监听端口,也可以通过辅助服务器进行监听。我们将介绍一些常见的服务器,以及有助于了解和调试服务器运行的工具。

网络客户端使用操作系统的传输层协议和接口,因此了解 TCP 和 UDP 传输层的基础知识非常重要。让我们从使用 TCP 的网络客户端开始了解网络应用。

10.1 服务基础

TCP服务最容易理解,因为它们建立在简单、不间断的双向数据流基础之上。了解它们如何工作的最佳方法也许是直接与 TCP 80 端口上的未加密网络服务器通信,以了解数据是如何在连接中移动的。例如,运行以下命令连接到 IANA 文档示例网络服务器:

$ telnet localhost 80 

您应该会得到如下响应,表明与服务器的连接成功:

$ telnet localhost 80 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. ... $ telnet localhost 81 # 没有开放的端口 Trying 127.0.0.1... telnet: Unable to connect to remote host: Connection refused  

现在输入这两行

GET / HTTP/1.1 # 按两次ENTER键。服务器会发送一堆 HTML 文本作为回应。要终止连接,请按 CTRL-D。两次ENTER键也会退出。 HTTP/1.1 408 Request Timeout Date: Fri, 26 Jul 2024 03:00:44 GMT Server: Apache/2.4.41 (Ubuntu) Content-Length: 296 Connection: close Content-Type: text/html; charset=iso-8859-1  <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN"> <html><head> <title>408 Request Timeout</title> </head><body> <h1>Request Timeout</h1> <p>Server timeout waiting for the HTTP request from the client.</p> <hr> <address>Apache/2.4.41 (Ubuntu) Server at 127.0.1.1 Port 80</address> </body></html> Connection closed by foreign host. 

注意:HTTP 1.1 和它的前身 HTTP 1.0 一样,已经过时;现在使用的是 HTTP/2、QUIC 和新兴的 HTTP/3 等更新的协议。

在最后一行后按两次 ENTER 键。服务器会发送一堆 HTML 文本作为回应。要终止连接,请按 CTRL-D。

本练习演示了

  • 本地网络服务器进程在监听 TCP 80 端口。
  • telnet 是发起连接的客户端。

必须按 CTRL-D 键终止连接的原因是,由于大多数网页需要多次请求才能加载,因此保持连接开放是合理的。如果你在协议层面探索网络服务器,你可能会发现这种行为各不相同。例如,许多服务器在连接打开后,如果没有很快收到请求,就会迅速断开连接。

telnet 最初是用来登录远程主机的。客户端程序可能没有默认安装在你的发行版上,但很容易作为一个额外的软件包安装。尽管 telnet 远程登录服务器完全不安全(稍后将了解到),但 telnet 客户端在调试远程服务时非常有用。如果你正在寻找一个通用的网络客户端,可以考虑第 10.5.3 节中介绍的 netcat。

10.2 近距离观察

在上一个示例中,你通过 telnet 使用 HTTP 应用层协议与网络上的网络服务器进行了手动交互。虽然你通常会使用网络浏览器来进行这种连接,但让我们在 telnet 的基础上更进一步,使用一个知道如何与 HTTP 应用层对话的命令行程序。我们将使用带有特殊选项的 curl 工具来记录通信细节:

$ curl --trace-ascii trace_file http://www.example.org/ 

你会得到大量 HTML 输出。忽略它(或将其重定向到 /dev/null),转而查看新创建的文件 trace_file。如果连接成功,在curl尝试与服务器建立TCP连接时,文件的第一部分应该如下所示:

== Info:   Trying 93.184.216.34... == Info: TCP_NODELAY set == Info: Connected to www.example.org (93.184.216.34) port 80 (#0) 

到目前为止,你所看到的一切都发生在传输层或以下。然而,如果连接成功,curl 会尝试发送请求(“头”);这是应用层开始的地方:

 => Send header, 79 bytes (0x4f) 2 0000: GET / HTTP/1.1 0010: Host: www.example.org 0027: User-Agent: curl/7.58.0 0040: Accept: */* 004d:  

第1行是curl的调试输出,告诉你接下来要做什么。其余几行显示了curl发送给服务器的内容。粗体文字是发送给服务器的内容;开头的十六进制数字是curl添加的调试偏移量,帮助你跟踪发送或接收了多少数据。

在第2行,你可以看到curl首先向服务器发送了一条GET命令(就像使用telnet一样),然后是一些额外的服务器信息和一行空行。接下来,服务器会发送一个回复,首先是它自己的报头:

<= Recv header, 17 bytes (0x11) 0000: HTTP/1.1 200 OK <= Recv header, 22 bytes (0x16) 0000: Accept-Ranges: bytes <= Recv header, 12 bytes (0xc) 0000: Age: 17629 --snip-- 

和前面的输出很像,<=行是调试输出,0000:在输出行之前,用来告诉你偏移量(在curl中,头不计入偏移量,这就是为什么所有这些行都以0开头)。

服务器回复中的标头可能相当长,但在某些时候,服务器会从发送标头过渡到发送实际请求的文件,就像下面这样:

<= Recv header, 22 bytes (0x16) 0000: Content-Length: 1256 <= Recv header, 2 bytes (0x2) 1 0000:  <= Recv data, 1256 bytes (0x4e8) 0000: <!doctype html>.<html>.<head>.    <title>Example Domain</title>. 0040: .    <meta charset="utf-8" />.    <meta http-equiv="Content-type --snip-- 

该输出还说明了应用层的一个重要属性。尽管调试输出显示的是Recv header和Recv data,暗示这是来自服务器的两种不同的信息,但curl如何与操作系统对话以获取这两种信息、操作系统如何处理它们,以及网络如何处理下面的数据包,都没有任何区别。curl知道在此之前它一直在获取报文头,但当它接收到HTTP报文头结束的空行1时,它就知道要把后面的内容理解为请求的文档。

发送这些数据的服务器也是如此。在发送回复时,服务器的操作系统并不区分报头和文档数据;这种区分发生在用户空间服务器程序中。

10.3 网络服务器

大多数网络服务器与系统中的其他服务器守护进程(如 cron)一样,只是它们与网络端口交互。事实上,第 7 章讨论的 syslogd 在使用 -r 选项启动时,就会接受 514 端口的 UDP 数据包。

以下是其他一些常见的网络服务器,你可能会发现它们正在你的系统上运行:

  • httpd、apache、apache2、nginx 网络服务器
  • sshd 安全 shell 守护进程
  • postfix、qmail、sendmail 邮件服务器
  • cupsd 打印服务器
  • nfsd、mountd 网络文件系统(文件共享)守护进程
  • smbd、nmbd Windows 文件共享守护进程(参见第 12 章)
  • rpcbind 远程过程调用(RPC)端口映射服务守护进程

大多数网络服务器的一个共同特点是,它们通常作为多个进程运行。至少有一个进程在网络端口上监听,当收到新的传入连接时,监听进程会使用 fork() 创建一个子进程,然后由子进程负责新的连接。子进程(通常称为工作进程)会在连接关闭时终止。与此同时,原始监听进程继续监听网络端口。通过这种进程,服务器可以轻松地处理许多连接,而不会有太多麻烦。

不过,这种模式也有一些例外。调用 fork() 会增加大量系统开销。为了避免这种情况,高性能 TCP 服务器(如 Apache 网络服务器)可能会在启动时创建多个工作进程,以便在需要时处理连接。接受 UDP 数据包的服务器根本不需要分叉,因为它们不需要监听连接;它们只需接收数据并作出反应。

10.3.1 Secure Shell

每个网络服务器程序的工作方式都有些不同。为了亲身体验服务器的配置和运行,让我们仔细看看独立的安全外壳(SSH)服务器。作为最常见的网络服务应用程序之一,SSH 是远程访问 Unix 机器的事实标准。SSH 的设计目的是实现安全的 shell 登录、远程程序执行、简单的文件共享等功能--它取代了旧式的、不安全的 telnet 和 rlogin 远程访问系统,使用公钥加密技术进行身份验证,并使用更简单的密码来处理会话数据。大多数互联网服务提供商和云服务提供商都要求使用 SSH 通过 shell 访问其服务,许多基于 Linux 的网络设备(如网络附加存储设备或 NAS 设备)也通过 SSH 提供访问。OpenSSH (http://www.openssh.com/) 是一种流行的 Unix 免费 SSH 实现,几乎所有 Linux 发行版都预装了它。OpenSSH 的客户端程序是 ssh,服务器程序是 sshd。SSH 协议主要有两个版本: 1 和 2。OpenSSH 只支持版本 2,由于存在漏洞和缺乏使用,已经放弃了对版本 1 的支持。

SSH 有许多有用的功能和特性,其中包括以下功能:

  • 加密你的密码和所有其他会话数据,保护你不被窥探。
  • 隧道其他网络连接,包括来自 X 窗口系统客户端的网络连接。(你将在第 14 章学到更多关于 X 的知识)。
  • 为几乎所有操作系统提供客户端。
  • 使用密钥进行主机身份验证。

注意:隧道传输是将一个网络连接打包并传输到另一个网络连接中的过程。使用 SSH 对 X 窗口系统连接进行隧道传输的优点是,SSH 会为你设置显示环境,并对隧道内的 X 数据进行加密。

SSH 也有一些缺点。首先,为了建立 SSH 连接,你需要远程主机的公钥,而且不一定能以安全的方式获得(不过你可以手动检查以确保你没有被欺骗)。要了解几种密码学方法的工作原理,请阅读《严肃密码学》(Serious Cryptography)一书: 现代加密实用入门》(No Starch Press,2017 年),作者 Jean-Philippe Aumasson。关于 SSH 的两本深度书籍是 SSH Mastery: 第 2 版,Michael W. Lucas 著(Tilted Windmill Press,2018 年)和 SSH,The Secure Shell: 第 2 版,作者 Daniel J. Barrett、Richard E. Silverman 和 Robert G. Byrnes(O'Reilly,2005 年)。

公钥加密:我们一直在使用公钥这个术语,但并没有太多的上下文,所以让我们回过头来简单讨论一下,以防你对它不熟悉。直到 20 世纪 70 年代,加密算法都是对称的,要求信息的发送方和接收方拥有相同的密钥。破解密码需要窃取密钥,而拥有密钥的人越多,密钥被泄露的机会就越多。但使用公开密钥加密技术时,有两个密钥:公开密钥和私人密钥。公开密钥可以加密信息,但不能解密;因此,谁能获得这把密钥并不重要。只有私人密钥才能解密来自公开密钥的信息。在大多数情况下,保护私人密钥更容易,因为只需一份副本,而且不必传输。

加密之外的另一个应用是身份验证;有一些方法可以在不传输任何密钥的情况下验证某人是否持有给定公钥的私钥。

10.3.2 sshd服务

运行sshd服务允许远程连接到系统需要配置文件和主机密钥。大多数发行版会将配置文件保存在 /etc/ssh 配置目录中,并在您安装其 sshd 软件包时为您正确配置一切。(服务器配置文件名为 sshd_config,很容易与客户端的 ssh_config 设置文件混淆,所以要小心)。

你应该不需要更改 sshd_config 文件中的任何内容,但检查一下也无妨。该文件由键值对组成,如本片段所示。

Port 22 #AddressFamily any  #ListenAddress 0.0.0.0 #ListenAddress :: #HostKey /etc/ssh/ssh_host_rsa_key #HostKey /etc/ssh/ssh_host_ecdsa_key #HostKey /etc/ssh/ssh_host_ed25519_key 

以 # 开头的行是注释,sshd_config 中的许多注释表示各种参数的默认值,如本节选所示。sshd_config(5) 手册页面包含参数和可能值的说明,但这些是最重要的:

  • HostKey 文件 将文件用作主机密钥。(接下来将介绍主机密钥)。
  • PermitRootLogin 值 如果值设置为 yes,则允许超级用户使用 SSH 登录。将值设为否则无法登录。
  • LogLevel 级别 以 syslog 级别(默认为 INFO)记录信息。
  • SyslogFacility name 使用 syslog 设施名称(默认为 AUTH)记录信息。
  • X11Forwarding 值 如果值设置为 yes,则启用 X 窗口系统客户端隧道。
  • XAuthLocation path 指定系统中 xauth 实用程序的位置。没有该路径,X 隧道将无法运行。如果 xauth 不在 /usr/bin,则将路径设为 xauth 的完整路径名。

OpenSSH 有多个主机密钥集。每个密钥集都有一个公钥(扩展名为 .pub 文件)和一个私钥(没有扩展名)。不要让任何人看到私钥,即使是在你自己的系统上,因为如果有人获取了私钥,你就会面临被入侵的风险。

SSH 版本 2 有 RSA 和 DSA 密钥。RSA 和 DSA 是公钥加密算法。密钥文件名如表 10-1 所示。

表 10-1: OpenSSH 密钥文件

Linux工作原理10网络应用程序和服务

创建密钥需要进行数字计算,生成公钥和私钥。通常情况下,你不需要创建密钥,因为 OpenSSH 安装程序或发行版的安装脚本会为你创建密钥,但如果你计划使用 ssh-agent 等程序,提供无密码的身份验证服务,就需要知道如何创建密钥。要创建 SSH 协议版本 2 密钥,请使用 OpenSSH 自带的 ssh-keygen 程序:

# ssh-keygen -t rsa -N '' -f /etc/ssh/ssh_host_rsa_key # ssh-keygen -t dsa -N '' -f /etc/ssh/ssh_host_dsa_key 

SSH 服务器和客户端还使用名为 ssh_known_hosts 的密钥文件来存储来自其他主机的公钥。如果要使用基于远程客户端身份的身份验证,服务器的 ssh_known_hosts 文件必须包含所有受信任客户端的主机公钥。如果要更换机器,了解密钥文件会很方便。从头开始安装新机器时,可以从旧机器导入密钥文件,确保用户在连接新机器时不会出现密钥不匹配的情况。

10.3.2.1 启动 SSH 服务器

虽然大多数发行版都自带 SSH,但它们通常不会默认启动 sshd 服务器。在 Ubuntu 和 Debian 上,新系统不会安装 SSH 服务器(openssh-server);安装其软件包会创建密钥、启动服务器,并将服务器启动添加到启动配置中。

Fedora 默认安装了 sshd,但已关闭。要在启动时启动 sshd,请像这样使用 systemctl:

# systemctl enable sshd 

如果想立即启动服务器而无需重启,请使用

# systemctl start sshd 

Fedora 通常会在第一次启动 sshd 时创建任何缺失的主机密钥文件。

如果您运行的是其他发行版,可能不需要手动配置 sshd 启动。不过,你应该知道有两种启动模式:独立模式和按需模式。独立服务器要常见得多,只需以 root 身份运行 sshd 即可。sshd 服务器进程会将其 PID 写入 /var/run/sshd.pid(当然,当由 systemd 运行时,它也会被其 cgroup 跟踪,这在第 6 章中已有介绍)。

作为替代方案,systemd 可以通过套接字单元按需启动 sshd。这通常不是个好主意,因为服务器偶尔需要生成密钥文件,而这个过程可能需要很长时间。

10.3.3 fail2ban

如果在机器上设置 SSH 服务器并将其开放到互联网上,很快就会发现不断有人试图入侵。如果你的系统配置正确,而且你没有选择愚蠢的密码,这些暴力破解攻击是不会成功的。不过,它们会很烦人,消耗 CPU 时间,还会不必要地扰乱日志。

为了防止这种情况,您需要建立一种机制来阻止重复登录尝试。到目前为止,fail2ban 软件包是最常用的方法;它只是一个监视日志信息的脚本。当看到某台主机在一定时间内发出了一定数量的失败请求时,fail2ban 就会使用 iptables 创建一条规则,拒绝来自该主机的流量。经过一段指定时间后(在此期间,主机可能已经放弃了连接),fail2ban 会删除该规则。

大多数 Linux 发行版都提供了预设 SSH 默认值的 fail2ban 软件包。

10.3.4 SSH 客户端

要登录远程主机,请运行

$ ssh remote_username@remote_host 

如果本地用户名与远程主机上的用户名相同,则可以省略 remote_username@。您还可以在 ssh 命令之间运行管道,如下面的示例所示,将目录 dir 复制到另一台主机:

$ tar zcvf - dir | ssh remote_host tar zxvf - 

全局 SSH 客户端配置文件 ssh_config 应位于 /etc/ssh,与 sshd_config 文件位于同一位置。与服务器配置文件一样,客户端配置文件也有键值对,但不需要更改。

使用 SSH 客户端最常见的问题是本地 ssh_known_hosts 或 .ssh/known_hosts 文件中的 SSH 公钥与远程主机上的密钥不匹配。错误的密钥会导致类似的错误或警告:

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ @    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY! Someone could be eavesdropping on you right now (man-in-the-middle attack)! It is also possible that the RSA host key has just been changed. The fingerprint for the RSA key sent by the remote host is 38:c2:f6:0d:0d:49:d4:05:55:68:54:2a:2f:83:06:11. Please contact your system administrator. Add correct host key in /home/user/.ssh/known_hosts to get rid of this message. 1 Offending key in /home/user/.ssh/known_hosts:12 RSA host key for host has changed and you have requested strict checking. Host key verification failed. 

这通常意味着远程主机的管理员更改了密钥(硬件或云服务器升级时经常会发生这种情况),但如果不确定,向管理员确认一下也无妨。无论如何,前面的信息告诉你,坏密钥位于用户已知主机文件 1 的第 12 行。

如果你不怀疑有诈,只需删除违规行或用正确的公钥替换即可。

10.3.4.1 SSH文件传输客户端

OpenSSH 包含文件传输程序 scp 和 sftp,这两个程序可替代旧式的不安全程序 rcp 和 ftp。你可以使用 scp 将文件传输到远程机器或从远程机器传输到你的机器,或从一台主机传输到另一台主机。它的工作原理与 cp 命令类似。下面是几个例子。

从远程主机复制文件到当前目录:

$ scp user@host:file . 

将文件从本地机器复制到远程主机:

$ scp file user@host:dir 

将文件从一台远程主机复制到另一台远程主机:

$ scp user1@host1:file user2@host2:dir 

sftp 程序与过时的命令行 ftp 客户端类似,使用 get 和 put 命令。远程主机必须安装有 sftp-server 程序,如果远程主机也使用 OpenSSH,则可以使用该程序。

如果你需要比 scp 和 sftp 更多的功能和灵活性(例如,如果你经常传输大量文件),请参考第 12 章中介绍的 rsync。

10.3.4.2 非Unix平台的SSH客户端

有适用于所有流行操作系统的 SSH 客户端。你应该选择哪个?PuTTY 是一款优秀的基本 Windows 客户端,包含一个安全的文件拷贝程序。MacOS 基于 Unix,包含 OpenSSH。另外Windows下面的MobaXterm用户甚至更多。

10.3.4.3 免密登录实例

# ssh-keygen -t rsa Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/my_id  Enter passphrase (empty for no passphrase): Enter same passphrase again: Your identification has been saved in /root/.ssh/my_id. Your public key has been saved in /root/.ssh/my_id.pub. The key fingerprint is: 1c:ee:bb:73:b2:42:34:02:e2:85:bf:c9:97:01:d1:f7 root@cae.netezza.com The key's randomart image is: +--[ RSA 2048]----+ |  ..o            | |...o . .         | |..o.. . o        | | . ...oo E       | |  . ooo.S        | |   + o..         | |    ..  .        | |      . o..      | |       .+*       | +-----------------+ You have new mail in /var/spool/mail/root # ssh-copy-id root@tf2 # ssh tf2  

注意sshd_config中PubkeyAuthentication要设置为yes。参考:https://www.ibm.com/support/pages/configuring-ssh-login-without-password

10.4 systemd之前的网络连接服务器:inetd/xinetd

在 systemd 和第 6.3.7 节中介绍的套接字单元得到广泛使用之前,有一些服务器提供了构建网络服务的标准方法。许多次要网络服务对连接的要求非常相似,因此为每项服务安装独立服务器的效率会很低。每个服务器都必须单独配置,以处理端口监听、访问控制和端口配置。对于大多数服务来说,这些操作都是以相同的方式执行的;只有当服务器接受连接时,才会以不同的方式处理通信。

简化服务器使用的一种传统方法是使用 inetd 守护进程,它是一种超级服务器,旨在规范网络端口访问以及服务器程序和网络端口之间的接口。启动 inetd 后,它会读取配置文件,然后监听文件中定义的网络端口。当有新的网络连接进入时,inetd 会将一个新启动的进程连接到该连接上。

inetd 的更新版本 xinetd 提供了更简便的配置和更好的访问控制,但 xinetd 几乎已被 systemd 完全取代。不过,在旧系统或不使用 systemd 的系统上,你可能会看到它。

TCP WRAPPERS: TCPPD、/etc/hosts.allow 和 /etc/hosts.deny
在 iptables 等低级防火墙流行之前,许多管理员使用 TCP 封装库和守护进程来控制对网络服务的访问。在这些实现中,inetd 运行 tcpd 程序,首先查看传入连接以及 /etc/hosts.allow 和 /etc/hosts.deny 文件中的访问控制列表。tcpd 程序会记录连接,如果它认为传入连接没有问题,就会将其交给最终服务程序。你可能会遇到仍在使用 TCP 封装系统的系统,但我们不会详细介绍,因为它在很大程度上已不再使用。

10.5 诊断工具

让我们来看看一些对探查应用层有用的诊断工具。有些工具会深入到传输层和网络层,因为应用层中的所有内容最终都会映射到这些较低层中的内容。

如第 9 章所述,netstat 是一种基本的网络服务调试工具,可以显示大量传输层和网络层统计数据。表 10-2 列出了一些用于查看连接的有用选项。

表 10-2:netstat 的有用连接报告选项
Linux工作原理10网络应用程序和服务

10.5.1 lsof

在第 8 章中,你了解到 lsof 不仅可以跟踪打开的文件,还可以列出当前使用或监听端口的程序。要获取此类程序的完整列表,请运行

COMMAND     PID     USER   FD   TYPE   DEVICE SIZE/OFF NODE NAME rpcbind     700     root    6u  IPv4    10492      0t0  UDP *:sunrpc 1 rpcbind     700     root    8u  IPv4    10508      0t0  TCP *:sunrpc (LISTEN) avahi-dae   872    avahi   13u  IPv4 21736375      0t0  UDP *:mdns 2 cupsd      1010     root    9u  IPv6 42321174      0t0  TCP ip6-localhost:ipp (LISTEN) 3 ssh       14366    juser    3u  IPv4 38995911      0t0  TCP thishost.local:55457-> 4     somehost.example.com:ssh (ESTABLISHED) chromium- 26534    juser    8r  IPv4 42525253      0t0  TCP thishost.local:41551-> 5     anotherhost.example.com:https (ESTABLISHED) 

以普通用户身份运行该命令时,只会显示该用户的进程。以 root 用户身份运行该命令时,输出结果应如下所示,显示各种进程和用户:

此示例输出显示了服务器和客户端程序的用户和进程 ID,从最上面的老式 RPC 服务 1,到 avahi 提供的多播 DNS 服务 2,甚至到 IPv6 就绪的打印机服务 cupsd 3。最后两个条目显示的是客户端连接:SSH 连接 4 和来自 Chromium 网络浏览器的安全网络连接 5。由于输出内容可能非常广泛,通常最好使用过滤器(如下节所述)。

lsof 程序与 netstat 程序一样,会尝试将找到的每个 IP 地址反向解析为主机名,从而减慢输出速度。使用 -n 选项可禁用名称解析:

# lsof -n -i 

还可以指定 -P 来禁用 /etc/services 端口名称查找。

如果要查找某个端口(例如,知道某个进程正在使用某个端口,并想知道该进程是什么),请使用此命令:

# lsof -i:port 

完整语法如下

# lsof -iprotocol@host:port 

协议、@host 和 :port 参数都是可选的,它们将相应地过滤 lsof 的输出。与大多数网络工具一样,host 和 port 可以是名称或数字。例如,如果只想查看 TCP 端口 443(HTTPS 端口)上的连接,请使用

# lsof -iTCP:443 

要根据 IP 版本进行过滤,请使用 -i4 (IPv4) 或 -i6 (IPv6)。您可以将其作为单独选项添加,也可以在更复杂的过滤器中直接添加数字(例如,-i6TCP:443)。

你可以从 /etc/services 中指定服务名称(如 -iTCP:ssh),而不是数字。

连接状态是一个特别方便的 lsof 过滤器。例如,要只显示监听 TCP 端口的进程,请输入

# lsof -iTCP -sTCP:LISTEN 

通过该命令,您可以很好地概览系统上当前运行的网络服务器进程。不过,由于 UDP 服务器不监听,也没有连接,因此必须使用 -iUDP 才能查看正在运行的客户端和服务器。这通常不是问题,因为系统中可能不会有很多 UDP 服务器。

10.5.2 tcpdump

通常情况下,您的系统不会去理会那些不属于其 MAC 地址的网络流量。如果您需要查看穿越网络的确切信息,tcpdump 会将您的网络接口卡设置为混杂模式,并报告穿越网络的每个数据包。输入不带参数的 tcpdump 会产生如下输出,其中包括一个 ARP 请求和网络连接:

# tcpdump tcpdump: listening on eth0 20:36:25.771304 arp who-has mikado.example.com tell duplex.example.com 20:36:25.774729 arp reply mikado.example.com is-at 0:2:2d:b:ee:4e 20:36:25.774796 duplex.example.com.48455 > mikado.example.com.www: S 3200063165:3200063165(0) win 5840 <mss 1460,sackOK,timestamp 38815804[|tcp]> (DF) 20:36:25.779283 mikado.example.com.www > duplex.example.com.48455: S 3494716463:3494716463(0) ack 3200063166 win 5792 <mss 1460,sackOK,timestamp 4620[|tcp]> (DF) 20:36:25.779409 duplex.example.com.48455 > mikado.example.com.www: . ack 1 win 5840 <nop,nop,timestamp 38815805 4620> (DF) 20:36:25.779787 duplex.example.com.48455 > mikado.example.com.www: P 1:427(426) ack 1 win 5840 <nop,nop,timestamp 38815805 4620> (DF) 20:36:25.784012 mikado.example.com.www > duplex.example.com.48455: . ack 427 win 6432 <nop,nop,timestamp 4620 38815805> (DF) 20:36:25.845645 mikado.example.com.www > duplex.example.com.48455: P 1:773(772) ack 427 win 6432 <nop,nop,timestamp 4626 38815805> (DF) 20:36:25.845732 duplex.example.com.48455 > mikado.example.com.www: . ack 773 win 6948 <nop,nop,timestamp 38815812 4626> (DF)  9 packets received by filter 0 packets dropped by kernel 

你可以通过添加过滤器让 tcpdump 更具针对性。您可以根据源主机和目标主机、网络、以太网地址、网络模型中许多不同层的协议等进行过滤。tcpdump 可识别的许多数据包协议包括 ARP、RARP、ICMP、TCP、UDP、IP、IPv6、AppleTalk 和 IPX 数据包。例如,要让 tcpdump 只输出 TCP 数据包,请运行

# tcpdump tcp 

要查看网络数据包和 UDP 数据包,请输入

# tcpdump udp or port 80 or port 443 

关键字 or 指定左边或右边的条件必须为真才能通过过滤器。同样,和关键字要求两个条件都为真。

如果需要进行大量的数据包嗅探,可以考虑使用图形用户界面来替代 tcpdump,如 Wireshark。

在前面的示例中,tcp、udp 和端口 80 是被称为基元的过滤器的基本元素。表 10-3 列出了最重要的基元。

Linux工作原理10网络应用程序和服务

tcpdump 可以使用多个运算符(如 and 和 !),还可以用括号将运算符分组。如果你打算认真使用 tcpdump,请务必阅读 pcap-filter(7) 手册,尤其是描述基元的部分。

注意:使用 tcpdump 时要小心。本节前面显示的 tcpdump 输出只包括数据包 TCP(传输层)和 IP(互联网层)报头信息,但你也可以让 tcpdump 打印整个数据包内容。尽管现在大多数重要的网络流量都已通过 TLS 加密,但除非你拥有这些网络或得到许可,否则不应随意窥探。

Linux工作原理10网络应用程序和服务

参考资料

10.5.3 netcat

netcat 可以连接远程 TCP/UDP 端口、指定本地端口、监听端口、扫描端口、将标准 I/O 重定向到网络连接或从网络连接重定向到标准 I/O 等。要使用 netcat 打开一个端口的 TCP 连接,请运行

$ netcat host port 

如果将标准输入重定向到 netcat,netcat 会在对方结束连接时终止,这可能会让人感到困惑,因为在发送数据后,你可能无法得到提示(与几乎所有其他命令管道不同)。你可以随时按下 CTRL-C 结束连接。(如果你希望程序和网络连接根据标准输入流终止,请尝试使用 sock 程序)。

要监听某个端口,请运行

$ netcat -l port_number 

如果 netcat 成功监听到端口,它就会等待连接,一旦建立连接,就会打印该连接的输出,并将任何标准输入发送到该连接。

下面是关于 netcat 的一些补充说明:

  • 默认情况下没有太多调试输出。如果出现故障,netcat 会无声地失败,但会设置一个适当的退出代码。如果需要更多信息,可添加 -v (“verbose”)选项。
  • 默认情况下,netcat 客户端会尝试连接 IPv4 和 IPv6。不过,在服务器模式下,netcat 默认使用 IPv4。要强制使用协议,IPv4 使用-4,IPv6 使用-6。
  • -u 选项指定 UDP 而不是 TCP。

10.5.4 端口扫描

有时,你甚至不知道网络上的机器在提供什么服务,甚至不知道哪些 IP 地址在使用。Network Mapper (Nmap) 程序会扫描机器或机器网络上的所有端口,寻找开放的端口,并列出找到的端口。大多数发行版都有 Nmap 软件包,也可以从 http://www.insecure.org/ 获取。(有关 Nmap 的所有功能,请参阅 Nmap 手册页面和在线资源)。

在列出自己机器上的端口时,通常至少从两点运行 Nmap 扫描:从自己的机器和另一台机器(可能在本地网络之外)。这样做可以让您大致了解防火墙阻止了哪些端口。

如果你想用 Nmap 扫描的网络是由其他人控制的,请先征得允许。网络管理员会监视端口扫描,通常会禁止访问运行端口扫描的机器。

运行 nmap host 在主机上运行一般扫描。例如

$ nmap 10.1.2.2 Starting Nmap 5.21 ( http://nmap.org ) at 2015-09-21 16:51 PST Nmap scan report for 10.1.2.2 Host is up (0.00027s latency). Not shown: 993 closed ports PORT     STATE SERVICE 22/tcp   open  ssh 25/tcp   open  smtp 80/tcp   open  http 111/tcp  open  rpcbind 8800/tcp open  unknown 9000/tcp open  cslistener 9090/tcp open  zeus-admin  Nmap done: 1 IP address (1 host up) scanned in 0.12 seconds 

正如你在这里看到的,许多服务都是开放的,其中许多在大多数发行版中都不是默认开启的。事实上,这里唯一默认开启的是 111 端口,即 rpcbind 端口。

如果添加 -6 选项,Nmap 还能扫描 IPv6 端口。这可以方便地识别不支持 IPv6 的服务。

10.6 远程过程调用

上一节中扫描的 rpcbind 服务怎么样?RPC 是远程过程调用(remote procedure call,简称 RPC)的缩写,是一个位于应用层底层的系统。它的设计目的是让程序员更容易构建客户端/服务器网络应用程序,其中客户端程序调用在远程服务器上执行的函数。每种远程服务器程序都有一个指定的程序编号。

RPC 实现使用 TCP 和 UDP 等传输协议,需要一个特殊的中介服务将程序编号映射到 TCP 和 UDP 端口。该服务器称为 rpcbind,它必须运行在任何想要使用 RPC 服务的计算机上。

要查看计算机上有哪些 RPC 服务,请运行

$ rpcinfo -p localhost 

RPC 是一种不死的协议。网络文件系统(NFS)和网络信息服务(NIS)系统都使用 RPC,但它们在单机上完全没有必要。但每当你认为已经完全不需要 rpcbind 时,就会有其他东西出现,例如 GNOME 中的文件访问监视器 (FAM) 支持。

10.7 网络安全

由于Linux 是 PC 平台上非常流行的 Unix,尤其是它被广泛用于网络服务器,因此吸引了许多不怀好意的人试图入侵计算机系统。第 9.25 节讨论了防火墙,但这并不是安全问题的全部。

网络安全会吸引极端分子--那些非常喜欢入侵系统的人(无论是为了好玩还是赚钱),以及那些精心设计保护方案并非常喜欢将试图入侵其系统的人赶走的人。(幸运的是,要确保系统安全,你并不需要知道太多。以下是几条基本经验法则:

运行尽可能少的服务 入侵者无法侵入系统中不存在的服务。如果你知道某项服务是什么,但你并没有使用它,那么就不要仅仅因为 “以后可能会用到 ”而打开它。
用防火墙尽可能多地阻止 Unix 系统有许多内部服务,但你可能并不知道(如 RPC 端口映射服务器的 TCP 111 端口),世界上任何其他系统都不应该知道这些服务。由于许多不同类型的程序会监听不同的端口,因此要跟踪和监管系统中的服务非常困难。为了防止入侵者发现系统中的内部服务,请使用有效的防火墙规则,并在路由器上安装防火墙。
跟踪您向互联网提供的服务 如果您运行 SSH 服务器、Postfix 或类似服务,请及时更新软件并获取相应的安全警报。(有关在线资源,请参见第 10.7.2 节)。
服务器使用 “长期支持 ”发行版 安全团队的工作通常集中在稳定、受支持的发行版上。开发和测试版本(如 Debian Unstable 和 Fedora Rawhide)受到的关注要少得多。
不要给不需要的人提供系统账户 从本地账户获得超级用户访问权限要比远程入侵容易得多。事实上,由于大多数系统上都有大量的软件(以及由此产生的错误和设计缺陷),因此在进入 shell 提示符后,很容易就能获得系统的超级用户访问权限。不要以为你的朋友知道如何保护自己的密码(或者一开始就选择了好的密码)。
避免安装可疑的二进制软件包 它们可能包含木马。
这就是保护自己的实用方法。但为什么要这样做呢?针对 Linux 机器的网络攻击有三种基本类型:

  • 完全入侵 这意味着获得机器的超级用户权限(完全控制)。入侵者可以通过尝试服务攻击(如缓冲区溢出漏洞),或接管保护不力的用户账户,然后尝试利用编写不佳的 setuid 程序来实现这一目的。
  • 拒绝服务(DoS)攻击 无需使用任何特殊访问权限,就能阻止计算机执行网络服务,或迫使计算机以其他方式出现故障。通常,DoS 攻击只是网络请求的泛滥,但也可能是利用服务器程序中的漏洞导致崩溃。这类攻击较难防范,但较容易应对。
  • 恶意软件 Linux 用户对电子邮件蠕虫和病毒等恶意软件大多免疫,原因很简单,因为他们的电子邮件客户端还没蠢到会实际运行邮件附件中发送的程序。但 Linux 恶意软件确实存在。避免从你从未听说过的地方下载和安装可执行文件。

10.7.1 典型漏洞

有两种基本类型的漏洞需要担心:直接攻击和明文密码嗅探。直接攻击试图接管机器,但并不十分隐蔽。最常见的攻击方式之一是在系统中找到未受保护或易受攻击的服务。这可以是简单的默认未验证服务,例如没有密码的管理员账户。一旦入侵者可以访问系统上的某项服务,他们就可以利用它试图入侵整个系统。过去,常见的直接攻击是缓冲区溢出漏洞,即粗心的程序员没有检查缓冲区数组的边界。内核中的地址空间布局随机化(ASLR)技术和其他地方的保护措施在一定程度上缓解了这种情况。

明文密码嗅探攻击会捕获以明文形式通过网络发送的密码,或使用从众多数据泄露事件中提取的密码数据库。一旦攻击者获取了你的密码,游戏就结束了。从这里开始,攻击者将不可避免地试图在本地获得超级用户访问权限(如前所述,这比远程攻击要容易得多),或试图将机器用作攻击其他主机的中介,或两者兼而有之。

注意:如果需要运行不提供本机加密支持的服务,可以试试 Stunnel (http://www.stunnel.org/),这是一个加密封装包,与 TCP 封装包很相似。Stunnel 尤其擅长封装通常使用 systemd 套接字单元或 inetd 激活的服务。

由于实施和设计不当,有些服务长期成为攻击目标。你应该始终停用以下服务(这些服务目前都已过时,而且在大多数系统中很少被默认激活):

  • ftpd 不管出于什么原因,所有 FTP 服务器似乎都存在漏洞。此外,大多数 FTP 服务器使用明文密码。如果需要将文件从一台机器转移到另一台机器,请考虑使用基于 SSH 的解决方案或 rsync 服务器。
  • telnetd、rlogind、rexecd 所有这些服务都以明文形式传递远程会话数据(包括密码)。除非使用启用 Kerberos 的版本,否则应避免使用这些服务。

10.7.2 安全资源

这里有三个很好的安全资源:

  • SANS Institute (http://www.sans.org/) 提供培训、服务、列出当前顶级漏洞的免费每周通讯、安全策略示例等。
  • 卡内基梅隆大学软件工程研究所的 CERT 部门 (http://www.cert.org/) 是查找最严重问题的好地方。
  • 黑客和 Nmap 创建者 Gordon “Fyodor” Lyon (http://www.insecure.org/) 的项目 Insecure.org 是查找 Nmap 和各种网络漏洞测试工具的好去处。与许多其他网站相比,该网站在漏洞利用方面更加开放和具体。

如果你对网络安全感兴趣,就应该了解一下传输层安全(TLS)及其前身安全套接字层(SSL)。这些用户空间网络级别通常被添加到网络客户端和服务器中,通过使用公钥加密和证书来支持网络交易。Davies 的 Implementing SSL/TLS Using Cryptography and PKI(Wiley,2011 年)或 Jean-Philippe Aumasson 的 Serious Cryptography(《严肃密码学》)是一本很好的指南: 现代加密实用入门》(No Starch Press,2017 年)。

10.8 展望未来

如果你有兴趣接触一些复杂的网络服务器,一些非常常见的服务器包括 Apache 或 nginx 网络服务器和 Postfix 电子邮件服务器。网络服务器尤其易于安装,大多数发行版都提供了安装包。如果你的机器位于防火墙或支持 NAT 的路由器后面,你可以随意尝试配置,而不必担心安全问题。

在过去的几章中,我们逐渐从内核空间进入了用户空间。本章讨论的实用程序中,只有 tcpdump 等少数几个与内核交互。本章其余部分将介绍套接字如何在内核传输层和用户空间应用层之间架起桥梁。这是程序员特别感兴趣的高级内容,如果你愿意,可以跳到下一章。

10.9 网络套接字

现在我们将换个角度,看看进程是如何完成从网络读取数据和向网络写入数据的工作的。对于进程来说,从已经建立的网络连接中读取数据和向网络连接中写入数据都非常简单:只需要一些系统调用,你可以在 recv(2) 和 send(2) 手册中找到相关信息。从进程的角度来看,最需要知道的也许是在使用这些系统调用时如何访问网络。在 Unix 系统中,进程使用套接字来标识何时以及如何与网络对话。套接字是进程通过内核访问网络的接口,代表了用户空间和内核空间的边界。它们通常也用于进程间通信(IPC)。

由于进程需要以不同方式访问网络,因此有不同类型的套接字。例如,TCP 连接由流套接字(SOCK_STREAM,从程序员的角度来看)表示,UDP 连接由数据报套接字(SOCK_DGRAM)表示。

网络套接字的设置可能有些复杂,因为你需要在特定的时候考虑套接字类型、IP 地址、端口和传输协议。不过,在理清所有初始细节后,服务器会使用某些标准方法来处理从网络传入的流量。图 10-1 中的流程图显示了许多服务器如何处理传入流套接字的连接。

Linux工作原理10网络应用程序和服务

请注意,这种服务器涉及两种套接字:一种用于监听,一种用于读写。主进程使用监听套接字查找来自网络的连接。当有新连接进入时,主进程会使用 accept() 系统调用来接受连接,并为该连接创建专用的读写套接字。接着,主进程使用 fork() 创建一个新的子进程来处理该连接。最后,原始套接字仍作为监听器,代表主进程继续寻找更多连接。

当一个进程设置了一个特定类型的套接字后,它就能以适合套接字类型的方式与之交互。这就是套接字的灵活性所在:如果你需要改变底层传输层,你不必重写所有发送和接收数据的部分;你主要需要修改初始化代码。

如果你是一名程序员,并想学习如何使用套接字接口,W. Richard Stevens、Bill Fenner 和 Andrew M. Rudoff 合著的《Unix 网络编程》第 1 卷第 3 版(Addison-Wesley Professional,2003 年)是一本经典指南。第 2 卷也涉及进程间通信。

10.10 Unix 域套接字

使用网络设施的应用程序不必涉及两个独立的主机。许多应用程序都是作为客户机-服务器或点对点机制构建的,其中在同一台机器上运行的进程使用进程间通信来协商需要完成哪些工作以及由谁来完成。例如,systemd 和 NetworkManager 等守护进程使用 D-Bus 监控系统事件并做出反应。

进程之间可以通过本地主机(127.0.0.1 或 ::1 )使用常规 IP 网络进行通信,但它们通常使用一种称为 Unix 域套接字的特殊套接字作为替代。当进程连接到 Unix 域套接字时,其行为几乎与网络套接字完全相同:可以监听和接受套接字上的连接,甚至可以选择不同的套接字类型,使其行为与 TCP 或 UDP 相似。

注意:请记住,Unix 域套接字不是网络套接字,其背后也没有网络。使用域套接字甚至不需要配置网络。Unix 域套接字也不必绑定到套接字文件。一个进程可以创建一个未命名的 Unix 域套接字,并与另一个进程共享地址。

开发人员喜欢将 Unix 域套接字用于 IPC 有两个原因。首先,它们允许在文件系统中使用特殊的套接字文件来控制访问权限,因此任何无法访问套接字文件的进程都无法使用它。另外,由于不与网络交互,所以更简单,也不容易受到传统网络入侵。例如,你通常可以在 /var/run/dbus 中找到 D-Bus 的套接字文件:

$ ls -l /var/run/dbus/system_bus_socket  srwxrwxrwx 1 root root 0 Nov 9 08:52 /var/run/dbus/system_bus_socket 

其次,由于 Linux 内核在处理 Unix 域套接字时无需经过网络子系统的许多层,因此性能往往要好得多。

为 Unix 域套接字编写代码与支持普通网络套接字并无太大区别。由于可以带来很大的好处,一些网络服务器同时通过网络和 Unix 域套接字进行通信。例如,MySQL 数据库服务器 mysqld 可以接受来自远程主机的客户端连接,但通常也会在 /var/run/mysqld/mysqld.sock 提供一个 Unix 域套接字。

您可以使用 lsof -U 查看系统上当前使用的 Unix 域套接字列表:

# lsof -U COMMAND     PID       USER   FD   TYPE     DEVICE SIZE/OFF     NODE NAME mysqld    19701      mysql   12u  unix 0xe4defcc0      0t0 35201227 /var/run/mysqld/mysqld.sock chromium- 26534      juser    5u  unix 0xeeac9b00      0t0 42445141 socket tlsmgr    30480    postfix    5u  unix 0xc3384240      0t0 17009106 socket tlsmgr    30480    postfix    6u  unix 0xe20161c0      0t0    10965 private/tlsmgr --snip-- 

由于许多应用程序大量使用未命名的套接字,因此列表会很长,这些套接字在 NAME 输出列中用 socket 表示。