F-Stack “No probed ethernet devices” 问题分析与解决指南

整理自 GitHub F-Stack/f-stack 仓库 issue 及 DPDK 官方文档

涉及 issue:#1035、#837、#693、#663、#583、#581、#531、#386、#379 等

最后更新:2026-03-09


一、问题概述

在运行 F-Stack(helloworld、nginx、自定义应用等)时,常见以下报错:

EAL: Error - exiting with code: 1

Cause: No probed ethernet devices

或:

ff_init failed with error "No probed ethernet devices"

该错误的根本原因是 DPDK 无法检测到可用的以太网设备,即 rte_eth_dev_count_avail() 返回 0。

这是 F-Stack 社区中出现频率最高的问题之一,历史上超过 10 个相关 issue,跨越 2019~2025 年,具有高度重复性。


二、根因分类

“No probed ethernet devices” 错误通常由以下几类原因导致,需按顺序逐一排查。


原因 1:网卡未绑定到 DPDK 兼容驱动

最常见原因。 DPDK 需要网卡从内核驱动(如 ixgbevirtio)解绑,并重新绑定到 DPDK 专用驱动(igb_uiovfio-pci)。

诊断命令:

cd /data/f-stack/dpdk

python3 usertools/dpdk-devbind.py --status

如果网卡显示在 Network devices using kernel driver 而不是 Network devices using DPDK-compatible driver,则需要绑定驱动。

解决方案:使用 igb_uio 绑定

# 1. 加载 igb_uio 模块

modprobe uio

insmod /data/f-stack/dpdk/build/kernel/linux/igb_uio/igb_uio.ko

2. 查看网卡 PCI 地址

python3 usertools/dpdk-devbind.py --status

3. 绑定网卡(替换 0000:00:03.0 为你的 PCI 地址)

python3 usertools/dpdk-devbind.py --bind=igb_uio 0000:00:03.0

4. 验证绑定成功

python3 usertools/dpdk-devbind.py --status

应看到网卡出现在 "Network devices using DPDK-compatible driver" 下

解决方案:使用 vfio-pci 绑定(推荐用于现代系统/虚拟机)

# 1. 加载 vfio-pci 模块

modprobe vfio-pci

2. 绑定网卡

python3 usertools/dpdk-devbind.py --bind=vfio-pci 0000:00:03.0

3. 如需在非 IOMMU 环境使用

echo 1 > /sys/module/vfio/parameters/enable_unsafe_noiommu_mode


原因 2:config.ini 中 port_list 未正确配置

config.ini[dpdk] 段中必须配置 port_list,且其编号要与实际绑定的 DPDK 端口对应。

错误示例:

[dpdk]

port_list=0

配置了 port 0,但网卡未绑定或 PCI 白名单不匹配。

排查方法:

# 查看当前绑定的 DPDK 端口

python3 usertools/dpdk-devbind.py --status

运行 testpmd 验证 DPDK 可以看到设备

dpdk-testpmd -l 0-1 -n 4 -- -i

解决方案:

确认 pci_whitelist(旧版)或 allow(新版)参数与实际网卡 PCI 地址匹配:

[dpdk]

lcore_mask=1

channel=4

port_list=0

旧版 DPDK

pci_whitelist=0000:00:03.0

新版 DPDK (21.11+)

allow=0000:00:03.0


原因 3:Makefile 链接参数缺少 --whole-archive

在自定义应用中链接 F-Stack 时,如果没有使用 --whole-archive 标志,DPDK 的 NIC PMD(Poll Mode Driver)驱动库不会被完整链接进来,导致运行时找不到网卡。

错误写法(不完整):

LIBS += -lfstack -ldpdk

正确写法:

DPDK 19.11 及以下:

LIBS += -L${FF_PATH}/lib -Wl,--whole-archive,-lfstack,--no-whole-archive

LIBS += -L${FF_DPDK}/lib -Wl,--whole-archive,-ldpdk,--no-whole-archive

DPDK 20.11 / 21.11 及以上(使用 pkg-config):

CFLAGS  += $(shell pkg-config --cflags libdpdk)

LDFLAGS += $(shell pkg-config --libs libdpdk)

LDFLAGS += -Wl,--whole-archive,-lfstack,--no-whole-archive

参考 f-stack/example/Makefile 作为标准模板


原因 4:pkg-config 版本过低

pkg-config 版本低于 0.28 时,无法正确解析 DPDK 的 .pc 文件,导致驱动库链接不完整,运行时检测不到网卡。

诊断命令:

pkg-config --version

解决方案:升级 pkg-config 到 0.28+

cd /data

wget https://pkg-config.freedesktop.org/releases/pkg-config-0.29.2.tar.gz

tar xzvf pkg-config-0.29.2.tar.gz

cd pkg-config-0.29.2

./configure --with-internal-glib

make && make install

mv /usr/bin/pkg-config /usr/bin/pkg-config.bak

ln -s /usr/local/bin/pkg-config /usr/bin/pkg-config

验证版本

pkg-config --version # 应显示 0.29.2


原因 5:Mellanox(MLX5)网卡缺少 glue 库

使用 Mellanox ConnectX 系列网卡时,DPDK 的 MLX5 PMD 需要额外的动态链接库支持。

错误日志:

net_mlx5: cannot load glue library: librte_pmd_mlx5_glue.so.xx.xx.x:

cannot open shared object file: No such file or directory

net_mlx5: cannot initialize PMD due to missing run-time dependency

on rdma-core libraries (libibverbs, libmlx5)

解决方案:

# 1. 安装 rdma-core 依赖

apt-get install rdma-core libibverbs-dev libmlx5-1 # Ubuntu

yum install rdma-core libibverbs libmlx5 # CentOS

2. 复制 glue 库到系统路径

cp /data/f-stack/dpdk/build/lib/librte_pmd_mlx5_glue.so.* /lib64/

ldconfig

3. 编译 DPDK 时启用 MLX5 支持

cd /data/f-stack/dpdk

make config T=x86_64-native-linuxapp-gcc

