Skip to content

Repository files navigation

steervtap

简体中文 | English

在 Linux 内核中按规则选择流量,再以对称 IP 地址对 hash 分配到独立虚拟 TAP 接口。 用户态程序通过 TAP FD 读写完整以太网帧,虚拟接口支持 tcpdump 和 ingress 处理。

项目原名 ditchvtap。当前实现是实验性的 out-of-tree 内核模块,已在 Linux 6.1 / 6.8、x86_64 上完成功能验证。

steervtap 使用 macvtap 同类的内核共享 CONFIG_TAP 层。它不是 /dev/net/tun 创建的标准 TAP:read(FD) 接收下层网卡分流的报文,write(FD) 从下层网卡发送。命中的原包不再交给宿主机 TCP/IP。

目录

功能与适用范围

能力 当前实现
绑定下层网卡 一个 Ethernet 下层设备对应一个共享组,最多 64 个 TAP 成员
流量选择 最多 64 条有序规则,支持 pass/hash,默认放行
匹配字段 IPv4/IPv6 CIDR、IP 协议、源/目标端口范围、最外层 VLAN、双向匹配
分流 对称 IP 地址对软件 hash,256 桶 RSS 间接表
配置更新 rtnetlink 下发;规则与整个 RSS 表通过 RCU 一起原子替换
用户态接收 每个接口提供独立 TAP 字符设备,读取完整以太网帧
用户态发送 写入同一个 TAP FD,经绑定的下层网卡发出
网卡接收路径 支持 AF_PACKET 抓包和 ingress hooks,已验证 tc ingress 丢包
统计 ip -s link、ethtool -S,按接口统计收发和驱动丢包

适合自定义 UDP 接入、用户态协议栈、报文处理与网络实验。示例 tap-worker 只负责读包、计数;认证、业务协议、路由、代理和连接状态由上层程序实现。

当前不提供连接状态识别、五元组 hash、无损迁移、被动镜像或完整交换机能力。Suricata 等程序可以沿 AF_PACKET 路径接入,但仓库尚未完成 Suricata、gVisor、tun2socks 集成验证。

架构与原理

控制面和接收方向

                         用户态配置工具
                  steervtapctl.py replace/show/clear
                                │
                         NETLINK_ROUTE
                                │
                    eth0 对应的共享配置组
                    ┌──────────────────────┐
                    │ 有序规则 + RSS 256 桶 │
                    │ 一个 RCU 快照发布    │
                    └───────────┬──────────┘
                                │
外部网络 → eth0 原有驱动 → Linux 接收流程
                                │
                         steer_receive()
                                │
                           规则匹配
                  ┌─────────────┴─────────────┐
                  │                           │
             pass / 未命中                 命中 hash
                  │                           │
          宿主机正常接收路径           对称 IP 对 hash
          (SSH、ARP、socket 等)              │
                                         RSS 桶查表
                              ┌───────────────┴──────────────┐
                              ▼                              ▼
                         steervtap0                     steervtap1
                              │                              │
                       标准网卡接收流程               标准网卡接收流程
                              │                              │
                       AF_PACKET / tcpdump            AF_PACKET / tcpdump
                              │                              │
                       ingress hooks                  ingress hooks
                              │                              │
                       共享 TAP handler               共享 TAP handler
                              │                              │
                          TAP 队列                       TAP 队列
                              │ read(FD)                     │ read(FD)
                              ▼                              ▼
                           worker 0                       worker 1

首个成员创建时,驱动在下层网卡注册 RX handler。选中的单播修改 skb->dev,返回 RX_HANDLER_ANOTHER,由网络核心按目标虚拟网卡重新走接收流程。共享 TAP handler 在抓包和 ingress 之后接管原包,返回 RX_HANDLER_CONSUMED,不再继续上交宿主机 ARP/IP/TCP/UDP。

命中的广播/组播是另一条路径:复制到所有 UP 成员,并通过 netif_rx() 进入各成员的接收流程;即使成员不在 RSS 表里,也会收到副本。命中的 PAUSE 帧直接消费,不做 fanout。

发送方向

worker 0:write(tap0_fd, Ethernet_frame) ──┐
                                         ├─→ steervtap ndo_start_xmit
