# 节点流量问题：限速 + Telegram 告警 + 自动停机守护
Date: 2026-02-18


> 节前发现 VPS 上运行节点，流量忽然增加，紧急增加了限速。整理下如何用 Linux `tc` 做限速、如何做月流量守护、如何通过 Telegram 实时接收告警。

## 一、问题背景

跑节点常见两个冲突目标：

1. 节点要尽量稳定在线（避免频繁超时、掉线）。
2. VPS 月流量有限（例如 1TB），不能无限跑。

之前运行正常而且节点流量不是很高，忽然流量突增，右节点 2 天跑了 700G，于是赶紧使用 claude code（后被封了 :(）、gemini 生成脚本。

主要目标是能够限制指定端口的流量，但是也不能影响 ssh 等服务以免影响正常服务，涉及的命令也梳理了一下。

---

## 二、`tc` 是什么及主要参数

`tc`（traffic control）是 Linux 内核流量控制工具，主要参数：

- **qdisc**: 调度排队
- **class**: 流量分类
- **filter**: 过滤器分类
- **action**: 动作，drop/mirred
- **handle**: 对象的标识
- **chain**: 过滤器链，用于 tc filter，大配量优化用
- **monitor**: 监控

---

## 三、`tc` 核心概念

### 1. qdisc（队列规则）

`qdisc` 是调度入口
| 工具名称 | 核心定位 | 核心特性 & 避坑指南 | 典型实战场景 |
| :--- | :--- | :--- | :--- |
| **fq_codel** | **公平+低延迟**<br>(Scheduling) | ✅ **现代标准**。结合了多流公平(FQ)与主动弃包(CoDel)。<br>💡 **抗 Bufferbloat 神器**。 | **通用默认防卡顿**<br>(HTB 的最佳叶子节点) |
| **Cake** | **全能流控**<br>(Shaping+AQM) | ✅ `fq_codel` 的进化版，自带整形功能。<br>⚠️ 对内核版本要求较高 (OpenWrt 常用)。 | **软路由/家庭宽带**<br>(一步到位的低延迟) |
| **TBF** | **简单限速**<br>(Shaping) | ✅ 令牌桶算法，配置简单。<br>⚠️ **一刀切**，不分流，参数不当会导致吞吐抖动。 | **网卡暴力限速**<br>(如限制 eth0 总速 10M) |
| **SFQ** | **老牌公平**<br>(Scheduling) | ⚠️ 仅保证连接间的公平，不负责压低延迟。<br>💡 防止单连接霸占带宽。 | **简单的多用户共享** |
| **pfifo_fast** | **旧默认**<br>(Scheduling) | 🛑 只有简单的 3 个优先级。<br>⚠️ 不抗 Bufferbloat，P2P 一多 SSH 必卡。 | **老旧系统默认**<br>(建议替换) |
| **Netem** | **网络仿真**<br>(Utility) | ✅ 模拟延时、丢包、乱序、重复。<br>💡 开发测试专用。 | **弱网环境测试**<br>(测试程序健壮性) |
| **Ingress** | **入站控制**<br>(Utility) | ⚠️ 入方向通常只能丢包(Policing)。<br>💡 若要整形需配合 IFB 设备。 | **限制下载速度**<br>(配合 IFB) |

`tbf`（Token Bucket Filter）由于其不区分流量类型，当 P2P 数据量增大拥堵时，ssh 请求包需要进行排队导致出现 head-of-line blocking，致使 ssh 延迟极高甚至无法登录。

后来选用了 `htb`（Hierarchical Token Bucket），它可以通过 class 对流量分类，对于节点流量和 ssh 流量进行区分，进行分类后，再通过 fq_codel，根据限流的需求进行精细化配置。

### 2. class（分类）

| 参数 | 全称 | 技术定义 (Definition) | 最佳实践策略 (Best Practice) |
| :--- | :--- | :--- | :--- |
| **rate** | **Guaranteed Rate**<br>(保障速率) | 该 Class 承诺的**最低可用带宽**。即使网络拥堵，HTB 也会确保该类流量能获得此速率。 | **SSH/交互类**：设为 512Kbps - 2Mbps（确保连接存活）。<br>**业务类**：设为剩余带宽的预估值。 |
| **ceil** | **Ceiling Rate**<br>(上限速率) | 该 Class 允许借用带宽后的**最大速率**。仅当父类有空闲带宽时，速率可从 `rate` 提升至 `ceil`。 | **硬限速**：设为 `ceil = rate`。<br>**弹性共享**：设为物理带宽上限（允许借用空闲资源）。 |
| **burst** | **Burst**<br>(令牌桶容量) | 允许在短时间内**突破 `rate` 限制**的数据量（字节）。决定了速率的平滑程度。 | **交互类**：适当调大（如 20KB-50KB），允许瞬间响应。<br>**大流量**：默认即可，过大会导致流量整形失效。 |
| **cburst** | **Ceiling Burst**<br>(上限桶容量) | 允许在短时间内**突破 `ceil` 限制**的数据量。主要受网卡物理特性限制。 | 通常保持默认。如果发现无法达到千兆线速，可适当调大。 |
| **prio** | **Priority**<br>(优先级) | 争抢**空闲带宽**（Ceil - Rate）时的优先权。数值越小，优先级越高（0-7）。 | **SSH/DNS**：设为 **0** 或 **1**（最高）。<br>**P2P/下载**：设为 **5** 或更低（避免抢占关键流量）。 |
| **quantum** | **Quantum**<br>(配额) | 在 DRR 调度算法中，每一轮服务允许发送的字节数。 | **一般勿动**。HTB 会根据 `rate` 自动计算。手动调整仅用于解决极低速率下的精度问题。 |
| **r2q** | **Rate to Quantum**<br>(转换系数) | 全局参数（Root）。用于根据 `rate` 自动计算 `quantum` 的系数（Quantum = Rate / r2q）。 | **默认 10**。只有在系统日志出现 "quantum is too small" 警告时才需修改。 |
| **default** | **Default Class**<br>(默认分类) | 全局参数（Root）。未匹配任何 Filter 规则的流量将进入此 Class。 | **必须配置**。建议指向低优先级的 Class（如 1:10），防止未分类的大流量挤死 SSH。 |

用 `htb` 时将流量划分为不同Class, 用于满足Qos需求
针对 SSH / DNS 这些高交互性协议， 为保证其低延时，需提供高优先级的Qos保障，防止维护期间出现卡顿。
对于业务流量及其他流量放到默认class对其进行流量限制， 防止其占满带宽导致拥堵。

### 3. filter（过滤规则）

| 选型/参数 | 全称 | 技术定义  | 实战建议与避坑  |
| :--- | :--- | :--- | :--- |
| **u32** | **Universal 32-bit Filter** | 位匹配过滤器 | 强烈推荐。独立于防火墙运行，实现流量控制与安全策略完全解耦。 |
| **fw** | **Firewall Mark Filter** | 标记匹配过滤器 | 复杂场景选型。依赖 iptables 打标，需处理防火墙规则持久化问题。 |
| **prio / pref** | **Priority / Preference** | 过滤器的检测顺序 | 管理先行。将 SSH/ICMP 设为 1，业务大流量设为 10 以上。 |
| **protocol** | **Protocol Identifier** | 指定链路层协议类型 | 务必显式指定 `protocol ip`，防止过滤器挂载失败。 |
| **match** | **Match Condition** | 具体的匹配表达式 | 下行限速关键。IFB 模式下通常匹配 `sport` (源端口)。 |
| **flowid** | **Flow Identifier** | 命中规则后的分类目标 | 必须与 `classid` 精确对应，否则流量会进入默认队列。 |
| **mirred** | **Mirror/Redirect Action** | 流量镜像/重定向动作 | 下行限流核心。用于将 ingress 流量导向 ifb 设备。 |
| **0xffff** | **Hex Mask** | 十六进制掩码 | 匹配端口时务必带上，防止匹配范围过大误伤其他业务。 |

Filter 过滤器选型，配置过程中使用过 fw(mark) 模式，但该方案与 ufw/firewalld 存在持久化问题和冲突处理问题，处理相对复杂，最终使用 u32 通过端口协议类型完成流量区分，不再依赖防火墙标识区分。

使用 `htb` 模式，带宽分配依赖以下两个参数：

- **rate**: 保底额度，这是分配该类别的基础保障带宽。即使系统极其拥堵时也会保证这部分流量分配，设置合理值是保证其不掉线关键。
- **ceil**: 封顶额度，由于 `htb` 存在借用机制（Borrowing），在带宽空闲情况下，该类型可以申请系统剩余资源来提升速率，但是最终速率受到 `ceil` 的限制。

### 4. 配置示例

```bash
tc qdisc add dev "$IFACE" root handle 1: htb default 10

# 高优先级类（SSH + DNS）
tc class add dev "$IFACE" parent 1: classid 1:1 htb rate 2mbit ceil 100mbit prio 0
tc qdisc add dev "$IFACE" parent 1:1 handle 11: fq_codel

# 默认限速类（其余流量）
tc class add dev "$IFACE" parent 1: classid 1:10 htb rate "$TX_LIMIT" ceil "$TX_CEIL" prio 5
tc qdisc add dev "$IFACE" parent 1:10 handle 110: fq_codel

# IPv4 规则
tc filter add dev "$IFACE" protocol ip parent 1: prio 1 u32 match ip dport "$SSH_PORT" 0xffff flowid 1:1
tc filter add dev "$IFACE" protocol ip parent 1: prio 1 u32 match ip sport "$SSH_PORT" 0xffff flowid 1:1
tc filter add dev "$IFACE" protocol ip parent 1: prio 2 u32 match ip dport 53 0xffff flowid 1:1
tc filter add dev "$IFACE" protocol ip parent 1: prio 2 u32 match ip sport 53 0xffff flowid 1:1

# IPv6 规则（系统不支持时忽略）
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 1 u32 match ip6 dport "$SSH_PORT" 0xffff flowid 1:1 2>/dev/null || true
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 1 u32 match ip6 sport "$SSH_PORT" 0xffff flowid 1:1 2>/dev/null || true
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 2 u32 match ip6 dport 53 0xffff flowid 1:1 2>/dev/null || true
tc filter add dev "$IFACE" protocol ipv6 parent 1: prio 2 u32 match ip6 sport 53 0xffff flowid 1:1 2>/dev/null || true
```

{{< mermaid >}}
graph TD
    classDef flow fill:#e1f5fe,stroke:#01579b
    classDef limit fill:#ffebee,stroke:#c62828

    A[🌊 实时流量输入] --> B{是否超过 rate?}
    B -- 否 --> C[✅ 保证通道: 直接发送]:::flow
    B -- 是 --> D{是否有闲置带宽?}
    
    D -- 无 --> E[⏳ 降速排队/丢包]:::limit
    D -- 有 --> F{是否达到 ceil?}
    
    F -- 否 --> G[🚀 借用模式: 加速发送]:::flow
    F -- 是 --> E
{{< /mermaid >}}
---


## 四、下行限速（IFB）说明

Linux 原生只方便整形 egress（上行）。下行要用 IFB：

1. 把网卡 ingress 重定向到 `ifb0`
2. 在 `ifb0` 上再做 `htb` 分类

这个方案可行，但更容易误伤稳定性。如果跑其他业务涉及端口需要进行区分，否则会被业务流量占满带宽后造成丢包。

```bash
# 加载 ifb 模块 
modprobe ifb numifbs=1 2>/dev/null || true
ip link add "$IFB_DEVICE" type ifb 2>/dev/null || true
ip link set "$IFB_DEVICE" up

tc qdisc add dev "$IFACE" handle ffff: ingress
tc filter add dev "$IFACE" parent ffff: protocol ip u32 match u32 0 0 \
  action mirred egress redirect dev "$IFB_DEVICE"

tc qdisc add dev "$IFB_DEVICE" root handle 2: htb default 20

# 高优先级类（SSH + DNS）
tc class add dev "$IFB_DEVICE" parent 2: classid 2:1 htb rate 2mbit ceil 100mbit prio 0
tc qdisc add dev "$IFB_DEVICE" parent 2:1 handle 21: fq_codel

# 默认限速类
tc class add dev "$IFB_DEVICE" parent 2: classid 2:20 htb rate "$RX_LIMIT" ceil "$RX_CEIL" prio 5
tc qdisc add dev "$IFB_DEVICE" parent 2:20 handle 220: fq_codel

# IFB 方向反转，sport/dport 都加
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 1 u32 match ip sport "$SSH_PORT" 0xffff flowid 2:1
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 1 u32 match ip dport "$SSH_PORT" 0xffff flowid 2:1
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 2 u32 match ip sport 53 0xffff flowid 2:1
tc filter add dev "$IFB_DEVICE" protocol ip parent 2: prio 2 u32 match ip dport 53 0xffff flowid 2:1
```
---


## 五、常用 `tc` 命令速查

### 1. 查看规则

```bash
sudo tc qdisc show dev eth0
sudo tc class show dev eth0
sudo tc filter show dev eth0
```

### 2. 查看统计（是否真的在限速）

```bash
sudo tc -s class show dev eth0
sudo tc -s class show dev ifb0
```

看这几个字段：

- `Sent`：发送字节
- `overlimits`：超限被整形次数
- `dropped`：丢包
- `backlog` / `ldelay`：排队与延迟

### 3. 清理规则（紧急恢复）

```bash
sudo tc qdisc del dev eth0 root 2>/dev/null || true
sudo tc qdisc del dev eth0 ingress 2>/dev/null || true
sudo ip link del ifb0 2>/dev/null || true
```

---


## 八、流量监控

目标：在达到月度阈值时自动停容器，避免超额。

### 1. 工作原理

1. 读取网卡累计字节：
   - `/sys/class/net/<iface>/statistics/rx_bytes`
   - `/sys/class/net/<iface>/statistics/tx_bytes`
2. 以“基线值”计算本周期增量
3. 增量超限后执行：`docker stop <container>`
4. 记录日志并发 Telegram 告警


## 九、Telegram 机器人注册与告警配置

### 1. 创建机器人

1. 在 Telegram 搜索 `@BotFather`
2. 发送 `/newbot`
3. 按提示设置机器人名称和用户名
4. BotFather 返回 `BOT_TOKEN`

`BOT_TOKEN` 形如：

```text
123456789:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

### 2. 获取 Chat ID

1. 在 Telegram 搜索 `@IDBot`
2. 发送 `/start`
3. 返回的 Id 就是你的 `TELEGRAM_CHAT_ID`

### 3. 填入脚本

```bash
TELEGRAM_BOT_TOKEN="你的BOT_TOKEN"
TELEGRAM_CHAT_ID="你的CHAT_ID"
```

### 4. 发送测试消息

```bash
curl -s -X POST "https://api.telegram.org/bot<BOT_TOKEN>/sendMessage" \
  -d chat_id="<CHAT_ID>" \
  -d text="Streamr monitor test"
```

---

## 十、其他

编写脚本后，防止重启后失效，可以配置 systemd 默认开机运行。

---

*最后更新: 2026-02-18*


