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
相关产品推荐
相关产品推荐