worker 1:write(tap1_fd, Ethernet_frame) ──┘              │
                                                 steer_xmit()
                                                       │
                                                 skb->dev = eth0
                                                       │
                                               dev_queue_xmit()
                                                       │
                                             eth0 发送路径 / 驱动
                                                       │
                                                    外部网络

TX 不查入站规则或 RSS 表。驱动不做地址转换、路由、MAC 改写或 TCP 处理,调用者必须提供正确的完整以太网帧。成功 write 或 TX 计数增加表示发送路径接受了报文,不保证对端已经收到。

与标准 TAP 的区别

操作 标准 /dev/net/tun TAP steervtap
用户程序 read(FD) 读取内核向 TAP 网卡发送的帧 读取下层网卡分流来的帧
用户程序 write(FD) 把帧注入 TAP RX,进入宿主机接收流程 把帧交给下层网卡 TX
是否直接绑定 eth0 不会自动绑定 创建时指定 link eth0
配置 IP 可用于宿主机协议栈通信 可以配置,但命中帧仍由 TAP handler 消费,不能据此认为普通 socket 可接收

steervtap 的 TAP 字符设备队列与 AF_PACKET 抓包入口是两套接口。AF_PACKET 可观察副本,但不会排空 TAP 队列,也不会自动转发原包。

编译与运行要求

  • Linux,匹配当前目标内核的 headers/build tree,C 编译器、make、kmod、iproute2、Python 3。
  • 内核启用 CONFIG_TAP;运行隔离测试还需要 veth、network namespace、IPv4/IPv6 和 tc ingress 相关支持。
  • 模块加载、设备创建和配置通常需要 root;worker 需要访问对应字符设备。
  • .ko 与目标内核版本、配置和 CPU 架构相关。升级内核或更换架构通常需要重编译;当前没有 DKMS 自动构建。
  • macOS/Windows 不能直接运行此模块,需要 Linux 虚拟机。Python 控制工具也依赖 Linux netlink。

Ubuntu/Debian 示例:

sudo apt-get install build-essential linux-headers-$(uname -r) kmod iproute2 python3 ethtool tcpdump

git clone https://github.com/singchia/steervtap.git
cd steervtap
make
make tools

指定其他构建目录:

make KDIR=/path/to/target/kernel/build

仅更改 KDIR 不会自动完成跨 CPU 架构编译,仍需正确的内核构建参数和工具链。不要把在 Docker 内编译的模块直接加载到不同版本的宿主机内核。

快速开始

下面使用 eth1,创建两个 TAP,捕获来自 192.0.2.0/24、目标 UDP 9000 的报文。请将网卡和源网段替换成实际值。

共享业务网卡上应精确匹配目标流量。全量接管可能包含 SSH;仅做观察也不能把 hash 当成镜像。

1. 加载模块并创建接口

sudo modprobe tap
sudo insmod steervtap.ko

sudo ip link add link eth1 name steervtap0 type steervtap
sudo ip link add link eth1 name steervtap1 type steervtap
sudo ip link set eth1 up
sudo ip link set steervtap0 up
sudo ip link set steervtap1 up

新组的规则为空,报文默认放行。创建命令不支持 src、dport 等自定义 iproute2 参数,也不支持在创建时直接下发组配置。

2. 先启动 reader

分别在两个终端运行:

sudo ./tools/tap-worker steervtap0
sudo ./tools/tap-worker steervtap1

程序启动时显示设备路径,随后持续读取;按 Ctrl+C 退出时显示 packets、bytes。它不会逐包打印,也不会默认回包。

3. 下发规则和 RSS 表

仓库提供 examples/udp-service.json:

{
  "rules": [
    {"action": "hash", "src": "192.0.2.0/24", "protocol": "udp", "dst_port": 9000}
  ],
  "rss": ["steervtap0", "steervtap1"]
}

修改源网段后执行:

sudo python3 tools/steervtapctl.py replace steervtap0 examples/udp-service.json
python3 tools/steervtapctl.py show steervtap1

配置属于整个下层网卡组,因此可以通过任意成员更新或读取。RSS 表按名字解析到本组成员,两个 TAP 交替占据 256 个桶。

4. 发流量并观察

从规则允许的外部源发送 UDP 到服务器可达地址的 9000 端口。抓包应在发流量前启动:

sudo tcpdump -ni steervtap0 'udp dst port 9000'
# 另一个终端可抓 steervtap1
ip -s link show steervtap0
ip -s link show steervtap1
sudo ethtool -S steervtap0

