Skip to content

feat: 流量统计支持 --iface 指定网卡,隧道与叠加设备按内核事实识别 - #6

Merged
stqfdyr merged 6 commits into
mainfrom
feat/iface
Sep 19, 2026
Merged

stqfdyr merged 6 commits into
mainfrom
feat/iface

Conversation

@stqfdyr

@stqfdyr stqfdyr commented Sep 18, 2026 •

Copy link
Copy Markdown
Collaborator

背景

#3:PVE、iStoreOS 这类有转发或虚拟网卡的机器,流量被重复统计。原因有两类:

  1. 名单漏网:PVE 给勾了防火墙的 guest 网卡建 fwbr / fwpr / fwln 三个设备,名单只有前两个,guest 的流量在物理网卡和 fwln 上各记一次。OpenWrt 的 pppoe-wan 叠在 WAN 口上,SQM 的 ifb* 是入向镜像,也各多记一次。
  2. 转发:软路由(或桥接了软路由的宿主机)上,同一个包先后经过 WAN、LAN 两块真网卡。哪块面向运营商只有使用者知道,自动规则回答不了。

改动

默认规则:按内核事实识别,改过名也认得出

  • 名单补 fwln、ifb、gretap、erspan、lxc 与 cilium(Cilium 的 pod veth 与宿主设备);is_stacked 补 pppoe-(只影响流量,pppoe-wan 上的公网地址照常上报)。
  • 新增 counted_elsewhere(),读 /sys/class/net/<name>/:
    • DEVTYPE=bridge|bond → 叠加设备,不管有没有端口
    • 有 lower_* 链接 → 叠加设备:VLAN、macvlan、DSA 交换机端口
    • 无 device,且链路类型为三层隧道(none / ipip / ip6tnl / sit / gre / ip6gre)或 DEVTYPE=vxlan|geneve → 隧道
    • 不会误排除:raw-IP 模式的 LTE 模块(type none 但有 device)、OpenVZ 的 venet0(type void)、移进容器的 macvlan(lower_* 链接只在同一 netns 内存在)
    • 无 device,且是网桥的端口(brport/)→ 虚拟机的 tap(如 libvirt 的 vnet0)、容器的 veth,名字随意。虚拟机发出的流量在物理口上已经记过一次;物理口挂在网桥下时有 device,照计。VRF、Open vSwitch 下的设备有 master 但不是网桥端口,照计,它们可能就是这台机器的上联
  • 仍然不采用「没有 device 就排除」:LXC 里 guest 唯一的 veth 也没有 device,排除它会让整台机器的流量归零。也不按 tun_flags 排除所有 tap:rootless 容器的 pasta 网络里,唯一的网卡就是一块以宿主网卡命名、不挂网桥的 tap。GRE tap 与不挂网桥的 tap、veth 继续靠名单,已在 SKIP_IFACES 的 ponytail: 标记里写明上限。

boot_id:所计网卡集合一变就重新对基线

boot_id 改为「内核 boot_id + / + 所计网卡集合的摘要」(名字排序后做 FNV-1a)。hub 只比较是否相等,所以同一次开机里集合的任何变化都会让 hub 重新对基线,包括改 --iface、网卡被重新归类、网卡改名进出名单。代价是每次变化丢一个采样间隔,新建的网卡(计数从 0 开始,原本能算对)也一样。计入的网卡频繁增减的机器(名字不在名单上的 pod、VPN 服务器上的 pppN)每次都丢一个间隔,已在 epoch 的 ponytail: 标记里写明;根治办法是逐网卡上报、由 hub 逐个累加。不在 agent 里记偏移量:偏移量随进程消失,agent 停机期间的流量也会跟着丢。hub 不用改代码。

速率改为逐网卡与自己的上一次读数相减,只算两次采样里都在的网卡:按总和相减,新加入网卡的全部历史读数会被当成一秒内的流量写进历史图。新加入的网卡下一次采样才计入,其余网卡照常。速率只存在内存里,不涉及无状态的约定。

升级到这个版本时 boot_id 的格式会变,每个节点会重新对一次基线。

--iface / MONITOR_IFACE

  • 写全名(--iface eth1,pppoe-wan):只统计列出的网卡,内置规则全部不再生效,因此能选 vmbr0、pppoe-wan。
  • -name:从本来会计入的网卡里去掉,排除优先于列出。网卡名各不相同的一组机器可以共用一条命令。
  • 以下写法直接报错退出,避免静默地全部匹配或匹配不到本意:名字带空白、单独的 -、-- 开头。
  • 只认完整网卡名,不支持通配。* 不单独拒绝:它和写错的名字一样匹配不到网卡,启动日志的 counting traffic on: 能看出来;install.sh 与面板的字符白名单本来就不收 *。
  • 新增上报字段 Metrics.iface,原样回报当前的 --iface,供面板显示和预填。hub 的公开页用白名单输出指标,这个字段不会外泄。
  • 启动时打印一行 counting traffic on: ...,列出此刻计入的网卡。
  • 取地址不受 --iface 影响。

另外修正了 --help 里多行说明的续行缩进:原先被 Rust 字符串续行吞掉了,--insecure 一直是错位的。