sed 's/CONFIG_RTE_LIBRTE_MLX5_PMD=n/CONFIG_RTE_LIBRTE_MLX5_PMD=y/g' \

-i build/.config

make clean && make && make install

4. 修改 config.ini 指定 PCI 地址

[dpdk]

pci_whitelist=0000:03:00.0


原因 6:在虚拟机中运行 / 无物理网卡

在笔记本、虚拟机或容器中没有 DPDK 兼容物理网卡时,需使用虚拟设备(vdev)。

使用 net_ring(内存环回设备):

[dpdk]

lcore_mask=1

channel=4

vdev=net_ring0

port_list=0

[port0]

addr=10.0.0.2

netmask=255.255.255.0

broadcast=10.0.0.255

gateway=10.0.0.1

注意: net_ring 仅用于测试/开发,不适用于生产环境。

在虚拟机中修复 igb_uio:

虚拟机中 pci_intx_mask_supported() 可能返回 false,需修改内核模块代码:

// 文件:f-stack/dpdk/kernel/linux/igb_uio/igb_uio.c 第 274 行

// 修改前:

if (pci_intx_mask_supported(udev->pdev)) {

// 修改后:

if (true || pci_intx_mask_supported(udev->pdev)) {

修改后重新编译 DPDK:

cd /data/f-stack/dpdk

meson -Denable_kmods=true build

ninja -C build && ninja -C build install


原因 7:使用 DPDK 共享库(.so)时驱动未加载

当使用 DPDK 动态库(CONFIG_RTE_BUILD_SHARED_LIB=y)时,PMD 驱动不会自动加载。

解决方案:

  • 切换到 dev 分支代码(已修复此问题)
  • 或改用静态库链接方式

原因 8:Rust binding 链接问题

在 Rust 项目中使用 F-Stack 绑定时,需要在构建脚本中明确链接 NIC 驱动库:

// build.rs

println!("cargo:rustc-link-lib=fstack");

println!("cargo:rustc-link-lib=rte_net_bond"); // 需要显式添加驱动库

// 使用 pkg-config 链接 DPDK

pkg_config::Config::new()

.print_system_libs(false)

.probe("libdpdk")

.unwrap();

注意: 确保 pkg-config 版本 ≥ 0.28,否则驱动库无法被正确识别。


三、快速排查流程

遇到 “No probed ethernet devices” 时,按以下步骤逐一排查:

Step 1: 检查网卡是否绑定 DPDK 驱动

└─ dpdk-devbind.py --status

├─ 未绑定 → 绑定 igb_uio 或 vfio-pci(见原因1)

└─ 已绑定 → 继续 Step 2

Step 2: 检查 config.ini 的 pci_whitelist/allow 是否正确

└─ 与 PCI 地址不匹配 → 修正 config.ini(见原因2)

└─ 匹配 → 继续 Step 3

Step 3: 检查是否自定义 Makefile / 应用

└─ 自定义 → 检查 --whole-archive 链接参数(见原因3)

└─ 使用示例 → 继续 Step 4

Step 4: 检查 pkg-config 版本

└─ < 0.28 → 升级 pkg-config(见原因4)

└─ ≥ 0.28 → 继续 Step 5

Step 5: 检查网卡类型

├─ Mellanox → 安装 rdma-core / 复制 glue 库(见原因5)

├─ 无物理网卡/虚拟机 → 使用 vdev 或修复 igb_uio(见原因6)

└─ 其他 → 确认网卡在 DPDK 支持列表中


四、验证步骤

修复后,按以下步骤验证:

# 1. 验证网卡绑定状态

python3 /data/f-stack/dpdk/usertools/dpdk-devbind.py --status

2. 用 testpmd 验证 DPDK 可以检测到设备

dpdk-testpmd -l 0-1 -n 4 -a 0000:00:03.0 -- -i

若看到 "Found 1 port(s)" 则正常

3. 运行 helloworld 验证 F-Stack 正常启动

cd /data/f-stack/example

./helloworld --conf /etc/f-stack.conf --proc-type=primary --proc-id=0

正常应显示 port 初始化信息,不出现 "No probed ethernet devices"


五、相关 issue 汇总

Issue 状态 关键场景 解决方案
#1035 Open 使用 vdev=net_ring0 但无效 检查 vdev 配置格式
#837 Open 阿里云服务器,eth0 仍使用内核驱动 绑定网卡到 DPDK 驱动
#693 Open 自定义应用中 ff_init 失败 Makefile 添加 –whole-archive
#663 Open Rust binding 运行失败 显式链接 PMD 驱动库
#583 Closed 使用 DPDK .so 动态库 切换到 dev 分支
#581 Closed helloworld 运行失败 升级 pkg-config ≥ 0.28
#531 Closed Mellanox MLX5 网卡 复制 glue 库到 /lib64
#386 Closed ixgbe 网卡,驱动未绑定 绑定 igb_uio 驱动
#379 Closed 自定义 PMD 驱动未链接 在 Makefile 中链接 .a 文件

六、参考资料


*本文档由 OpenClaw 自动整理,基于 F-Stack GitHub issue 历史数据和 DPDK 官方文档。*

过去的2025

今年的头疼比去年略微好一点,但是还是挺严重的,晚上的失眠也是依然非常严重,安眠药的效果已经很弱了,有时候用酒精麻痹下效果还好下,还好布洛芬止疼的效果目前还凑合,随意搜了下,这种状况还是挺多的,但是人的性格就是这样,基本是很难改变了,可以参考:https://www.zhihu.com/question/461572685/answer/3561814996、https://www.v2ex.com/t/1189014。另外发际线也在持续后退,M型越来越明显了。

M


有依然繁琐无用的流程问题令人十分百分千分厌烦,有依然存在的业务稳定性压力,本来是要提离职的,目前同意了部门给的一个选择,不再负责任何一项具体的业务,脱身出来只作为技术专家有问题时进行支持。后续看是否会有些好转吧,个人自身并没有太大的信心能放下,也只能说是常识一段时间看下。头疼和失眠问题是从24年中多了一堆乱七八糟的流程后几个月变得很严重的,在此之前2-3年只有业务稳定压力的时候头疼和失眠本身也会有,只是很偶发,没那么严重,后续个人如果能放下业务稳定性压力,也许会好转,当然前提时能放下。

此图像的alt属性为空;文件名为image.png
虽然不那么准确,但是可以看到趋势,这是在安眠药和酒精麻醉下才达到的,否则会差的更多

AI焦虑问题,实话实说,AI在25年进步还是很大的,从最基本的问答机器人进化到能做一些稍复杂的系列任务的智能体,比较简单或比较独立的代码和流程问题都处理的效果也很不错了,当然比较复杂的大型代码目前还是差强人意的程度也没有达到(比如claude-code-opus-4.5/4.6等效果依然不行),还是需要人工或其他方式不停的拆解任务或修改提示,才能达到比较好的效果。不过按照这两年的进化趋势,说不定26年就都能做得比较好了。


目前AI的使用上,除了一些华而不实的玩具外,主要还是先来搭个框架,做到了6、70分,人工再补充完善到7、80分还是可以的,还是能节省具体的写代码或搭框架的很多时间,只是将之前大部分边写边完善的步骤变换了下,变成了先写好比较完善的流程提示词,再和AI交互逐步完善,虽然AI的进一步完善很多时候的效果就不行了,最后这20%会很依赖人工来完善,如果继续依赖AI完善,浪费的时间和token实在是得不偿失。


总体来说目前AI极大缩小了初级牛马和中级牛马的差距,高级牛马的地位也岌岌可危。但是那个但是呢,作为牛马并不会变得更轻松,AI跑任务的时候,牛马就在并行干其他事情了,总体好像产出会变高,但是对个人的压力不会有任何的减轻,反而会更重了。

F-Stack ff_rss_check()优化介绍

1. 概述

本文档旨在介绍 F-Stack 中对 ff_rss_check() 函数的一项重要优化。该优化通过引入一个预计算的静态端口查找表,显著提升了应用程序作为客户端主动发起大量短连接时的性能,解决了原 ff_rss_check() 函数在高并发场景下可能成为性能瓶颈的问题。

核心优化提交:e54aa4317b5d81f9f8643e491d8ec0ec1e72282a

2. 背景与问题分析

ff_rss_check() 函数在 F-Stack 中负责一个关键任务:当应用程序作为客户端发起新的 TCP 连接时,为其分配合适的本地源端口。

  • 原有实现机制: 每次需要建立新连接时,都会动态调用 ff_rss_check()。该函数会根据目标服务的 IP 和端口(4元组信息),通过一个哈希计算来选择一个 RSS(Receive Side Scaling)友好的本地端口,以确保数据包能被高效地分发到正确的 CPU 核心上处理。
  • 性能瓶颈: 在需要频繁创建短连接(例如,处理 HTTP 请求且未开启 keep-alive)的场景下,每次连接建立都需要执行一次或多次端口选择和冲突检查。这个过程涉及多次的toeplitz_hash计算(平均每次耗时约300+tsc,平均次数至少为该端口的总队列(进程)数),在高并发压力下,会消耗可观的 CPU 资源,并成为限制连接建立速率的瓶颈。

3. 优化方案:静态 RSS 端口表

为了克服上述性能问题,优化方案的核心思想是:变“动态计算”为“静态查找”

  1. 预初始化静态表
    • 在应用程序启动阶段(ff_init() 时),根据配置文件中的预定义规则,预先计算并初始化一个静态的端口查找表(ff_rss_tbl),。
    • 该表包含了预先调用ff_rss_check()计算好的哪些本地端口发出去的包可以返回本进程对应的网卡队列,表中包含 {{远程地址,远程端口}:{本地地址, [所有可用本地端口]}}组合,以及一些辅助数据结构,如可用端口的起始、结束索引,上次选择的到的端口索引等。
  2. 高效的端口选择
    • 当需要建立新连接时,首先尝试从这个预先生成的静态表中进行查找。
    • 如果能找到一个与当前连接目标地址/端口匹配且未被占用的本地端口,则直接使用它。这个过程几乎是无锁且开销极低的(平均耗时约100-250tsc)
    • 最重要的是,静态查找表无需多次选择并计算RSS,只需要查找一次本地端口即可保证远程回包可以回到本进程的队列进行处理,极大提升了选择源端口的效率,进而提升总体的QPS性能。
  3. 优雅降级
    • 如果静态表中所有符合条件的端口都已被占用或该四元组未配置静态查找表,系统会自动降级到原有的 ff_rss_check() 动态计算流程,确保功能的正确性。

4. 配置说明

此功能需要通过配置文件(例如 config.ini)手动开启和定义。相关配置段如下:

# 启用或禁用静态 ff_rss_check 表。
# 若启用,F-Stack 将在 APP 启动时初始化该表。
# 此后当 APP 作为客户端连接服务器时,会首先尝试从该表中选择本地端口。
[ff_rss_check_tbl]
# 启用开关:0-禁用,1-启用。默认为 0。
enable = 1
# 定义端口分配规则(4元组)。
# 格式:<网卡端口ID> <本地地址 daddr> <远程地址 saddr> <远程端口 sport>
# 单个四元组内用空格分隔,多个四元组之间用分号分隔。
# 注意:saddr/sport 二元组最多支持 16 个,同一 saddr/sport 下最多支持 4 个 daddr。
# 因此,最多支持 64 种组合,超出的配置将被忽略。
rss_tbl=0 192.168.1.10 192.168.2.10 80;0 192.168.1.10 192.168.2.11 80;0 192.168.1.10 192.168.2.12 80

配置参数详解

  • enable: 总开关,必须设置为 1 才能启用此优化功能。
  • rss_tbl: 定义端口分配规则。每一条规则指定了网卡端口ID用于获取网卡的队列配置,对于特定的目标服务(saddr + sport),使用哪个预先分配好的本地地址(daddr)。

5. 性能提升

根据内部测试数据,该优化带来了显著的性能提升:

  • 测试场景: 客户端(wrk)向F-Stack Nginx发起http请求,使用长连接。F-Stack Nginx设置反向代理,作为客户端,频繁向多个远程服务器发起短 TCP 连接(未使用 keep-alive)。示例图如下所示:
  • 测试数据:如下表所示
  • 常规 QPS 提升约 2-6%左右,且提升的比列会随着进程数的增加而增加,因为进程数越多,原有的ff_rss_check()需要计算的平均次数就越多。
  • 某些特殊场景下提升可以达到35%以上,主要是因为在某些进程数的配置下,原始动态计算方式,随机选择源端口的次数会远超数学期望的总进程数+少量次数,导致需要大量额外调用ff_rss_check()进行计算,消耗了大量CPU资源。
    • 【注意】不同的upstream服务器配置(数量、IP、端口等)可能会导致不同的进程数存在类似问题,在本测试场景下配置8或16进程时,会出现该问题。导致该问题的具体原因则尚未完全明确,查看相关静态表、端口是否正在被占用和请求测试都正常。
    • 如下表所示分别为8和12进程Nginx调用ff_rss_check()次数的统计,可以看到8核时的随机选择次数明显超出了数学期望的(应该在8-12次左右),16核同理。
      • 其中randomtime表示内核参数net.inet.ip.portrange.randomtime的设置,因为F-Stack框架的特性,作为客户端选择源端口时此时完全随机选择效果更好,且F-Stack对随机性要求并不高,早前已经替换了性能好好很多的伪随机函数。
  • perf top截图

8进程间歇性随机(大部分不随机)时ff_rss_check()调用次数太多,热点很高

8进程完全随机,ff_rss_check()调用次数热点有一定降低

12进程,ff_rss_check()的热点则大幅下降

  • 8进程间歇随机和完全随机选择源端口QPS性能对比

6. 其他参数优化调整

F-Stack对应调整了部分参数,具体如下,其他业务如何配置可以根据自己业务的具体特点灵活调整

[freebsd.sysctl]
net.inet.tcp.delayed_ack=1 # 可以提升大并发的吞吐量
net.inet.tcp.fast_finwait2_recycle=1
net.inet.tcp.finwait2_timeout=5000
net.inet.tcp.maxtcptw=128 # 尽快释放TIME_WAIT状态的端口,增加空闲端口,减少in_pcblookup_local()的调用次数,进而减少ff_rss_check()的调用次数
net.inet.ip.portrange.randomized=1
# Always do random while connect to remote server.
# In some scenarios of F-Stack application, the performance can be improved to a certain extent, ablout 5%.
net.inet.ip.portrange.randomtime=0 # 对某些特定配置条件下原动态计算ff_rss_check()方式有一定性能提升

7. 总结

本次对 ff_rss_check() 的优化是 F-Stack 追求性优化能的一个典型例子。它通过以下方式解决了核心瓶颈:

  1. 空间换时间: 使用预计算的静态表换取运行时动态计算的开销。
  2. 减少调用次数: 极大降低了端口选择时的ff_rss_check()和in_pcblookup_local()的重试调用次数,实现总体系统2-6%,特殊极限场景35%以上的总体系统性能提升。
  3. 保证兼容: 通过降级机制保障了在任何情况下的功能正确性。

建议启用此功能的场景: 所有使用 F-Stack 作为客户端、需要高频率创建新连接(特别是短连接)的应用程序。启用后,只需在配置文件中预先定义好常用的目标服务地址,即可获得显著的性能收益。

【AI使用声明】本文初始版本使用DeepSeek-V3.1生成,然后进行人工调整;示意图片由元宝根据人工提供的提示词生成和修改

递归DNS如何选择权威DNS的IP-SRTT优化及实践

本文主要介绍递归DNS的SRTT本身机制及如何进行更进一步的优化,来达到更好的递归效果。都是字,不想弄图、表和数字,格式也有点乱,不想整理了,凑合看吧

1. SRTT本身机制

本节仅对SRTT进行简单的介绍供了解,不进行特别深入和详细的分析。

SRTT(Smoothed Round-Trip Time,平滑往返时延)是在递归DNS系统里,用加权平均算法计算出的上游DNS服务器的“响应时延”估值,它用于优化解析性能和选择最快的上游服务器。

延伸阅读

BIND的SRTT机制总体做的还是相对比较好的,可以参考 https://developer.aliyun.com/article/622410。

而Unbound社区版的SRTT做的则不够好,基本就是可用权威IP完全随机,递归的平均延迟非常高,所以在实际业务使用前必须进行优化,具体算法优化可以参考相关论文或BIND的实现等。

虽说递归DNS选择权威服务器时都希望选择延迟更低,但在最大限度降低递归时延的前提下,也要遵循一定的规则,如

  • 需要有一定的随机性选择非延迟最低的权威IP来自适应权威IP的RTT的波动和服务器的突然故障。
    • 如某个权威IP正常RTT很低,但偶发丢包被惩罚延迟后,需要能够逐步平滑降低其RTT。
    • 如果低RTT的权威IP突然故障,除了丢包惩罚延迟外,还需要随机性来选择其他权威DNS IP,防止单个IP不可用导致整个域名解析异常。
  • 标记不可用的IP也需要定时发送DNS请求。
    • 不可用的IP恢复可用后需要使用该机制探活标记为恢复可用
    • BIND和UNBOUND都会在15或30分钟完全重置一次权威IP的相关状态,其中包括RTT,以及IP的EDNS支持状态等。
    • 也可以低频率的对惩罚到标记不可用的IP做并发异步的DNS探活请求。
  • 不能向单一权威IP发送过于集中的请求,降低被权威封禁递归IP的概率。
    • 一个可探讨的方案介绍:如果本次不进行随机选择递归IP,选择最低RTT的IP时候再进行二次选择,1/2直接选择最低RTT的权威服务器,1/2在最低RTT差距一定范围内(如minrtt + 20ms,也认为有比较低的延迟)的其他权威服务器IP中随机选择。
    • 国内的各大权威DNS服务商的服务只要不是真实的攻击,常规的SRTT优化很是很难触发递归IP被封禁的场景的,但是一些单位、机构等自建的权威DNS,如果单个IP请求量较大则比较容易触发,所以这里也是递归DNS必须要考虑的一种场景。

2. SRTT的配套机制优化

只有SRTT本身的选择机制对一个递归DNS是不够的,还必须配合多个其他配套系统来使用,而这些配套系统对递归DNS的整体递归时延也是非常重要的。

2.1 某个权威DNS IP的SRTT生效范围

在实际业务中,存在以下两种场景会使多个域名使用相同的权威DNS IP

  • 不同的域名会使用相同的NameServer(NS)
  • 不同的域名使用的NS不同,但NS对应的IP相同(如腾讯云解析/DNSPod提供的个性化NS功能)

在我们的常规认知中,既然权威DNS IP相同,那么统一进行SRTT的探测和选择即可,这样多个域名的平均递归延迟都可以有效降低,如在每次权威IP状态重置后快速重新探测到RTT最低的权威DNS IP,且对所有域名生效。

但现实是相反的,多数递归DNS对这种场景的权威DNS IP探测是域名相关的,不同域名的权威DNS IP即使相同,也是分别独立探测和存储状态的(如Unbound的)。

对于某些特殊场景,这种做法可能有一定用处,如同一个IP对域名A支持EDNS,对域名B不支持等,但这极大增加了递归DNS解析时延(也浪费了一些内存,虽然现代服务器不怎么在意了)。

目前腾讯云/DNSPod的递归服务器的做法是所有域名只要权威DNS IP相同即共享一份SRTT的探测和选择结果,可以大幅提高选择RTT低的权威DNS概率(尤其是请求量不大的域名),降低解析延迟,不考虑一些特别极端的场景(也可以解析,但可能不是最优的结果,且此类极端场景在国内的权威DNS服务商中基本不存在)。

2.2 权威DNS IP的GEO优化

大家都知道普通域名的DNS解析可以做智能解析(分线路、GEO、基于延迟等),来给不同地域不同运营商的客户端分配不同的解析结果,达到降低延迟或节省成本的目的。那么对于给递归DNS使用的NameServer(NS)域名是否可以使用智能解析,来达到同样的目的呢?毕竟因为各种原因,国内大部分权威DNS服务商很难完全使用完全相同的Anycast IP在全球同时提供很高的高可用和很低的延迟,如果该方案可行,则权威DNS服务商有了一个变通的方式来同时满足高可用和低延迟的需求,如除了Anycast IP外还可以在不通地区提供一些本地的Unicast IP来降低本地递归DNS的访问延迟,比如目前腾讯解析/DNSPod和阿里云解析都做了该操作,那么效果如何呢。

答案是一半一半。

递归获取一个域名的权威DNS的NS IP有两个渠道,胶水记录和权威服务器的记录,其中NS域名在注册商/局设置胶水记录不支持智能解析,而在其本身权威DNS设置的解析记录则是可以支持设置智能解析线路的,下面分别进行介绍。

2.2.1 注册局的TLD服务器返回的glue record

  • 胶水记录(Glue Record)是一种特殊的DNS记录,用于解决域名解析过程中的循环依赖问题。当域名的权威DNS服务器(NS记录)是当前域名的子域名时,胶水记录是必须存在的(如后续示例所示),由域名所有者直接在注册商/局(父域DNS,如.com的TLD服务器)中提供子域名服务器的IP地址,确保递归DNS能够正确找到目标的权威DNS服务器。
dig @a.gtld-servers.net ns3.dnsv5.com

; <<>> DiG 9.11.26-RedHat-9.11.26-6.tl3 <<>> @a.gtld-servers.net ns3.dnsv5.com
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45009
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 11
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;ns3.dnsv5.com.                 IN      A

;; AUTHORITY SECTION:
dnsv5.com.              172800  IN      NS      ns3.dnsv5.com.
dnsv5.com.              172800  IN      NS      ns4.dnsv5.net.

# 因为NS域名ns3.dnsv5.com是请求域名dnsv5.com的子域名,此时胶水几率是必须返回的,否则后续继续向TLD服务器请求ns3.dnsv5.com还是返回上面两条NS记录,造成死循环
# 只返回tld相同的ns3.dnsv5.com的胶水记录,而不返回ns4.dnsv5.net的胶水记录
;; ADDITIONAL SECTION:
ns3.dnsv5.com.          172800  IN      A       1.12.0.17
ns3.dnsv5.com.          172800  IN      A       1.12.0.18
ns3.dnsv5.com.          172800  IN      A       1.12.14.17
ns3.dnsv5.com.          172800  IN      A       1.12.14.18
ns3.dnsv5.com.          172800  IN      A       101.227.168.52
ns3.dnsv5.com.          172800  IN      A       108.136.87.44
ns3.dnsv5.com.          172800  IN      A       163.177.5.75
ns3.dnsv5.com.          172800  IN      A       220.196.136.52
ns3.dnsv5.com.          172800  IN      AAAA    2402:4e00:1470:2::f
ns3.dnsv5.com.          172800  IN      A       35.165.107.227

;; Query time: 217 msec
;; SERVER: 192.5.6.30#53(192.5.6.30)
;; WHEN: Fri Aug 15 15:59:20 CST 2025
;; MSG SIZE  rcvd: 255
  • TLD服务器一般只有请求的域名的TLD和本域名设置的NS的TLD一致,会返回胶水记录的(这里大部分其实是非必须的,但是同时返回了胶水记录可以减少一次额外的请求,降低延迟),否则一般不返回胶水记录。如example.com设置NS为ns1.example.com, ns2.example.net, 那么这时候向TLD服务器请求example.com的时候,TLD服务器就会返回example.com的NS记录ns1.example.com和ns2.example.net,且同时返回ns1.example.com的A和AAAA记录(TLD相同,都是.com),但并不会返回ns2.example.net的胶水记录(TLD不同, .com vs .net),如上述和下述请求所示
dig @a.gtld-servers.net dnsov.net

; <<>> DiG 9.11.26-RedHat-9.11.26-6.tl3 <<>> @a.gtld-servers.net dnsov.net
; (2 servers found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8363
;; flags: qr rd; QUERY: 1, ANSWER: 0, AUTHORITY: 2, ADDITIONAL: 11
;; WARNING: recursion requested but not available

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
;; QUESTION SECTION:
;dnsov.net.                     IN      A

;; AUTHORITY SECTION:
dnsov.net.              172800  IN      NS      ns3.dnsv5.com.
dnsov.net.              172800  IN      NS      ns4.dnsv5.net.

# 只返回tld相同的ns4.dnsv5.net的胶水记录,而不返回ns3.dnsv5.com的胶水记录,但其实这里的胶水记录并不是必须要返回的,因为dnsov.net和dnsv5.net是两个不同的域名,再次请求并不会造成死循环,但是同时返回了胶水记录可以减少一次额外的请求,降低延迟
;; ADDITIONAL SECTION:
ns4.dnsv5.net.          172800  IN      A       1.12.0.16
ns4.dnsv5.net.          172800  IN      A       1.12.0.19
ns4.dnsv5.net.          172800  IN      A       1.12.14.16
ns4.dnsv5.net.          172800  IN      A       1.12.14.19
ns4.dnsv5.net.          172800  IN      A       112.80.181.106
ns4.dnsv5.net.          172800  IN      A       117.135.128.152
ns4.dnsv5.net.          172800  IN      A       124.64.205.152
ns4.dnsv5.net.          172800  IN      A       13.37.58.163
ns4.dnsv5.net.          172800  IN      AAAA    2402:4e00:111:fff::8
ns4.dnsv5.net.          172800  IN      A       49.7.107.152

;; Query time: 192 msec
;; SERVER: 192.5.6.30#53(192.5.6.30)
;; WHEN: Fri Aug 15 16:00:30 CST 2025
;; MSG SIZE  rcvd: 261
  • 这里未介绍子域名授权场景的胶水记录,总体原则是一致的。

2.2.2 NS域名本身权威服务器返回的IP记录

NS域名(如ns3.dnsv5.com)在其权威DNS(如腾讯云解析/DNSPod)设置解析记录的时候,和普通域名可以一样设置智能解析(如境内设置一批IP,境外设置另一批IP等),和普通域名的使用没有区别。

2.2.3 递归DNS的行为及优化

递归DNS在进行迭代查询时,有可能从TLD服务器直接获取到NS域名的胶水记录,也可能从权威DNS获取,且两处获取的记录可能不同(部分检测系统会告警,但忽略即可),而权威DNS设置的IP支持智能解析,提供了更大的优化空间,一般相比胶水记录是更好的选择(且非专业技术人员常常忽视对胶水记录的维护),那递归DNS如何使用和优化呢。

常规的递归DNS(如BIND和UNBOUND)一般是这种行为(会略有不同,总体行为类似)

  • 如果向TLD服务器请求某个域名时,TLD服务器同时返回了NS的胶水记录,那么直接使用胶水记录作为NS的IP,不会再去向NS的权威请求记录
  • 如果向TLD服务器请求某个域名时,TLD服务器没有返回NS的胶水记录,则再去正常查询NS域名的记录时,可能回会权威请求NS记录,获取到权威可能设置的分线路智能解析结果,从而后续优先使用权威返回的解析记录
  • 有用户向递归DNS显示请求NS域名的A/AAAA记录,那么递归DNS必然会向NS域名的权威请求一次NS的A/AAAA记录(胶水记录不是其域名权威返回的,用户显式的请求去需要去获取权威设置的记录,而不能直接返回缓存的胶水记录),获取到权威可能设置的分线路智能解析结果,从而后续优先使用权威返回的解析记录
  • 如果权威返回的解析记录全部不可用,则重新回退到使用胶水记录提升可用性

可以看出来,常规的递归DNS不会主动去获取权威DNS的分线路智能解析结果,只会在被动获取到权威返回结果的时候,代替胶水记录进行优先使用,从腾讯云解析/DNSPod使用的实际效果看,权威解析才会设置的解析IP的流量比例总体并不低,这样设置还是有比较好的效果的(具体数据就不提供了)。

那对递归DNS能不能更进一步的进行优化呢,当然可以

  • 即使递归DNS从TLD服务器获取到了NS域名的胶水记录,在正常向胶水记录IP发起查询请求的同时,也自动异步去解析NS域名在权威的A/AAAA记录, 使用NS权威解析记录的比例可以大幅提高。
    • 根域名、TLD域名以及海外大厂的NS等域名不必做这个操作,因为记录一般都是一致的Anycast IP
    • 腾讯云/DNSPod所有对外提供服务的公共DNS、HTTPDNS、DoH、DoT、云内网DNS等的后端递归DNS都支持该项优化

F-Stack如何修改TCP连接的MSS


本文仅介绍F-Stack服务器作为被动接受连接的一方如何在不修改程序的前提下如何相对简单的修改TCP建连时的MSS选项

TCP数据包在经过某些网络设备(如负载均衡等)时可能会被增加某些头(如vlan、gre、ipip等),但是又没有正确的减小syn包中MSS选项的值,导致后续的数据包加上额外的头后可能超过1500的MTU而无法正常收发包,此时我们可能无法控制中间链路的网络设备,就需要修改应用服务器的MSS配置,但最好上还是应该由中间网络设备进行兼容。

F-Stack-1.21.5(FreeBSD-11.0)

FreeBSD-11.0的ipfw工具不支持直接修改tcp的mss选项的值,必须要同时借助netgraph(工具:ngctl)来实现,参考命令如下

# 创建 tcpmss 节点并将其连接到 ng_ipfw 节点
ff_ngctl mkpeer ipfw: tcpmss 100 msshook

# 设置mss和hook函数
# 该命令会报错ff_ngctl: recv incoming message: Operation not permitted,实际为回显结果时f-stack的ff_ngctl对freesbd的ngctl的一个兼容性问题,不影响设置效果,仅影响设置成功后的回显
ff_ngctl msg ipfw:100 config '{ inHook="msshook" outHook="msshook" maxMSS=1440 }'

# 将流量导入 tcpmss 节点
ff_ipfw add 300 netgraph 100 tcp from any to any tcpflags syn out via f-stack-0

# 让数据包在被修改后继续由 ipfw 处理, 可选,也可以配置在config.ini
ff_sysctl net.inet.ip.fw.one_pass=0


参考:ng_tcpmss man page

F-Stack-1.22+(FreeBSD-13.0)

FreeBSD-13.0的ipfw工具通过pmod模块引入的方式支持了直接修改tcp的mss选项值的功能,命令参考如下:

ff_ipfw add 1000 tcp-setmss 1440 tcp from any to any tcpflags syn out

【注意:】目前除了dev分支最新代码默认支持了ip_fw_pmod模块,其他分支和release版本需要先打上下面这个patch,在F-Stack的ipfw开启ip_fw_pmod模块功能:ff_ipfw support ip_fw_pmod and tcpmod for tcp-setmss.

F-Stack通用方式

因为F-Stack并未实际实现修改网卡MTU的功能,所以可以通过修改用户态FreeBSD协议栈中网卡设备MTU的操作,来达到减小mss选项的值(MTU – 40),但网卡实际的MTU还是1500,并没有减小,从而达到也可以正常收发超过设置的MTU(如1480)大小的包(如1500),但这里借助了F-Stack的feature(不是bug)来实现的,并不建议作为标准方案使用,参考命令如下

ff_ifconfig f-stack-0 mtu 1480

Linux协议栈

linux协议栈对标ipfw,使用iptables修改mss选项值的命令参考

iptables -t mangle -A OUTPUT -p tcp --tcp-flags SYN,RST SYN -j TCPMSS --set-mss 1440

过去的2024

2025.5.24 更新

本来是5.21周三去进行哥哥的海葬,结果周三全天大雾取消了,改成了今天。早上六点集合,6点25左右发船,经过一个多小时后到达海葬海域,船不大,海浪有点大,大部分人吐的稀里哗啦。不过总算结束了一件事情,哥哥一路走好,活着的时候半生自由活动受限,现在至少可以算是自由的,可以顺着大海游遍全世界。

======================================================

频繁的头疼欲裂,需要靠大量的布洛芬和替扎尼定续命,如果有选择,早就不再继续卷了。

原有家庭本身最后一个人离去,多次住院,加部分截肢多坚持了一年半吧,临走前可能也是有点感应吧,做梦失眠头疼加剧。

小孩小学还是卷的厉害呀,目前看文理科都没有很好的天赋,也是顺其自然吧。

最后是工作的事情,24年最大的体验其实是一些老生常谈的东西,当业务增长不动的时候,各种流程就来了。

不能说流程没用,在合理的流程下,可以提高项目质量的下限,虽然对部分项目的质量上限没什么太大作用,但是在一个比较大的部门中确实不能否认它的作用。

但是流程本身是问题了提升质量服务的,而当前完全本末倒置了,已经达到了丧心病狂的程度,各种不合理和冗余的流程,完全是为了流程而流程,对质量提升的终极目标的效果非常有限。

而且严重拖后腿,花费特别大的精力,流程搞了一大堆,再一看实际产出,大部分直接呵呵了,虽然即使有产出也不一定对最后的产品收益就一定有多大帮助,但合理的产出总比不合理的流程合理多了。

看到的最大好处算是至少有了审计的基础。

其他吐槽的欲望都没有了,这就样吧,结束。

2024年某些”小众”递归DNS那些事儿

本文就不写运营商递归DNS和相对比较知名一些的公共DNS了,通过几个case聊聊2024年当前可以应答DNS请求、在国内用户量不小,但是又搜索不到什么公开信息的一些”小众“递归DNS的那些事儿,部分已经无法访问的递归DNS就不再包含进来了。这些递归DNS的用户体量并不比不少知名公共DNS小,只是基本不为人知,关注该领域的可以大概了解下。

使用这些DNS比较容易出现解析线路不准确(如国内用户解析到国外IP)、域名被劫持等场景,因为让所有用户修改DNS并不现实,一般还是更建议有端的客户端(如APP等)尽量使用HttpDNS或其他技术手段规避类似场景。

【注1】本文部分线索来自其他伙伴。

【注2】本文所有数据为简单测试,仅供参考。

case 1:183.2.x.x

该DNS疑似某供应商为园区、公司等场景提供的递归DNS,在本省的使用率不低。在24年5月的时候测试,使用该DNS会大概率将域名解析到国外的IP,会导致终端用户访问延迟升高,也造成该省份域名解析到国外IP的概率略微高于其他省份。但是在24年下半年测试没什么大的问题了,可以正常解析为本省份结果,只是解析结果没有区分运营商,基本不影响正常使用。

下图分别为24年5月和12月的测试递归DNS出口IP的结果对比

case 2:45.14.x.x、45.128.x.x

该组递归DNS分布于多个C段,多个海外其他国家,目前看国内各省份运营商皆有固网/wifi用户访问,设置比例大约在小万分之x,使用移动网络则不会访问该组递归DNS,疑似从2024.12.04 16:00+开始对外提供DNS解析服务。

该组递归DNS后端在进行递归请求时固定携带127.0.0.1作为ECS IP,大部分域名可以解析到默认线路,无省份运营商区分,解析到国外IP的概率倒是不高。

该组DNS貌似会随机劫持某些域名到其他IP,并302跳转到其他违规网站。历史测试截图如下。

注3】截止本文时间(2024.12.18),之前历史被劫持域名已经没有被劫持,且之前被劫持到的相关IP也已经无法访问,疑似已经被处理。

case 3:154.86.x.x

该组递归DNS分布于多个C段,部署于中国香港,目前看国内各省份运营商皆有固网/wifi用户访问,设置比例大约在小千分之x,使用移动网络则不会访问该组递归DNS,具体对外提供服务时间则不确定。

该组递归DNS后端在进行递归请求时会随机转发给国内外多个其他公共DNS,且并未携带ecs,大部分域名的解析结果IP为国外IP,会导致终端用户访问延迟升高,递归DNS后端出口IP测试结果如下所示,其中包括Google、UltraDNS等其他的公共DNS的递归出口IP

该组DNS因为是纯转发,所以部分特征会测试到转发目标公共DNS的特征,如NeuStar家的UltraDNS,但实际并非该集群本身的版本特征,也并非转发目标公共DNS部署的DNS缓存转发服务,部分特征测试结果如下所示:

dig version.bind chaos txt @154.86.x.x +short
“1733297354.ultradns”
dig hostname.bind chaos txt @154.86.x.x +short
“rcrsv2.jptyo1.ultradns”

dig version.bind chaos txt @154.86.x.x
version.bind. 86400 CH TXT “PowerDNS Recursor 5.1.1 (built Jul 22 2024 13:49:14 by root@localhost)”

dig version.bind chaos txt @154.86.x.x +short
version.bind. 75428 CH TXT “Q9-P-7.6”

该组DNS貌似会随机劫持某些过期域名到其他IP,并302跳转到其他网站(有点类似纠错页),当前(2024.12.18)跳转的目标网站实测无法访问,也未找到历史网页内容,且很多安全情报都会报告风险并进行访问拦截,没有什么实质性的影响。

F-Stack 对 HTTP/3 的支持使用说明

Nginx 主线在1.25.x 版本中已经加入了对 HTTP/3 的支持,F-Stack 在等待了两个小版本之后,也移植了 Nginx-1.25.2 版本到 F-Stack 上,目前可以支持HTTP/3的测试使用,本文介绍移植过程中的一些兼容性改造及使用注意事项。

主要兼容项

主要是 F-Stack 的一些接口兼容和 FreeBSD 不支持一些 Linux 的部分选项,而 Nginx 的自动配置检测的是 Linux 是否支持,需要进行一些修改。

  1. 对 F-Stack 的 ff_recvmsg 和 ff_sendmsg 接口进行修改,兼容 Linux 接口,主要是部分结构体字段类型不一致的兼容,虽然结构体编译对齐后总长度是一致的。
  2. 关闭了 BPF sockhash 功能(bpf,SO_COOKIE)的功能检测和开始,该功能主要用于通过从 bpf 的 socket_cookie 直接获取数据,提升性能。
  3. 关闭了 UDP_SEGMENT 功能,主要功能设置 UDP 分段大小。
  4. IP_PKTINFO 选项不探测是否支持,强制使用 FreeBSD 的 IP_RECVDSTADDR 和 IP_SENDSRCADDR 选项。
  5. IP_MTU_DISCOVER 选项不探测是否支持, 强制使用 FreeBSD 的 IP_DONTFRAG 选项。
  6. IPV6_MTU_DISCOVER 选项不探测是否支持, 强制使用 IPV6_DONTFRAG 选项,该选项目前 FreeBSD 和 Linux 都支持。

编译过程

SSL 库

此处以 OpenSSL quic 为例,可以参考以下方式编译

cd /data/
wget https://github.com/quictls/openssl/archive/refs/tags/OpenSSL_1_1_1v-quic1.tar.gz
tar xzvf OpenSSL_1_1_1v-quic1.tar.gz
cd /data/openssl-OpenSSL_1_1_1v-quic1/
./config enable-tls1_3 no-shared --prefix=/usr/local/OpenSSL_1_1_1v-quic1
make
make install_sw

DPDK 和 F-Stack lib

总体编译方式不变,额外需要注意的是如果系统的 OpenSSL 库版本与上面使用的 OpenSSL quic 版本不兼容时,编译 DPDK lib 库时需要也使用上面的OpenSSL quic 库(通过配置 PKG_CONFIG_PATH 使用),参考以下方式编译

export FF_PATH=/data/f-stack
export PKG_CONFIG_PATH=/usr/local/OpenSSL_1_1_1v-quic1/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/local/lib64/pkgconfig:/usr/lib/pkgconfig

mkdir -p /data/f-stack
git clone https://github.com/F-Stack/f-stack.git /data/f-stack

# DPDK lib
cd /data/f-stack/dpdk/
meson -Denable_kmods=true build
ninja -C build
ninja -C build install

# F-Stack lib
cd /data/f-stack/lib/
make
make install

# ff tools
cd /data/f-stack/tools
make
make install

F-Stack Nginx-1.25.2

Nginx 可以参考以下参数进行编译,如果有更多额外需求,自行调整相关配置

export FF_PATH=/data/f-stack
export PKG_CONFIG_PATH=/usr/local/OpenSSL_1_1_1v-quic1/lib/pkgconfig:/usr/lib64/pkgconfig:/usr/local/lib64/pkgconfig:/usr/lib/pkgconfig

cd /data/f-stack/app/nginx-1.25.2/
./configure --prefix=/usr/local/nginx_fstack --with-ff_module --with-http_ssl_module --with-http_v2_module --with-http_v3_module --with-cc-opt=-I/usr/local/OpenSSL_1_1_1v-quic1/include --with-ld-opt='-L/usr/local/OpenSSL_1_1_1v-quic1/lib/'
make
make install

测试使用注意事项

  1. keepalive_timeout = 65 # 因为 Nginx 的 quic 中将 keepalive_timeout参数值作为了读超时时间,所以不能设置为 0
  2. listen 443 quic; # 监听HTTP/3的时候不能设置REUSEPORT,否则多进程会有异常
  3. ulimit -n 100000 # 调大该参数值
  4. 其他使用注意事项可以参考 F-Stack 和 HTTP/3 相关配置文档

性能测试对比

这里不考虑现网实际客户端访问网站的延迟对比,仅考虑 F-Stack Nginx 和源生 Nginx 的性能对比测试。

但是在尝试了多种客户端后,仅 curl8 测试成功,但是只能测试单连接的延迟,这里不太关注。其他压测客户端工具 wrk-quic、h2load、Nighthawk 等在编译测试时都遇到了各种各样的问题,暂时未能成功测试,性能对比数据暂时缺失,如果有人有压测客户端,欢迎进行对比测试并提供测试数据。