同一源/目标 IP 对的所有端口共享一个 hash,少量同源同目标测试包通常只进入一个 TAP,这是预期行为。

5. 恢复放行并清理

先清规则,再停止 worker,避免命中后没有 reader:

sudo python3 tools/steervtapctl.py clear steervtap0
# 在两个 worker 终端按 Ctrl+C
sudo ip link del steervtap0
sudo ip link del steervtap1
sudo rmmod steervtap

这些配置仅针对当前运行时,仓库没有默认安装开机加载服务。

规则与 RSS 配置

可组合多个字段,并用靠前的 pass 规则排除流量:

{
  "rules": [
    {"action": "pass", "protocol": "tcp", "dst_port": 22},
    {"action": "hash", "src": "192.0.2.0/24", "protocol": "udp",
     "dst_port": "9000-9099", "bidirectional": true}
  ],
  "rss": ["steervtap0", "steervtap1", "steervtap1"]
}
  • 第一条匹配规则决定结果,未匹配默认 pass;省略字段表示该字段不限制。
  • 支持 family、src、dst、protocol、src_port、dst_port、vlan、bidirectional。
  • 端口匹配要求显式指定 TCP 或 UDP。双向匹配同时交换源/目标地址和端口。
  • RSS 输入为 1–256 个本组接口名字或 ifindex,工具循环填充 256 桶;重复成员可表达近似权重。
  • 所有 hash 规则共享一张表,不能给每条规则指定不同 TAP 组。
  • 源/目标地址排序后参与软件 jhash,VLAN 信息参与 hash;非 IP 使用对称 MAC 地址对。
  • 随机种子在组存在期间固定;硬件 RSS hash 不参与。重建组、主动更换表都可能改变归属。
  • 配置先整体校验再通过 RCU 发布,失败保留旧配置;每个包的规则判断和选桶使用同一个快照。
  • 更新 ACK 不是队列排空屏障,在途包仍可能按旧配置完成处理。

完整字段、解析边界、4360 字节版本化 netlink 二进制布局见 配置协议。

用户态程序接入

最小 C 示例是 tools/tap-worker.c。接入步骤:

  1. 从 /sys/class/net/INTERFACE/steervtap*/dev 读取字符设备 major/minor。
  2. 打开 /dev/steervtap<minor>,使用非阻塞方式并以 poll 等待数据。
  3. 用 TUNSETIFF 协商 IFF_TAP | IFF_NO_PI,禁用默认 virtio 网络头,使用原始以太网帧。
  4. read 获取帧,业务程序处理后可 write 完整帧回下层网卡。
  5. 正常关闭时停止轮询并关闭 FD,再删除网卡。

/dev/steervtap1 的后缀是 minor,不是 ifindex,也不一定对应 steervtap1。devtmpfs/udev 通常负责节点创建;root 测试程序会按 sysfs 信息创建临时节点。

默认每个接口使用一个 reader。额外打开可能触发共享 TAP 的多队列选择,不应把它当作广播订阅;当前验证范围是每接口一个队列。Linux 6.1 共享 TAP 在设备删除时不保证唤醒已阻塞的 read,因此示例使用非阻塞 FD 和可取消轮询。

tap-worker --echo 会将收到的帧原样写出,并不交换 MAC、IP 或端口,不是一个正确的 TCP/UDP echo 服务。只应在隔离链路测试中使用。

观察与排障

现象 含义 / 检查方向
接口 UP 但没流量 检查组配置是否为空、规则和下层到包情况、RSS 是否选中了另一个成员
所有包落一个 TAP IP 对相同通常如此;不同端口不改变 hash
tcpdump 有包但 reader 没包 抓包在 ingress/TAP handler 前,检查 tc、reader、队列和丢包计数
宿主机 UDP socket 等不到包 命中的原包由 TAP 接管,不再交宿主机 socket
rx_no_reader 增长 对应 TAP 没有附着 reader;AF_PACKET 程序不能替代 FD reader
rx_target_down 增长 RSS 目标 DOWN,但还有其他组成员 UP,组仍在接管流量
rx_tap_dropped 增长 TAP 队列/转换或 multicast 接收 backlog 丢包,不全是队列满
rx_allocation_dropped 增长 skb 分配或克隆失败
TX 计数为 0 只有写 TAP FD 或向虚拟接口发送才走其 TX;宿主机直接从 eth0 发包不计入
加载模块失败 检查目标内核版本、架构、CONFIG_TAP、模块签名要求和内核日志

