目录

4.13 网络

本节我们讲解网络相关的操作系统原理。网络分析的目的除了改进网络延时和吞吐量外,另一个常见的任务是消除可能由丢包引起的延时异常。网络分析是跨硬件和软件的。硬件指的是物理网络包括网络接口卡,交换机,路由器和网管。软件指的是内核协议栈。

1. 网络

本节我们将从以下几个方面介绍网络相关的操作系统原理:

  1. 网络协议栈
  2. TCP 连接状态转移
  3. TCP 拥塞控制
  4. 操作系统的网络栈

最后我们会介绍网络相关的检测指标。想对网络深入了解,推荐大家阅读:

  1. CCNA学习指南
  2. TCP/IP详解 卷1:协议

1. 网络协议栈

网络的五层和七层模型想必大家都听过,下面是这两个协议栈模型的示意图:

/images/linux_pf/network_model.png

数据经过网络协议栈时,通过包封装将每层协议的元数据添加到负载前(包头),之后(包后),或者二者,但不会修改负载数据。下面展示了以太网 TCP/IP 的栈封装过程:

/images/linux_pf/tcp_encapsulation.png

数据就是这样经过封装,并通过路由器等网络设备在网络上进行传播,并最后被目标主机接收和解析。

/images/linux_pf/network_trans.png

2. TCP 状态转移

下面是TCP建立连接三次握手和断开连接四次挥手,TCP 状态示意图: /images/linux_pf/tcp_state.png

实际传输过程中,服务器端收到客户端的 FIN 后,服务器端可以同时关闭连接,这样就可以把 ACK 和 FIN 合并到一起发送,节省了一个包,变成了“三次挥手”。

而如果服务器端收到客户端的 FIN 后,还没发送完数据,就会先回复客户端一个 ACK 包。稍等一会儿,完成所有数据包的发送后,才会发送 FIN 包。这也就是四次挥手了。

更详细的状态转移过程参见: TCP 状态转移

/images/linux_pf/tcp_states.png

2.1 长连接和短连接

长连接和短连接除了关闭连接的时机,更重要的是长连接需要有一个保活机制。

3. TCP 拥塞控制

TCP 的滑动窗口,拥塞控制可以看这篇文章图解TCP 重传、滑动窗口、流量控制、拥塞控制发愁,图文并茂很容易理解。

3.1 Socket 连接选项

Socket Api 提供了众多选项用于控制 TCP 的传输。常见的会影响传输性能的选项包括:

  1. TCP_QUICKACK: 取消延迟确认
    • 延迟确认是针对 TCP ACK 的一种优化机制,也就是说,不用每次请求都发送一个 ACK,而是先等一会儿(比如 40ms),看看有没有“顺风车”。如果这段时间内,正好有其他包需要发送,那就捎带着 ACK 一起发送过去。当然,如果一直等不到其他包,那就超时后单独发送 ACK。
    • 只有 TCP 套接字专门设置了 TCP_QUICKACK ,才会开启快速确认模式;否则,默认情况下,采用的就是延迟确认机制:
  2. TCP_NODELAY: 禁用 Nagle 算法
    • Nagle 算法规定,一个 TCP 连接上,最多只能有一个未被确认的未完成分组;在收到这个分组的 ACK 前,不发送其他分组。这些小分组会被组合起来,并在收到 ACK 后,用同一个分组发送出去。它通过合并 TCP 小包,提高网络带宽的利用率。
    • Linux 上默认会启用 Nagle 算法,只有设置了 TCP_NODELAY 后才会禁用
    • 当客户端的延迟确认和服务器端的 Negle 算法同时启用时会对网络性能造成非常明显的网络延时。通常都需要关闭Nagle 算法。
  3. TCP_CORK: 开启 TCP_CORK 后,可以让小包聚合成大包后再发送(会阻塞小包的发送)
  4. SO_SNDBUF 和 SO_RCVBUF ,可以分别调整套接字发送缓冲区和接收缓冲区的大小

使用 strace 可以跟踪网络发送的系统调用,从而确认网络连接启用的 Socket 连接选项。

