Post

TCP发送接收过程(三) -- 实验观察TCP性能和窗口、Buffer的关系(下)

通过tc模拟异常实验观察TCP性能和窗口、Buffer的关系。

TCP发送接收过程(三) -- 实验观察TCP性能和窗口、Buffer的关系(下)

1. 引言

上篇进行了基本的TCP发送接收窗口调整实验,实际情况中则可能会碰到网络丢包、网络延迟、乱序等各种情况,本篇借助tc进行异常情况模拟,观察TCP发送接收的表现。

2. tc说明

几篇不错的tc相关介绍和使用参考文章:

2.1. 基本介绍

TC(Traffic Control)Linux内核中的流量控制子系统,而本文中使用的tc是与之对应的用户态工具,包含在iproute2包中。

tc包含如下作用:

  • 流量整形(Shaping): 限制网络接口的传输速率,平滑突发流量,避免拥塞,只用于网络出方向(egress)
  • 流量调度(Scheduling):按优先级分配带宽(reorder),确保关键业务的传输优先级。只用于网络出方向(egress)
  • 流量策略(Policing): 根据到达速率决策接收还是丢弃数据包(如限制 DDoS 攻击),用于网络入方向(ingress)
  • 流量过滤(Dropping/Filtering): 根据规则(如 IP 地址、端口、协议)对流量分类,并施加不同的策略(如限速、丢弃),可以用于出入两个方向

tc的操作依赖 3个核心组件

  • qdisc(queueing discipline)队列规则,决定了数据包如何排队和发送
    • 支持多种规则,如 pfifo(pure First In, First Out queue)、pfifo_fast(默认规则)、htbtbf(token buffer filter,令牌桶过滤器)、fifo
    • qdisc是一个整流器/整形器(shaper),可以包含多个class,不同class可以应用不同的策略。
    • qdisc对外暴露两个回调接口enqueuedequeue,分别用于数据包入队和数据包出队,而具体的排队算法实现则在qdisc内部隐藏。
      • 不同的qdisc实现在Linux内核中实现为不同的内核模块,在系统的内核模块目录里可以查看前缀为sch_的模块。
      • ls -ltrh /usr/lib/modules/5.14.0-503.14.1.el9_5.x86_64/kernel/net/sched/sch_*
  • class(类别),将流量分类,每类流量可分配不同的带宽或优先级
    • 与 htb 等qdisc结合使用,实现带宽分级管理
  • filter(过滤器),根据规则对流量进行分类
    • filter也叫分类器(classifier),需要挂载/附着(attach)到class 或 qdisc上
    • 用于对网络设备上的流量进行分类,并将包分发(dispatch)到前面定义的不同 class

使用步骤:

  • 1、为网络设备 创建一个qdisc
    • qdisc需要附着(attach)到某个网络接口
  • 2、创建流量类别(class),并附着(attach)到qdisc
  • 3、创建filter,并attach到qdisc
  • 4、另外 可以给filter添加action,比如将选中的包丢弃(drop)
    • 大约15年时,TC框架加入了Classifier-Action机制。
    • classifier即上面的filter,当数据包匹配到特定filter后可执行该filter所挂载的actions对数据包进行处理。

class可以理解为qdisc的载体,它还可以包含子classqdisc,最终形成的是一个以 root qdisc 为根的 。当数据包流入最顶层qdisc时,会层层向下递归进行调用。

简要处理过程说明:

  • 父对象(qdiscclass)的enqueue回调被调用时,其上挂载的filter会依次调用,直到一个filter匹配成功;
  • 然后将数据包入队到filter所指向的class,具体实现则是调用class所配置的qdiscenqueue函数;
  • 没有成功匹配filter的数据包则分类到默认的class中。

tc_qdisc_process
出处

扩展阅读Linux-Traffic-Control-Classifier-Action-Subsystem-Architecture.pdf,介绍了TC Classifier-Action子系统的架构。

  • 可了解到其和netfilter的位置关系:数据流入 -> ingress TC -> netfilter -> egress TC -> 数据流出。见下图示意。
  • 关于netfilter可见 TCP发送接收过程(三) – 学习netfilter和iptables 中的学习梳理。另外看了下当前Rocky Linux环境的firewalld防火墙后端,也默认nftables了(弃用了iptables):FirewallBackend=nftables

linux-tc-netfilter-datapath
出处

之前看netfilter时的数据流也可以看到tc qdisc的位置:
netfilter各hook点和控制流
出处 Wikipedia

2.2. 使用示例

