返回

解决tc命令RTNETLINK错误: No such file or directory

Linux

解决 “RTNETLINK answers: No such file or directory” 错误 (tc 命令)

最近在用 tc 命令模拟网络延迟、丢包和带宽限制时, 碰到一个头疼的错误:“RTNETLINK answers: No such file or directory”。脚本执行到一半就卡住了, 体验极差. 这篇博客就来好好聊聊这个问题的成因和解决方法。

一、 问题重现

先来看看出错的脚本(简化版):

ETH="eth0" # 假设是 eth0, 请根据实际情况修改
LATENCY="100ms"
JITTER="10ms"
LOSS="1%"
BW="10mbit"

sudo /sbin/tc qdisc del dev $ETH root  2> /dev/null # 忽略错误输出,因为可能root qdisc 还未建立
sudo /sbin/tc qdisc add dev $ETH root handle 1: netem delay $LATENCY $JITTER
sudo /sbin/tc qdisc add dev $ETH parent 1:1 handle 10: netem loss $LOSS
sudo /sbin/tc qdisc add dev $ETH parent 10:1 handle 20: htb default 11 #htb 子分支一般喜欢以11 作为default 分类.
sudo /sbin/tc class add dev $ETH parent 20:1 classid 20:11 htb rate $BW ceil $BW # 此处注意 htb 的classid 的父子关系
sudo /sbin/tc qdisc show

执行上述脚本,可能会在第 8、9 或 10 行报 “RTNETLINK answers: No such file or directory” 错误。

二、 问题原因分析

这个错误信息比较笼统,但核心原因通常是:尝试对一个不存在的 qdisc(排队规则)或 class(类) 进行操作 。 结合 tc 命令的工作原理,可能有以下几种情况:

  1. 父 qdisc 或 class 不存在:

    • tc qdisc add ... parent ... 命令中, 指定的 parent 对应的 qdisc 或 class 还没有被创建。 就像你要把文件放到一个不存在的文件夹里, 肯定会出错。
    • 例如, 在上面的脚本中, 如果 1: 的 qdisc 没有成功创建, 那么在第 8 行尝试添加 parent 1:1 的 qdisc 时, 就会失败.
  2. 设备不存在:

    • dev 参数指定的网络接口(例如 eth0)不存在或未启用。
  3. 内核模块缺失:

    • tc 命令依赖于内核的 Traffic Control 模块。如果相关模块没有加载, tc 可能会无法正常工作。不过,这种情况通常会有更明确的错误提示,比如 "Cannot find device..."之类的.
  4. 脚本执行顺序错误:

    • 即使每个 tc 命令本身没问题,但如果执行顺序不正确,也可能导致依赖关系出错。比如,先创建子 qdisc,再创建父 qdisc。
  5. Handle 冲突:

    • 虽然较少见, 但是如果你手动指定了Handle (handle 1:, handle 10:等), 有可能和其他地方已有的 Handle 冲突, 也可能导致失败.

三、 解决方案

针对以上原因,可以尝试以下方法来解决问题:

  1. 确保父 qdisc/class 已存在:

    这是最常见的错误原因。 仔细检查脚本中 parent 参数指定的 qdisc 或 class 是否已经正确创建. 必要时可以添加一些调试输出 (比如 tc qdisc show) 来查看当前的状态。

    改进示例:

    ETH="eth0"
    LATENCY="100ms"
    JITTER="10ms"
    LOSS="1%"
    BW="10mbit"
    
    sudo /sbin/tc qdisc del dev $ETH root 2> /dev/null
    
    # 先创建根 qdisc
    if ! sudo /sbin/tc qdisc add dev $ETH root handle 1: netem delay $LATENCY $JITTER; then
      echo "Error: Failed to create root qdisc."
      exit 1
    fi
    
    # 创建第一个子 qdisc (loss)
    if ! sudo /sbin/tc qdisc add dev $ETH parent 1:1 handle 10: netem loss $LOSS; then
        echo "创建父级 1:1 的loss qdisc 失败。也许1: 不存在"
        exit 1
    fi
    
    #创建第二个子 qdisc(htb), 挂在netem loss 下.
    if ! sudo /sbin/tc qdisc add dev $ETH parent 10:1 handle 20: htb default 11; then
       echo "创建 htb qdisc (父级 10:1) 失败"
       exit 1
    fi
    
     # 接着可以创建 htb 的类了.
     if ! sudo /sbin/tc class add dev $ETH parent 20:1 classid 20:11 htb rate $BW ceil $BW; then
        echo "创建带宽class (父级 20:1, classid 20:11) 失败."
        exit 1
    fi
    
    sudo /sbin/tc qdisc show dev $ETH
    sudo /sbin/tc class show dev $ETH
    

    这个例子增加了错误检查, 能更清楚地定位问题. 而且展示了htb 如何挂在 netem loss qdisc 下。

  2. 检查网络接口:

    确认 dev 参数指定的网络接口名称是否正确。使用 ip link show 命令查看系统中可用的网络接口。

    示例:

    ip link show  # 查看所有接口, 确保 $ETH 变量的值正确
    

    如果网络接口没启动,可以用 ip link set dev $ETH up启动。

  3. 检查/确认内核模块:

    虽然现在发行版的内核一般都自带这些模块了。可以使用lsmod确认:

      lsmod | grep sch_netem
      lsmod | grep sch_htb
    

    如果没加载, 可以尝试手动加载 modprobe sch_netemmodprobe sch_htb.
    如果确实缺少相关模块, 那么你需要重新编译内核或者安装包含这些模块的内核包.

  4. 使用 try...except (bash不支持, 建议改用python):