验证

  • cargo test 26 个全部通过(新增 3 个),clippy 无告警。
  • 变异检查:逐条改坏每一条规则,每种改坏都有测试变红:
    • 内核事实判定:网桥/bond 的 DEVTYPE、lower_ 判定及其与 device 的先后、device 豁免、隧道 DEVTYPE、隧道类型集合
    • 网桥端口规则:去掉、改回按 master 判定、挪到硬件检查之前
    • 名单:新增的每一项
    • --iface:全名覆盖内置规则、排除优先、各项参数校验
    • 纪元:摘要必须随集合变化,且与名字顺序无关
    • 速率:缺上次读数时按 0 算
  • 真实内核的临时 netns(重新挂载 sysfs)里:挂在网桥 lan 下的 tap vm1 与 veth ct1 不计;不挂网桥的 veth eth0、以宿主网卡命名的 tap enp1s0、VRF 成员 up1 照计;网桥、VRF 设备与 tun 不计。
  • 真实内核(6.1,临时 netns):建了 WireGuard(命名为 nebula1)、sit(he-ipv6)、gre、vxlan、geneve、tun/tap、PVE 防火墙 veth 对、自命名网桥 lan、VLAN、macvlan、pppoe-wan、ifb。默认规则下只计入以太网口。
  • 临时 hub 端到端,每轮都在同一次开机内:
场景 修复前(hub 多记) 修复后
LXD 式网桥 lxdbr0 失去最后一个端口(容器停掉) 6,116,566 字节,即该网桥的整机读数 0
一块已有 3 MB 读数的网卡改名后进入求和 3,072,480 字节 0(重新对基线)
从只统计一个 veth 切回默认规则 rx 10.5 GB / tx 13.0 GB(去掉纪元的对照构建) 29.6 KB(4 秒的真实流量)
  • --iface 的校验:同一批 18 种写法,本 PR 的 agent 与 install.sh(dash 与 busybox)逐一对照。-、eth0,-、--eth0、eth0 eth1 两边都拒绝;eth*、*、-*、e*h0 与 shell 元字符只有 install.sh 拒绝,它的字符白名单只会更严。面板的正则由它的单元测试覆盖。

安装脚本与面板的 --iface 见 monitor-probe/monitor#27。发版顺序:本 PR 先于含 monitor#27 的 hub,hub 转发的是 agent 的最新发布,早于本 PR 的 agent 会静默忽略 MONITOR_IFACE。

Closes #3

- 名单补 fwln(PVE 防火墙 veth 的另一端)、ifb、gretap、erspan;pppoe- 归入叠加设备
- 新增 counted_elsewhere():有 lower_* 链接的是叠加设备,无 device 且为三层隧道类型或
  DEVTYPE=vxlan/geneve 的是隧道,改过名也认得出
- --iface / MONITOR_IFACE:列出的网卡是唯一答案,内置规则不再生效;-name 从本来会计入的里
  去掉;末尾 * 匹配前缀
- 设了 --iface 时拼进 boot_id,改设置后 hub 重新对基线,不把两组读数之差记成流量
- 启动打印计入的网卡;修正 --help 续行缩进
- boot_id 后接所计网卡集合的摘要(原为 --iface 文本):LXD 网桥失去最后一个端口、网卡改名
  进出名单时,hub 不再把它的整机读数记成流量
- 网桥、bond 按 uevent 的 DEVTYPE 判定,与有无端口无关
- 新增 Metrics.iface,供面板显示当前设置,不再从 boot_id 里解析
- --iface 的前缀只在默认会计入的网卡里挑,不带上 VLAN 子接口;拒绝单独的 *、-* 与 -- 开头
- 名单补 lxc、cilium(Cilium 的 pod veth 与宿主设备);测试的临时目录开跑前先清理
- 没有硬件、又挂在网桥(或其它 master)下的设备不计:libvirt 的 vnetN 等虚拟机 tap、自定义名字的
  容器 veth。虚拟机发出的流量在物理口上已经记过一次。只凭 tun_flags 判 tap 不可取:rootless 容器的
  pasta 网络里,唯一的网卡就是一块不挂网桥的 tap。
- 集合变化时 net_rx / net_tx 按新旧两组读数之差计算,会把新加入网卡的全部历史读数当成一秒内的流量,
  写进历史图。现在纪元不同就不算速率,与 hub 重新对基线一致。
- epoch 的 ponytail 标注:集合每变一次,hub 丢一个间隔的流量,以及消除它的办法。
- 上一版按 master 判定,范围比说明宽:VRF、Open vSwitch 下的设备也有 master,可能正是这台机器的
  上联。改按 brport/ 判定,只认网桥端口,与 README 的「挂在网桥上」一致。
- 所计网卡集合变化那一次不再整体把速率记成 0:速率只存在内存里,逐网卡与自己的上一次读数相减,
  只算两次采样里都在的网卡。新加入的网卡下一次采样才计入,计数器重新开始的网卡这一次记 0,
  其余网卡照常。总流量仍按 boot_id 由 hub 重新对基线,不变。
前缀规则是这套语法里最绕的一条(只在默认会计入的网卡里挑),也让 agent、install.sh、面板三处的
校验最复杂,而有了 `-` 排除之后,基本没有非它不可的场景。现在只有两种写法:列出要统计的网卡,
或在名字前加 `-` 排除。写了 `*` 直接报错退出,不会静默地什么都匹配不到。
通配匹配已经去掉,`*` 没有理由比写错的网卡名多一条规则:两者都匹配不到网卡,启动日志的
`counting traffic on:` 和面板的「当前」都能看出来。install.sh 与面板的字符白名单照样不收 `*`。
@stqfdyr
stqfdyr merged commit 138db5c into main Sep 19, 2026
1 check passed
@stqfdyr
stqfdyr deleted the feat/iface branch September 19, 2026 13:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[功能请求] 支持只监听指定网卡流量

1 participant