Linux内核模块中如何修改struct msghdr插入自定义UDP头部?
解决Linux 6.5.0内核下RUDP_sendmsg插入Header时的页错误问题
核心问题定位
你的页错误(权限违规)和copy_to_iter/copy_to_user返回0的根源在于:
- 用户态传入的
msg->msg_iter是READ方向的迭代器(用于内核从用户空间读取数据),直接用copy_to_iter往里面写会因为方向不匹配失败,返回0。 - 内核态直接通过
memcpy访问用户空间内存,会触发页表权限检查,导致权限违规的页错误。
正确实现思路
内核态不能直接修改用户空间的iov_iter,正确的做法是:先把RUDP Header和用户数据合并到内核缓冲区,再构造内核态的iov_iter传给UDP的sendmsg函数。
具体代码实现步骤
#include <linux/socket.h> #include <linux/net.h> #include <linux/udp.h> #include <linux/mm.h> #include <linux/uio.h> int rudp_sendmsg(struct socket *sock, struct msghdr *msg, size_t len) { struct iov_iter kernel_iter; void *kernel_buf; size_t header_len = sizeof(struct rudp_header); size_t total_len = header_len + len; struct rudp_header header; int ret; // 1. 分配内核缓冲区(进程上下文用GFP_KERNEL,软中断/中断上下文改用GFP_ATOMIC) kernel_buf = kmalloc(total_len, GFP_KERNEL); if (!kernel_buf) return -ENOMEM; // 2. 填充RUDP Header到内核缓冲区开头(根据协议定义修改字段) header.seq = htonl(rudp_get_seq(sock)); header.ack = htonl(rudp_get_ack(sock)); header.flags = RUDP_FLAG_DATA; memcpy(kernel_buf, &header, header_len); // 3. 从用户态迭代器拷贝数据到内核缓冲区的Header之后 ret = copy_from_iter(kernel_buf + header_len, len, &msg->msg_iter); if (ret != len) { kfree(kernel_buf); return -EFAULT; } // 4. 初始化内核态READ方向的迭代器(供UDP读取数据发送) struct iovec kernel_iov = {.iov_base = kernel_buf, .iov_len = total_len}; iov_iter_init(&kernel_iter, READ, &kernel_iov, 1, total_len); // 5. 替换迭代器并调用UDP的sendmsg struct iov_iter orig_iter = msg->msg_iter; msg->msg_iter = kernel_iter; ret = udp_sendmsg(sock, msg, total_len); // 6. 恢复原迭代器、释放内核缓冲区 msg->msg_iter = orig_iter; kfree(kernel_buf); return ret; }
关键细节说明
- 迭代器方向匹配:用户态传入的迭代器是READ方向(内核读用户数据),我们构造的内核迭代器也用READ方向,确保UDP模块能正确读取内核缓冲区的数据。
- 内存分配上下文:如果
rudp_sendmsg运行在软中断/中断上下文,必须用GFP_ATOMIC,避免内存分配时睡眠导致死锁。 - 完整性检查:必须校验
copy_from_iter的返回值,确保用户数据完整拷贝到内核缓冲区,否则返回-EFAULT。
避坑注意事项
- 禁止直接操作用户态内存:内核态没有用户空间内存的写入权限,强行修改会触发页错误。
- 不要混用迭代器方向:
copy_to_iter仅适用于WRITE方向的迭代器,用户态sendmsg的迭代器是READ方向,调用必然返回0。 - 及时释放内核内存:无论UDP发送成功或失败,都要释放分配的内核缓冲区,避免内存泄漏。
内容的提问来源于stack exchange,提问作者zhf999
相关产品推荐
相关产品推荐