4. 操作系统的网络栈

网络通信软件包括网络栈、TCP和设备驱动程序。下面是一个通用的网络栈模型。

/images/linux_pf/network_stack.png

  • ARP: 地址解析协议
  • Data Link(generic net driver): 数据链路,通用网络驱动软件
  • NIC: 网卡: 网卡是发送和接收网络包的基本设备。在系统启动过程中,网卡通过内核中的网卡驱动程序注册到系统中。而在网络收发过程中,内核通过中断跟网卡进行交互。

网络的收发的过程中,网卡硬中断只处理最核心的网卡数据读取或发送,而协议栈中的大部分逻辑,都会放到软中断中处理。

4.1 网络包收发过程

/images/linux_pf/tcp_send.png

网络包的收过程

当一个网络帧到达网卡后:

  1. 网卡会通过 DMA 方式,把这个网络包放到收包队列中;然后通过硬中断,告诉中断处理程序已经收到了网络包。
  2. DMA(Direct Memory Access,直接存储器访问) 允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载
  3. 网卡中断处理程序会为网络帧分配内核数据结构(sk_buff),并将其拷贝到 sk_buff 缓冲区中;然后再通过软中断,通知内核收到了新的网络帧。
  4. 内核协议栈从缓冲区中取出网络帧,并通过网络协议栈,从下到上逐层处理这个网络帧。
    • 在链路层检查报文的合法性,找出上层协议的类型(比如 IPv4 还是 IPv6),再去掉帧头、帧尾,然后交给网络层。
    • 网络层取出 IP 头,判断网络包下一步的走向,当网络层确认这个包是要发送到本机后,就会取出上层协议的类型(比如 TCP 还是 UDP),去掉 IP 头,再交给传输层处理
    • 传输层取出 TCP 头或者 UDP 头后,根据 < 源 IP、源端口、目的 IP、目的端口 > 四元组作为标识,找出对应的 Socket,并把数据拷贝到 Socket 的接收缓存中。
    • 最后,应用程序就可以使用 Socket 接口,读取到新接收到的数据了。

环形缓冲区,由于需要 DMA 与网卡交互,属于网卡设备驱动的范围。

sk_buff 缓冲区,是一个维护网络帧结构的双向链表,链表中的每一个元素都是一个网络帧(Packet)。虽然 TCP/IP 协议栈分了好几层,但上下不同层之间的传递,实际上只需要操作这个数据结构中的指针,而无需进行数据复制。

套接字缓冲区,则允许应用程序,给每个套接字配置不同大小的接收或发送缓冲区。应用程序发送数据,实际上就是将数据写入缓冲区;而接收数据,其实就是从缓冲区中读取。至于缓冲区中数据的进一步处理,则由传输层的 TCP 或 UDP 协议来完成。

sk_buff、套接字缓冲、连接跟踪等,都通过 slab 分配器来管理。你可以直接通过 /proc/slabinfo,来查看它们占用的内存大小。

网络包的发送过程

网络包的发送方向,正好跟接收方向相反:

  1. 应用程序调用 Socket API(比如 sendmsg)发送网络包。由于这是一个系统调用,所以会陷入到内核态的套接字层中。套接字层会把数据包放到 Socket 发送缓冲区中。
  2. 接下来,网络协议栈从 Socket 发送缓冲区中,取出数据包;再按照 TCP/IP 栈,从上到下逐层处理。
  3. 这一切完成后,会有软中断通知驱动程序:发包队列中有新的网络帧需要发送。
  4. 最后,驱动程序通过 DMA ,从发包队列中读出网络帧,并通过物理网卡把它发送出去。

更加详细的收发过程详见Linux网络包收发总体过程

4.2 Linux

Linux 中 TCP,IP以及通用网络驱动软件是内核的核心组件,设备驱动程序是附加模块,数据包以 struct_sk_buff 数据类型穿过这些内核组件。通用驱动程序能通过合并中断提高性能。

