linux内核 NFQueue
NFQueue (Netfilter Queue)是 Linux 内核Netfilter框架的核心组件它把匹配 iptables/nftables 规则的数据包从内核态挂起并通过nfnetlink套接字发到用户态程序处理处理完后由用户态返回判决Accept/Drop/ 修改内核再按判决放行或丢弃。核心价值让用户态程序能深度介入内核网络栈实现自定义过滤、DPI、IDS/IPS、流量控制、协议分析等灵活逻辑。内核态数据包拦截与入队挂载点在 Netfilter 的 5 个钩子PRE_ROUTING、INPUT、FORWARD、OUTPUT、POST_ROUTING处通过 iptables 规则匹配流量触发NFQUEUE目标。挂起与入队匹配包被暂停内核处理分配唯一packet_id存入内核队列链表结构按队列号0~65535隔离。通知用户态内核通过nfnetlinkNetfilter 专用 Netlink 协议将包数据与元数据skb封装成消息发给绑定该队列号的用户态进程。用户态接收、处理、判决绑定队列用户态程序通过libnetfilter_queue库创建 Netlink 套接字绑定指定队列号监听内核消息。读取与处理接收内核发来的数据包可解析、修改、丢弃如检测恶意流量、篡改内容。返回判决处理完后通过 Netlink 回复判决Verdict核心类型NF_ACCEPT放行内核继续转发 / 接收NF_DROP丢弃内核释放包NF_REPEAT重新入队支持修改包内容后重注入整体数据流网卡 → 内核Netfilter钩子 → 匹配iptables规则 → NFQueue入队 → nfnetlink → 用户态程序(libnetfilter_queue) → 判决返回 → 内核执行(放行/丢弃)关键特性多队列隔离支持 0~65535 队列可按流量类型TCP/UDP/ 端口分队列负载均衡 隔离。零拷贝模式通过queue.set_mode_copy(0xffff)设置内核直接传递包指针避免全量拷贝提升吞吐。异步判决用户态可乱序返回判决内核按packet_id匹配处理灵活适配复杂逻辑。队列容量限制队列满时新包会被内核直接丢弃防止内存溢出用户态需及时处理避免积压。内核模块依赖确保加载nfnetlink、nf_queuemodprobe nfnetlink modprobe nf_queuenfnetlinknfnetlink是Netfilter 专用的 Netlink 协议是内核 - 用户态 的通信总线NFQueue是基于nfnetlink实现的数据包队列子系统。层级应用层(libnetfilter_queue) ↓ nfnetlink 套接字(Netlink 协议族) ↓ 内核 nfnetlink 子系统 ↓ Netfilter 钩子 nf_queue 模块核心NFQueue 本质就是跑在 nfnetlink 协议上的业务逻辑。关键内核模块依赖完整依赖链nfnetlink基础 nfnetlink 核心框架nfnetlink_queueNFQueue 专属 nfnetlink 业务实现nf_queueNetfilter NFQUEUE 目标内核实现加载命令modprobe nfnetlink modprobe nfnetlink_queue modprobe nf_queue查看模块依赖lsmod | grep nfnetlinknfnetlink 与 普通 Netlink 区别普通 Netlinknfnetlink协议号自定义固定协议簇NETLINK_NETFILTER通用内核事件专为 Netfilter 子系统设计queue、conntrack、log 等单消息格式有标准nfnetlink 消息头 子系统私有载荷核心宏定义内核态// netlink 协议类型 #define NETLINK_NETFILTER 12 // nfnetlink 消息标准头 struct nfgenmsg { uint8_t nfgen_family; uint8_t version; uint16_t res_id; };NFQueue 在此基础上再封装包元数据、packet id、队列号、skb 载荷。NFQueue 基于 nfnetlink 的完整交互流程用户态创建NETLINK_NETFILTER类型 socket绑定指定queue 号设置队列模式拷贝模式 / 零拷贝阻塞读 nfnetlink 消息内核态iptables/nftables 匹配流量 - 丢入 NFQUEUEskb 挂起分配packet_id封装nfnetlink 消息头 NFQueue 私有属性 数据包内容通过 nfnetlink 广播发给绑定该队列的用户态进程用户态处理后构造 nfnetlink 判决消息accept/drop/modify发回内核内核根据 verdict 恢复数据包网络栈流转nfnetlink 消息结构NFQueue 报文整体结构Netlink 标准头 struct nlmsghdr ↓ nfnetlink 通用头 struct nfgenmsg ↓ NFQueue 私有消息头 属性TLV ↓ IP/TCP/UDP 原始数据包载荷三种工作模式通过 nfnetlink 配置通过nfq_set_mode经由 nfnetlink 下发内核NFQ_COPY_PACKET拷贝整个数据包到用户态最常用兼容所有内核。NFQ_COPY_META只发元数据不发完整包节省带宽。NFQ_COPY_NONE零拷贝内核 3.8只传指针用户态直接操作内核 skb性能最高仅限新版本内核。用户态编程底层脉络不用库裸写socket 创建socket(AF_NETLINK, SOCK_RAW, NETLINK_NETFILTER);bind 绑定 nfnetlink 组发送 nfnetlink 控制消息绑定队列、设置拷贝模式recv()收内核发来的数据包消息处理后构造 verdict 消息send()回内核实际开发没人裸写都用libnetfilter_queue封装了这套 nfnetlink 交互细节。NFQueueVerdictVerdict 就是用户态程序告诉内核这个数据包怎么处理是 NFQueue 最核心的指令。Verdict 判决结果用户态处理完数据包后通过 nfnetlink 发回内核的指令告诉内核放行丢弃修改后重入重新入队批量判决内核必须严格按照这个 verdict 执行。标准 Verdict 枚举内核定义这些是内核固定常量所有语言C/Python/Go都用这些值// 最核心 4 个 NF_ACCEPT 1 // 放行 NF_DROP 0 // 丢弃 NF_STOLEN 2 // 内核释放包用户态接管几乎不用 NF_QUEUE 3 // 重新入队Repeat NF_REPEAT 4 // 再次经过 Netfilter 钩子 NF_STOP 5 // 停止后续钩子处理实际开发只用前 2 个 1 个扩展NF_ACCEPTNF_DROPNF_REPEAT修改数据包后重处理每个 Verdict 真实作用1. NF_ACCEPT最常用含义放行继续走内核网络栈内核恢复数据包流程正常转发、接收、发送适用合法流量、检测通过的包2. NF_DROP最常用含义直接丢弃不通知发送方内核释放 skb 内存包彻底消失适用恶意流量、非法协议、黑名单 IP3. NF_REPEAT修改包必用含义修改数据包后重新送入 Netfilter 钩子处理你改了包内容IP/TCP/HTTP必须用 REPEAT 让内核重新解析否则修改不生效4. NF_QUEUE含义把包再次放入队列很少用一般用 REPEAT 替代。5. NF_STOLEN用户态完全接管数据包内核不管了几乎不用容易内存泄漏带修改数据包的 Verdict高级普通 verdict 只是决定 “放 / 丢”。但 NFQueue 最强能力是修改数据包内容 下发 Verdict格式NF_ACCEPT | NF_REPEAT内核行为使用你修改后的新数据包重新进入 Netfilter 链正常转发这是DPI、流量篡改、DNS 劫持、网页注入的核心原理。批量 Verdict内核 ≥3.1高并发场景下一个一个发判决太慢。内核支持批量判决Batch Verdict一次性返回 1~N 个包的判决大幅减少 netlink 消息数量性能提升 3~10 倍APInfq_set_verdict_batch(qh, id, verdict, count);代码示例 C 语言libnetfilter_queue// 放行 nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); // 丢弃 nfq_set_verdict(qh, id, NF_DROP, 0, NULL); // 修改数据包后 放行重处理 nfq_set_verdict(qh, id, NF_ACCEPT | NF_REPEAT, new_len, new_packet);Verdict 底层流程用户态 send() 发送 verdict packet_id ↓ nfnetlink 接收消息 ↓ 内核找到对应队列的数据包 ↓ 根据 verdict 执行动作 ↓ 释放队列资源一个 packet_id 只能发一次 verdict不能重复重复发会导致内核报错packet already lost性能与注意事项开销来源内核 - 用户态上下文切换、数据拷贝非零拷贝时、用户态处理延迟。适用场景灵活性 极致性能如 IDS/IPS、协议分析、自定义防火墙高吞吐转发不推荐。丢包风险用户态处理慢 → 队列满 → 内核丢包需优化用户态逻辑、控制队列长度。安全用户态程序需root 权限避免未授权访问网络数据。典型应用IDS/IPS如 Suricata、Snort 用 NFQueue 实现入侵检测与防御。流量过滤自定义协议过滤、恶意流量拦截。协议分析解析私有协议、深度包检测DPI。NAT 扩展复杂 NAT 逻辑下放用户态处理。拦截ip C实例功能拦截指定黑名单 IP其他 IP 直接放行使用NF_DROP/NF_ACCEPT判决。完整 C 代码nfqueue_block_ip.c#include stdio.h #include stdlib.h #include unistd.h #include netinet/in.h #include linux/netfilter.h #include linux/netfilter/nfnetlink.h #include linux/netfilter/nfqueue.h #include libnetfilter_queue/libnetfilter_queue.h #include arpa/inet.h // 黑名单 IP要拦截的IP #define BLOCK_IP 192.168.1.100 /** * 数据包回调函数内核把包发过来这里处理 */ static int callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { // 1. 获取数据包 ID 和 载荷 struct nfqnl_msg_packet_hdr *ph; unsigned char *payload; uint32_t id; int payload_len; ph nfq_get_msg_packet_hdr(nfa); id ntohl(ph-packet_id); payload_len nfq_get_payload(nfa, payload); if (payload_len 20) { // 小于IPv4最小头部长度 return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } // 2. 提取源IPIPv4 struct in_addr src_ip; src_ip.s_addr *(uint32_t *)(payload 12); // IP头偏移12字节是源IP char src_ip_str[INET_ADDRSTRLEN]; inet_ntop(AF_INET, src_ip, src_ip_str, INET_ADDRSTRLEN); printf(收到包 - 源IP: %s\n, src_ip_str); // 3. 匹配黑名单返回判决VERDICT if (strcmp(src_ip_str, BLOCK_IP) 0) { printf(└─ 拦截黑名单IP: %s → DROP\n, src_ip_str); return nfq_set_verdict(qh, id, NF_DROP, 0, NULL); } else { printf(└─ 允许通过 → ACCEPT\n); return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } } int main() { struct nfq_handle *h; struct nfq_q_handle *qh; int fd; char buf[4096]; // 打开 nfqueue 句柄 h nfq_open(); if (!h) { perror(nfq_open failed); exit(1); } // 绑定 nfnetlink if (nfq_unbind_pf(h, AF_INET) 0 || nfq_bind_pf(h, AF_INET) 0) { perror(nfq_bind_pf failed); exit(1); } // 创建队列 0 qh nfq_create_queue(h, 0, callback, NULL); if (!qh) { perror(nfq_create_queue failed); exit(1); } // 设置模式复制完整数据包 nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff); // 获取 socket fd fd nfq_fd(h); printf(NFQueue 监听队列 0拦截IP: %s\n\n, BLOCK_IP); // 循环读取数据包 while (1) { int len read(fd, buf, sizeof(buf)); nfq_handle_packet(h, buf, len); } // 清理 nfq_destroy_queue(qh); nfq_close(h); return 0; }iptables 配置把流量导入 NFQueue 队列 0# 清空已有规则 iptables -F iptables -X # 将所有流量导入队列 0 iptables -A INPUT -j NFQUEUE --queue-num 0 iptables -A FORWARD -j NFQUEUE --queue-num 0完整内核模块将流量导入 NFQueue 队列 0编写纯内核模块不使用 iptables直接在内核 Netfilter 钩子中把流量导入 NFQueue 队列 0。nfq_kernel_module.c#include linux/module.h #include linux/kernel.h #include linux/init.h #include linux/skbuff.h #include linux/netfilter.h #include linux/netfilter_ipv4.h #include net/netfilter/nf_queue.h // 钩子挂载点INPUT 方向可改 FORWARD/OUTPUT #define HOOK_POINT NF_INET_LOCAL_IN // 目标 NFQUEUE 队列号 #define NFQUEUE_NUM 0 static struct nf_hook_ops nfho; // 钩子函数将数据包入队 NFQueue static unsigned int hook_func(void *priv, struct sk_buff *skb, const struct nf_hook_state *state) { // 内核标准 API直接把数据包导入 NFQueue 队列 num return nf_queue(skb, state, NFQUEUE_NUM, 0); } // 模块加载 static int __init nfq_module_init(void) { nfho.hook hook_func; nfho.hooknum HOOK_POINT; // 挂载点 nfho.pf NFPROTO_IPV4; // IPv4 nfho.priority NF_IP_PRI_FIRST; // 最高优先级 nf_register_net_hook(init_net, nfho); printk(KERN_INFO NFQueue 内核模块加载成功流量导入队列 %d\n, NFQUEUE_NUM); return 0; } // 模块卸载 static void __exit nfq_module_exit(void) { nf_unregister_net_hook(init_net, nfho); printk(KERN_INFO NFQueue 内核模块已卸载\n); } module_init(nfq_module_init); module_exit(nfq_module_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Kernel module redirect traffic to NFQUEUE 0); MODULE_AUTHOR(Linux Kernel Dev);工作流程说明内核把数据包通过nfnetlink发给用户态程序程序提取源 IP对比黑名单匹配 → 下发NF_DROP拦截不匹配 → 下发NF_ACCEPT放行内核执行判决总结NFQueue 是内核与用户态网络处理的桥梁通过 Netfilter Netlink 实现流量劫持与自定义处理是 Linux 网络安全与流量控制的核心工具。