RX packets/bytes 在报文转入虚拟接口时计数,先于 ingress 和用户态读取。rx_dropped 汇总驱动计数,tc 丢包应通过 tc -s filter show dev steervtap0 ingress 查看。TX 计数表示下层发送提交结果,不是远端确认。

生命周期与限制

  • 新建组为空规则;添加成员不改变已发布的 RSS 表。
  • 删除任何成员都清空整个组的配置,恢复默认放行,随后需显式重新配置。
  • 目标 DOWN、没有 reader、队列满时丢包,不自动换目标或回退宿主机;全部成员 DOWN 时整个组放行。
  • 任何成员 UP 时,下层保持一个混杂模式引用,包括空规则状态;最后一个成员 DOWN 时释放。
  • MTU 不能超过下层设备;下层 MTU 减小到成员 MTU 以下会被拒绝。
  • 不支持嵌套 steervtap、跨 namespace 创建或接口迁移;下层注销时联动删除成员。
  • 所有分片,包括首片和 IPv6 atomic fragment,都不匹配端口规则;地址规则可以捕获分片。
  • 最多解析四层 inline VLAN、八个受支持的 IPv6 扩展头;畸形/截断报文、超出解析范围和 IPv6 zero-payload/jumbogram 放行。该分类器不能当成完整防火墙校验器。
  • 虚拟 MAC 不用于选择 RX 目标,也不改写用户发送的帧。ARP/NDP、回程路由和二层可达性需要业务方案明确处理。
  • 没有主动声明 checksum/GSO 加速能力,没有硬件 RSS、零拷贝或吞吐量保证。

测试

模块已加载时运行:

sudo make test

测试使用临时 namespace 和 veth,验证规则、原子配置、IPv4/IPv6/VLAN、双向及分片归属、广播、AF_PACKET、tc ingress、所有 TAP 的 TX、worker、丢包、失败回滚和并发删除。

对当前内核执行构建及三轮加载/测试/卸载:

sudo make test-native

native runner 拒绝替换已加载的 steervtap 模块;请先清理自己的试用接口和 worker。测试记录保存在 gitignored 的 test-results/。

已有验证:

环境 结果
Linux 6.1.0-50-amd64,QEMU TCG 三轮完整回归通过,包含 AF_PACKET 与 tc ingress
Linux 6.8.0-101-generic,Ubuntu x86_64 三轮原生完整回归通过,包含 AF_PACKET 与 tc ingress
云服务器 eth0,virtio_net 精确 DNS 源 IP/端口分流、TAP 外发及外部回包、虚拟接口 tcpdump 实测通过

这些是功能验证,不等于 KASAN/lockdep、真实物理网卡 offload 或吞吐量测试。QEMU 复现方法及记录见 测试说明。tests/live_eth0.py 是显式运行的 eth0 实测脚本,不由 make test 自动触发。

源码导航

steervtap_main.c          网卡/字符设备生命周期、RX/TX、hash、统计、RCU 配置发布
steervtap_config.c        报文解析、规则匹配、netlink 数据校验及序列化
steervtap_config.h        配置结构、规则字段和 ABI 常量
Makefile                 内核模块、C worker、测试入口
tools/steervtapctl.py     Python 标准库实现的 rtnetlink 控制工具
tools/tap-worker.c        原始以太网 FD 消费/发送示例
examples/udp-service.json 示例规则与 RSS 配置
tests/                   namespace、原生内核、QEMU 和显式 eth0 测试
docs/                    配置协议和测试说明

后续方向与许可

可继续扩展:五元组与分片关联、连接状态分流、多 RSS 组、镜像模式、流状态迁移、per-CPU 统计、DKMS、用户态协议栈集成。上述项目当前未实现。

内核 C 源码、C worker 和控制工具声明 GPL-2.0-or-later。仓库根目录保留了历史版本的 Apache-2.0 LICENSE,与当前源文件声明存在历史不一致;本次名称和文档更新没有重新授权已有代码。正式发布前需要统一许可文件及声明。

About

Linux kernel flow steering to virtual TAP interfaces with symmetric IP-pair hashing and atomic rtnetlink configuration.

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages