You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux rtnetlink发送RTM_NEWQDISC未调用tc_modify_qdisc问题

问题描述

尝试参考rtnetlink官方手册通过rtnetlink套接字创建新的qdisc。程序逻辑为:先打开netlink套接字,将sockaddr_nl结构绑定到套接字后发送携带rtnetlink数据的消息。代码中将nlmsghdr的类型设置为RTM_NEWQDISC以创建新qdisc,但内核调试发现RTM_NEWQDISC对应的处理函数(位于net/sched/sch_api.c中的tc_modify_qdisc)始终未被调用,且全程无任何系统调用返回错误,初步判断为构造的消息结构错误或字段不完整,仅查阅手册无法定位问题。使用内核版本为5.17,复现代码如下:

#include <stdio.h>
#include <asm/types.h>
#include <sys/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>

struct data {
  struct nlmsghdr nl;
  struct tcmsg tc;
  struct rtattr rt;
};

int main() {
  int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  if(fd == -1) {
      printf("Error in socket: %s\n", strerror(errno));
      return -1;
  }

  struct sockaddr_nl la;
  memset((void *)&la, 0, sizeof(struct sockaddr_nl));
  la.nl_family = AF_NETLINK;
  la.nl_pid = getpid();

  if(bind(fd, (struct sockaddr*)&la, sizeof(la)) == -1) {
    printf("Error in bind: %s\n", strerror(errno));
    return -1;
  }

  struct data d;
  memset((void *)&d, 0, sizeof(struct data));
  d.nl.nlmsg_len = NLMSG_LENGTH(sizeof(struct tcmsg));
  d.nl.nlmsg_type = RTM_NEWQDISC;
  d.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_DUMP | NLM_F_CREATE | NLM_F_ACK;
  d.nl.nlmsg_seq = 0;
  d.nl.nlmsg_pid = 0;

  d.tc.tcm_family = 0;
  d.tc.tcm_ifindex = 0;
  d.tc.tcm_handle = 0;
  d.tc.tcm_parent = 0;
  d.tc.tcm_info = 0;

  d.rt.rta_type = TCA_UNSPEC;

  struct msghdr msg;
  memset((void *)&msg, 0, sizeof(struct msghdr));
  msg.msg_name = (void *)&la;
  msg.msg_namelen = sizeof(la);

  struct iovec iov;
  memset((void *)&iov, 0, sizeof(struct iovec));
  iov.iov_base = (void *)&d.nl;
  iov.iov_len = d.nl.nlmsg_len;

  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  if(sendmsg(fd, &msg, 0) == -1) {
    printf("Error in sendmsg: %s\n", strerror(errno));
    return -1;
  }

  puts("success");
}
根因分析

代码存在4个核心错误,导致消息根本没有被内核的qdisc创建逻辑接收处理:

  • 发送目标地址配置错误:代码直接复用bind时的本地sockaddr_nl结构作为发送目标,该结构的nl_pid被设置为当前进程PID,意味着消息被发送给了进程自身,根本没有递交给内核。netlink消息发送给内核时,目标地址必须满足nl_pid=0、nl_groups=0。
  • 消息标志位错误携带NLM_F_DUMP:该标志仅用于GET类请求拉取全量配置,内核rtnetlink处理逻辑中,只要检测到该标志就会直接路由到dump回调,不会进入RTM_NEWQDISC对应的tc_modify_qdisc创建流程。
  • 关键字段设置无效:tcm_ifindex=0不是合法的网络设备索引,必须指定要挂载qdisc的实际网卡索引(可通过if_nametoindex根据网卡名获取);tcm_family需设置为AF_UNSPEC,不能填0;创建根qdisc时tcm_parent需指定为TC_H_ROOT。
  • 消息长度与属性缺失:nlmsg_len仅计算了nlmsghdr+tcmsg的长度,后续定义的rtattr根本没有被纳入发送范围,同时创建qdisc必须携带TCA_KIND属性指定qdisc类型(如pfifo、htb等),仅传TCA_UNSPEC无法完成创建。
修正后参考代码
#include <stdio.h>
#include <asm/types.h>
#include <sys/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <net/if.h>

#define BUF_SIZE 1024

int main() {
  int fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
  if(fd == -1) {
      printf("Error in socket: %s\n", strerror(errno));
      return -1;
  }

  struct sockaddr_nl local_addr;
  memset(&local_addr, 0, sizeof(local_addr));
  local_addr.nl_family = AF_NETLINK;
  local_addr.nl_pid = getpid();

  if(bind(fd, (struct sockaddr*)&local_addr, sizeof(local_addr)) == -1) {
    printf("Error in bind: %s\n", strerror(errno));
    close(fd);
    return -1;
  }

  // 构造发送给内核的目标地址
  struct sockaddr_nl kernel_addr;
  memset(&kernel_addr, 0, sizeof(kernel_addr));
  kernel_addr.nl_family = AF_NETLINK;
  kernel_addr.nl_pid = 0;
  kernel_addr.nl_groups = 0;

  char buf[BUF_SIZE];
  memset(buf, 0, BUF_SIZE);
  struct nlmsghdr *nl = (struct nlmsghdr *)buf;
  struct tcmsg *tc = (struct tcmsg *)NLMSG_DATA(nl);
  char *attr_ptr = (char *)(tc + 1);

  // 填充nlmsghdr
  nl->nlmsg_type = RTM_NEWQDISC;
  nl->nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK;
  nl->nlmsg_seq = 1;
  nl->nlmsg_pid = getpid();

  // 填充tcmsg
  tc->tcm_family = AF_UNSPEC;
  tc->tcm_ifindex = if_nametoindex("lo"); // 示例挂载到lo网卡,替换为实际网卡名
  tc->tcm_handle = TC_H_MAKE(TC_H_ROOT, 0);
  tc->tcm_parent = TC_H_ROOT;
  tc->tcm_info = 0;

  // 添加TCA_KIND属性,指定qdisc类型为pfifo
  const char *qdisc_kind = "pfifo";
  size_t kind_len = strlen(qdisc_kind) + 1;
  struct rtattr *kind_attr = (struct rtattr *)attr_ptr;
  kind_attr->rta_type = TCA_KIND;
  kind_attr->rta_len = RTA_LENGTH(kind_len);
  memcpy(RTA_DATA(kind_attr), qdisc_kind, kind_len);
  attr_ptr += RTA_ALIGN(kind_attr->rta_len);

  // 计算最终消息长度
  nl->nlmsg_len = attr_ptr - buf;

  struct msghdr msg;
  memset(&msg, 0, sizeof(msg));
  msg.msg_name = &kernel_addr;
  msg.msg_namelen = sizeof(kernel_addr);

  struct iovec iov;
  iov.iov_base = buf;
  iov.iov_len = nl->nlmsg_len;

  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;

  if(sendmsg(fd, &msg, 0) == -1) {
    printf("Error in sendmsg: %s\n", strerror(errno));
    close(fd);
    return -1;
  }

  // 读取内核返回的ACK或错误信息
  char recv_buf[BUF_SIZE];
  memset(recv_buf, 0, BUF_SIZE);
  struct iovec recv_iov = {
    .iov_base = recv_buf,
    .iov_len = BUF_SIZE
  };
  struct msghdr recv_msg = {
    .msg_name = &kernel_addr,
    .msg_namelen = sizeof(kernel_addr),
    .msg_iov = &recv_iov,
    .msg_iovlen = 1
  };
  ssize_t recv_len = recvmsg(fd, &recv_msg, 0);
  if (recv_len == -1) {
    printf("Error in recvmsg: %s\n", strerror(errno));
    close(fd);
    return -1;
  }

  struct nlmsghdr *ret_nl = (struct nlmsghdr *)recv_buf;
  if (ret_nl->nlmsg_type == NLMSG_ERROR) {
    struct nlmsgerr *err = (struct nlmsgerr *)NLMSG_DATA(ret_nl);
    if (err->error == 0) {
      puts("qdisc create success");
    } else {
      printf("qdisc create failed: %s\n", strerror(-err->error));
    }
  }

  close(fd);
  return 0;
}

内容的提问来源于stack exchange,提问作者BitFriends

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 04:03:26