数据包的高处理器率通过调用多个 CPU 处理包和 TCP/IP 栈。Linux3.7 记录了如下不同的方法:

  1. RSS,接收端缩放: 现代 NIC 支持多个队列并且计算包哈希以放置不同的队列,而后依次按直接中断由不同的 CPU 处理。这个哈希值可能基于 IP和TCP 端口,因此源自同一连接的包能被同一个 CPU 处理。
  2. RPS,接收数据包转向: 对于不支持多队列的NIC的RSS关键实现。一个短中断服务例行程序映射传入的数据包给 CPU 处理,用一个类似的哈希按数据包头的字段映射数据包到 CPU
  3. RFS,接收流转向: 类似 RPS,不过偏向前一个处理套接字的CPU,以提高CPU缓存命中率和内存本地性
  4. 加速接收数据流转向: 对于支持该功能的NIC,这是 RFS 的硬件实现。它用流信息更新NIC以确定中断哪个CPU
  5. XPS,传输数据包转向: 对于支持多个传输队列的NIC,这支持多个 CPU传输队列

当缺乏数据包的CPU负载均衡时,NIC会中断同一个CPU,进而达到100% 的使用率并成为瓶颈。

基于例如RFS 实现的缓存一致性等因素而映射中断到多个 CPU,能显著提升网络性能。这样能通过 irqbalancer 进程实现,它能分配中断请求 IRQ 给 CPU。

注:NIC 是网络接口卡的简称

4.3 积压队列和缓冲

积压队列

突发的链接由积压队列处理。这里有两个队列:

  1. 一个在TCP 握手完成前处理未完成的连接,又称为SYN积压队列
  2. 另一个处理等待应用程序接受的已建立的会话,又称为侦听积压队列

/images/linux_pf/backlog_queues.png

有两个队列的情况下第一个可作为潜在的伪造连接的集结地,仅在连接建立后才迁移到第二个队列,此队列可以设置的很长以吸收海量 SYN,并且优化为仅存放最少的必要元数据 第二个队列可由应用程序 listent() 的积压队列参数设置。

1
2
3
4
5
6
# 查看积压队列的容量
sysctl net.ipv4.tcp_max_syn_backlog

# 修改积压队列的容量
sysctl -w net.ipv4.tcp_max_syn_backlog=1024
net.ipv4.tcp_max_syn_backlog = 1024

缓冲

利用套接字的发送和接收缓冲能够提升数据的吞吐量:

/images/linux_pf/socket_buffer.png

对于写通道,数据缓冲在 TCP发送缓冲区,然后送往IP发送。尽管IP协议有能力分段数据包,TCP仍试图发送 MSS 长度的段给IP以避免这种情况。这意味重发送单位对应分段的单位,否则一个被丢弃的数据段会导致整个分段前的数据包被重新传输。由于避免了分段和组装常规数据包,这种实现方式提升了 TCP/IP 栈的效率。

缓冲区的大小是可调整的。Linux 会基于连接的活跃度自动调节缓冲区大小。

网络设备驱动

网络设备驱动通常还有一个附加的缓冲区(环形缓冲区 DMA)用于在内核内存与NIC间发送和接收数据包。

随着10GbE以太网网的引入,利用中断结合模式的利于性能的功能愈发常见。一个中断仅仅在计时器激活或者达到一定数据量的包时才被发送,而不是每当有数据包达到就中断内核。这降低了内核与 NIC 通信的频率。允许缓冲更多的发送,从而达到更高的吞吐量。

5. 网络监测

下面是网络常用的性能测量指标:

  1. 延时
  2. 带宽: 表示链路的最大传输速率
  3. 吞吐量: 表示单位时间内成功传输的数据量,吞吐量 / 带宽,也就是该网络的使用率。
  4. PPS:
    • PPS,是 Packet Per Second(包 / 秒),表示以网络包为单位的传输速率。
    • PPS 通常用来评估网络的转发能力。基于 Linux 服务器的转发,很容易受到网络包大小的影响
    • 交换机通常不会受到太大影响,可以做到线性转发
    • 对于数据库、缓存等系统,快速完成网络收发,即低延迟,是主要的性能目标
  5. 应用层指标:
    • 网络的可用性(网络能否正常通信)
    • 并发连接数(TCP 连接数量)
    • 丢包率(丢包百分比)
    • 重传率(重新传输的网络包比例)等也是常用的性能指标。
  6. DNS
  7. DDos 拒绝服务攻击

总的来说,先要获得网络基准测试报告,然后通过相关性能工具,定位出网络性能瓶颈。在优化网络性能时,可以结合 Linux 系统的网络协议栈和网络收发流程,然后从应用程序、套接字、传输层、网络层再到链路层等,进行逐层优化。

5.1 延时

延时是一个重要的网络性能指标,并且有多种测量方法,包括:

  1. 主机名解析延时
  2. ping 延时: 往返延时 RTT(Round-Trip Time)
  3. 连接延时
  4. 首字节延时
  5. 往返延时

除了使用 ping, traceroute 或 hping3 的 TCP 和 UDP 模式,也可以获取网络延迟。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# 1. 通过 TCP 测量网络延时
# -c表示发送3次请求,-S表示设置TCP SYN,-p表示端口号为80
$ hping3 -c 3 -S -p 80 baidu.com

# --tcp表示使用TCP协议,-p表示端口号,-n表示不对结果中的IP地址执行反向域名解析
$ traceroute --tcp -p 80 -n baidu.com

# 使用 wrk 可以测量并发连接的延时
# 测试80端口性能
$ wrk --latency -c 100 -t 2 --timeout 2 baidu.com

网络延时的排查思路

在发现网络延迟增大后,你可以用 traceroute、hping3、tcpdump、Wireshark、strace 等多种工具,来定位网络中的潜在问题。比如:

  1. 使用 hping3 以及 wrk 等工具,确认单次请求和并发请求情况的网络延迟是否正常。
  2. 使用 traceroute,确认路由是否正确,并查看路由中每一跳网关的延迟。
  3. 使用 tcpdump 和 Wireshark,确认网络包的收发是否正常。
  4. 使用 strace 等,观察应用程序对网络套接字的调用情况是否正常。

这样,就可以依次从路由、网络包的收发、再到应用程序等,逐层排查,直到定位问题根源。

5.2 带宽、吞吐量和PPS

网络吞吐量和 PPS 可以使用 sar 命令查看。带宽的查看可以使用 ethtool 工具。

使用率

网络连接口的使用率可以用当前的吞吐量除以最大带宽来计算。但是考虑到可变的带宽和自动协商的双工模式,计算不像看上去那么简单。对于全双工,使用率适合每个方向且用该方向当前的吞吐量除以当前协商的带宽来计算。

饱和度

测量连接积压队列导致的丢包是一种衡量网络连接饱和度的方法

5.2 DNS

DNS 不仅方便了人们访问不同的互联网服务,更为很多应用提供了,动态服务发现和全局负载均衡(Global Server Load Balance,GSLB)的机制。这样,DNS 就可以选择离用户最近的 IP 来提供服务。即使后端服务的 IP 地址发生变化,用户依然可以用相同域名来访问。

DNS 解析是基础而重要的一个环节。我们需要关注它的性能。 可以借助 nslookup 或者 dig 的调试功能,分析 DNS 的解析过程,再配合 ping 等工具调试 DNS 服务器的延迟,从而定位出性能瓶颈。

DNS 有如下几种常见的优化方法:

  1. 对 DNS 解析的结果进行缓存
  2. 对 DNS 解析的结果进行预取。这是浏览器等 Web 应用中最常用的方法
  3. 使用 HTTPDNS 取代常规的 DNS 解析。这是很多移动应用会选择的方法,特别是如今域名劫持普遍存在,使用 HTTP 协议绕过链路中的 DNS 服务器,就可以避免域名劫持的问题。
  4. 基于 DNS 的全局负载均衡(GSLB)。这不仅为服务提供了负载均衡和高可用的功能,还可以根据用户的位置,返回距离最近的 IP 地址。