一个网络接口有两个默认的qdisc锚点(挂载点)入方向的锚点叫做ingress, 出方向叫做root

  • 入方向的ingress功能比较有限,不能挂载其他的class,只是做为Classifier-Action机制的挂载点。

qdiscclass标识符叫做handle, 它是一个32位的整数,分为majorminor两部分,各占16位,表达方式为:m:n, m或n省略时,表示0

  • m:0一般表示qdisc
  • 对于classminor一般从1开始,major则用它所挂载qdisc的major号
  • root qdischandle一般使用1:0表示,ingress一般使用ffff:0表示。

下面tc用到的netem需要先安装 sch_netem 内核模块(yum install kernel-modules-extra确认内核小版本也是一致的、modprobe sch_netem)。
之前 实验bcc网络工具时就踩过坑了,所以本次安装Rocky Linux时/boot分区分的空间足够大。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 增加规则
    # dev enp4s0,附着到enp4s0网口
    # root,网卡的根级规则
    # netem(Network Emulator),内核的网络模拟模块
    # delay 2ms,延时2ms
tc qdisc add dev enp4s0 root netem delay 2ms
# 修改
tc qdisc change dev enp4s0 root netem delay 3ms
# 查看
tc qdisc show dev enp4s0
# 删除
tc qdisc del dev enp4s0 root

# 一些查看命令
tc qdisc ls
tc class ls
tc filter ls
# 查看统计信息
tc -s qdisc show dev enp4s0
tc -s class show dev enp4s0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# root,表示将此队列规则(qdisc)添加到接口的根节点(即所有流量的默认路径)
# `handle 1:` 为该 qdisc 分配句柄 1:,后续的子规则可通过此句柄引用(例如 parent 1:)
# netem,使用 netem(网络模拟器)工具,用于模拟网络延迟、丢包、重排序等特性
# reorder 25% 50%:25% 的数据包会被随机重排序(即数据包的顺序被打乱),50% 表示重排序的最大延迟
tc qdisc add dev bond0 root handle 1: netem delay 10ms reorder 25% 50% loss 0.2%
# `parent 1:` 表示此 qdisc 是之前 netem qdisc(句柄 1:)的子节点。
    # netem 会先处理流量,再传递给 tbf
# `handle 2:` 为该 qdisc 分配句柄 2:,用于后续管理或调试。
# tbf,使用 tbf(token buffer filter,令牌桶过滤器)算法,用于 限制带宽 和 控制突发流量。
# rate 1mbit,将带宽限制为 1 Mbps
# burst 32kbit,允许突发流量的最大值为 32 Kbit(即短时间内的瞬时流量可超过 1 Mbps,但总和不能超过 rate × latency + burst)。
    # 突发流量的计算公式:burst = rate × (latency + burst_time)
# latency 10ms,设置 最大延迟为 10ms,即数据包在队列中等待的时间不能超过 10ms
    # 该参数与 tbf 的令牌桶机制结合,用于控制流量整形的精度
tc qdisc add dev bond0 parent 1: handle 2: tbf rate 1mbit burst 32kbit latency 10ms

3. 实验说明

3.1. 环境和步骤

环境:和上篇一样,起2个阿里云ECS(2核2G)

  • 系统:Rocky Linux release 9.5 (Blue Onyx)
  • 内核:5.14.0-503.35.1.el9_5.x86_64

步骤说明:

  • 服务端:python -m http.server起http服务,监听8000端口
    • dd一个2GB文件用于测试:dd if=/dev/zero of=test.dat bs=1G count=2
  • 客户端:curl请求下载文件
    • curl "http://xxx:8000/test.dat" --output test.dat
  • 抓包:
    • tcpdump -i any port 8000 -s 100 -w normal_client.cap -v
    • 先两端都抓包对比下,服务端抓包:tcpdump -i any port 8000 -s 100 -w normal_server.cap -v

3.2. 用例

用例:参考下述博文,里面的场景已经比较充分了

具体用例:

  • 1、基准用例。直接复用上篇默认参数的场景结果即可,不用重复实验
  • 2、tc叠加延时:2ms
  • 3、tc叠加延时:100ms
  • 4、tc模拟丢包:1%
  • 5、tc模拟丢包:20%
  • 6、tc模拟包重复:1%
  • 7、tc模拟包重复:20%
  • 8、tc模拟包损坏:1%
  • 9、tc模拟包损坏:20%
  • 10、tc模拟包乱序:1%、delay 100ms、相关性 10%
  • 11、tc模拟包乱序:20%、delay 100ms、相关性 10%
  • 12、tc限制带宽:50Mbps
  • 13、tc限制带宽:1Mbps

4. 小结

5. 参考

This post is licensed under CC BY 4.0 by the author.