如果你的tc 操作不一定每次都能执行, 又不希望出错就终止,可以将可能出现问题的部分语句使用错误处理机制。
(注意, Bash 本身没有 try-except, 但我们可以用其他方法模拟.)

**改进的思路(python):** 

```python
import subprocess

def run_tc_command(command):
    try:
        subprocess.run(command, shell=True, check=True, stderr=subprocess.PIPE) #stderr=subprocess.PIPE 会捕获错误输出流,check=True 若返回值不为0 会抛出异常。
    except subprocess.CalledProcessError as e:
        print(f"命令执行失败: {command}")
        print(f"错误信息: {e.stderr.decode()}") #将 bytes 解码成字符串
        # 可以选择 exit(1) 退出, 或者继续

ETH = "eth0"
LATENCY = "100ms"
JITTER = "10ms"
LOSS = "1%"
BW = "10mbit"

run_tc_command(f"sudo /sbin/tc qdisc del dev {ETH} root") # 可以忽略这里的错误, 因为有可能root 不存在
run_tc_command(f"sudo /sbin/tc qdisc add dev {ETH} root handle 1: netem delay {LATENCY} {JITTER}")
run_tc_command(f"sudo /sbin/tc qdisc add dev {ETH} parent 1:1 handle 10: netem loss {LOSS}")
run_tc_command(f"sudo /sbin/tc qdisc add dev {ETH} parent 10:1 handle 20: htb default 11")
run_tc_command(f"sudo /sbin/tc class add dev {ETH} parent 20:1 classid 20:11 htb rate {BW} ceil {BW}")
run_tc_command(f"sudo /sbin/tc qdisc show dev {ETH}")
```

这个 Python 版本的脚本更健壮,出错时能给出更友好的提示. 也更方便扩展和维护.
  1. 避免 Handle 冲突(如果手动指定):
    尽量避免手动指定 Handle, 让 tc 自动分配. 如果非要手动指定, 一定要仔细检查, 确保没有重复。

  2. 精简操作:
    如果问题依然存在, 尝试简化 tc 命令, 逐步排除。 例如, 先只添加 delay, 看是否成功, 再逐步添加 loss 和带宽限制.

  3. 检查 tc 版本和内核兼容性 (不常见, 但是一种可能)
    在一些老旧的系统或者嵌入式系统上, tc 的版本和内核版本的兼容可能也是一个原因。确保你使用的是兼容当前内核版本的tc工具.

  4. 正确理解classid :

    sudo /sbin/tc class add dev eth0 parent 20: classid 0:1 htb rate $BW ceil $BW
    

    在上面错误代码里classid 0:1中的0是没有意义的。 正确的方式中classid 的父级部分要和其所在qdisc的 handle 号一致.
    在修改版代码中我们已经把classid改正。 HTB的classid 和 qdisc的 handle的 结构都是major:minor

四、 总结及额外安全建议

解决 "RTNETLINK answers: No such file or directory" 错误的关键在于理解 tc 命令的层次结构, 以及 qdisc 和 class 之间的依赖关系. 大多数情况下,都是因为父级 qdisc/class 不存在, 或者脚本的先后顺序写的不合理导致的。 仔细检查并按照上面提供的解决方案一步步排查,一般都能解决问题.

安全建议:

  • 谨慎操作: tc 命令会直接影响网络流量, 错误的配置可能导致网络中断。 在生产环境中进行修改前, 务必在测试环境中充分验证。
  • 备份配置: 在进行大的修改之前, 建议备份当前的 tc 配置. 这样即使出错,也能快速恢复。
  • 最小权限原则: 虽然示例中使用了 sudo, 但如果可能, 尽量使用具有特定权限的普通用户来执行 tc 命令, 避免不必要的风险. (可以使用 capabilities 机制).
  • 考虑网络接口 down掉的情况,和up 时候自动加载 tc 规则。

这些建议可以帮助你更安全、有效地使用 tc 命令.