5.3 DDos 拒绝服务攻击

DDos 的类型

DDoS 的前身是 DoS(Denail of Service),即拒绝服务攻击,指利用大量的合理请求,来占用过多的目标资源,从而使目标服务无法响应正常请求。

DDoS 可以分为下面几种类型。

  1. 第一种,耗尽带宽。无论是服务器还是路由器、交换机等网络设备,带宽都有固定的上限。
  2. 第二种,耗尽操作系统资源
  3. 第三种,耗尽应用程序资源

DDoS 并不一定是因为大流量或者大 PPS,有时候,慢速的请求也会带来巨大的性能下降(这种情况称为慢速 DDoS)。比如,很多针对应用程序的攻击,都会伪装成正常用户来请求资源。这种情况下,请求流量可能本身并不大,但响应流量却可能很大,并且应用程序内部也很可能要耗费大量资源处理。

DDos 的缓解方案

针对DDos 我们有一下方法缓解其影响:

  1. 单源Dos攻击:
    • 通过防火墙禁止特定源的访问
    • 限制syn并发数
    • 限制单个IP在60秒新建立的连接数
    • 扩大半开状态的连接数
    • 减少每个 SYN_RECV 失败时的重试次数
    • 使用 TCP SYN Cookies
  2. 多源DDos: 只能缓解,而无法彻底解决
    • 在服务器外部的网络设备中,设法识别并阻断流量(当然前提是网络设备要能扛住流量攻击)。比如,购置专业的入侵检测和防御设备,配置流量清洗设备阻断恶意流量等。
    • 针对应用,需要应用程序考虑识别,并尽早拒绝掉这些恶意流量,比如合理利用缓存、增加 WAF(Web Application Firewall)、使用 CDN 等等。

下面是用 hping3 模拟 Dos,并尝试缓解 Dos 攻击的示例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 1. 构造一个 SYN 泛洪攻击
# -S参数表示设置TCP协议的SYN(同步序列号),
# -p表示目的端口为80
# -i u10表示每隔10微秒发送一个网络帧
# --flood 尽可能按最快速度发,不用回应
# --rand-source 使用随机源地址
$ hping3 -S -p 80 -i u10 --flood --rand-source 192.168.0.30

# 1. SYN 泛洪攻击对策 -- 针对单个源
# 限制syn并发数为每秒1次
$ iptables -A INPUT -p tcp --syn -m limit --limit 1/s -j ACCEPT

# a.限制单个IP在60秒新建立的连接数为10
$ iptables -I INPUT -p tcp --dport 80 --syn -m recent --name SYN_FLOOD --update --seconds 60 --hitcount 10 -j REJECT

# b.查看半连接容量并修改积压队列的容量
sysctl net.ipv4.tcp_max_syn_backlog

sysctl -w net.ipv4.tcp_max_syn_backlog=1024
net.ipv4.tcp_max_syn_backlog = 1024

# c.连接每个 SYN_RECV 时,如果失败的话,内核还会自动重试,
# 默认的重试次数是 5 次。可将其减小为 1 次:
sysctl -w net.ipv4.tcp_synack_retries=1
net.ipv4.tcp_synack_retries = 1

# d.TCP SYN Cookies 也是一种专门防御 SYN Flood 攻击的方法

TCP SYN Cookies:

  1. 是基于连接信息(包括源地址、源端口、目的地址、目的端口等)以及一个加密种子(如系统启动时间),计算出一个哈希值(SHA1),这个哈希值称为 cookie。
  2. 然后,这个 cookie 就被用作序列号,来应答 SYN+ACK 包,并释放连接状态。
  3. 当客户端发送完三次握手的最后一次 ACK 后,服务器就会再次计算这个哈希值,确认是上次返回的 SYN+ACK 的返回包,才会进入 TCP 的连接状态。
  4. 因而,开启 SYN Cookies 后,就不需要维护半开连接状态了,进而也就没有了半连接数的限制。
  5. 开启 TCP syncookies 后,内核选项 net.ipv4.tcp_max_syn_backlog 也就无效了。