使用netfilter_queue重定向IP数据包
使用netfilter_queue重定向IP数据包(单向)
一、需求
使用libnetfilter_queue实现在192.168.0.92服务器2001端口收到的数据包转发至192.168.0.54服务器9000端口。
二、开发环境
OS:Debian 12
gcc :15.1.0
IP:192.168.0.92
安装libnetfilter-queue-dev开发工具包
sudo apt-get install libnetfilter-queue-dev
三、实现
主要步骤如下:
- 设置Iptables规则,将发送到本机2001端口的TCP数据包引导至用户态程序处理。
- 编写用户态程序,使用
libnetfilter_queue库接收数据包,修改目标地址和端口,然后放行。 - 处理数据包,在程序中修改IP头和TCP头,并重新计算校验和。
1、配置Iptables规则
首先,需要设置iptables规则,将目标端口为2001的tcp数据包送入NFQUEUE。假设使用队列编号为0:
# 在 mangle 表的 OUTPUT 链添加规则(针对本机发出的包) iptables -t mangle -A OUTPUT -p tcp --dport 2001 -j NFQUEUE --queue-num 0 # 在 mangle 表的 PREROUTING 链添加规则(针对转发的包) iptables -t mangle -A PREROUTING -p tcp --dport 2001 -j NFQUEUE --queue-num 0
避免循环转发,如果转发数据包再次匹配iptables规则,会导致循环。可以考虑使用数据包的mark字段来标记已处理的数据包,避免再次进入队列。
# 标记已处理的数据包,避免重复进入队列 iptables -t mangle -A PREROUTING -p tcp --dport 2001 -j MARK --set-mark 1 iptables -t mangle -A PREROUTING -p tcp --dport 2001 -m mark ! --mark 1 -j NFQUEUE
注意:
测试完毕之后,记得用下列指令删除规则:
iptables -t mangle -D
2、编写用户态程序
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <linux/netfilter.h> #include <libnetfilter_queue/libnetfilter_queue.h> // 计算校验和的辅助函数 static uint16_t checksum(uint32_t init, const uint8_t *data, size_t len) { uint32_t sum = init; while (len > 1) { sum += *((uint16_t *)data); data += 2; len -= 2; } if (len > 0) { sum += *((uint8_t *)data); } while (sum >> 16) { sum = (sum & 0xFFFF) + (sum >> 16); } return (uint16_t)~sum; } // 计算TCP校验和(需要伪头) static uint16_t tcp_checksum(struct iphdr *iph, struct tcphdr *tcph, size_t tcp_len) { uint32_t sum = 0; struct pseudo_header { uint32_t src_addr; uint32_t dest_addr; uint8_t zero; uint8_t protocol; uint16_t tcp_len; } psh; psh.src_addr = iph->saddr; psh.dest_addr = iph->daddr; psh.zero = 0; psh.protocol = IPPROTO_TCP; psh.tcp_len = htons(tcp_len); sum = checksum(0, (uint8_t *)&psh, sizeof(psh)); return checksum(sum, (uint8_t *)tcph, tcp_len); } // NFQUEUE 回调函数 static int cb(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data) { (void)nfmsg; (void)data; uint32_t id = 0; unsigned char *packet_data; int packet_len; struct nfqnl_msg_packet_hdr *ph; ph = nfq_get_msg_packet_hdr(nfa); if (ph) { id = ntohl(ph->packet_id); } // 获取整个数据包(包括IP头) packet_len = nfq_get_payload(nfa, (char **)&packet_data); if (packet_len < 0) { printf("Failed to get payload\n"); return NF_ACCEPT; } // 解析IP头和TCP头 struct iphdr *iph = (struct iphdr *)packet_data; if (iph->protocol != IPPROTO_TCP) { // 只处理TCP return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL); } size_t ip_hdr_len = iph->ihl * 4; struct tcphdr *tcph = (struct tcphdr *)(packet_data + ip_hdr_len); size_t tcp_len = ntohs(iph->tot_len) - ip_hdr_len; // 修改目标IP地址和端口 struct in_addr new_dest_addr; inet_pton(AF_INET, "192.168.0.2", &new_dest_addr); iph->daddr = new_dest_addr.s_addr; // 修改IP头中的目标IP tcph->dest = htons(9000); // 修改TCP头中的目标端口 // 重新计算IP头和TCP头的校验和 iph->check = 0; iph->check = htons(checksum(0, (uint8_t *)iph, ip_hdr_len)); // 注意网络字节序 tcph->check = 0; tcph->check = htons(tcp_checksum(iph, tcph, tcp_len)); // 注意网络字节序 // 放行修改后的数据包 return nfq_set_verdict(qh, id, NF_ACCEPT, packet_len, packet_data); } int main() { struct nfq_handle *h; struct nfq_q_handle *qh; int fd, rv; char buf[4096]; h = nfq_open(); if (!h) { fprintf(stderr, "Error during nfq_open()\n"); exit(1); } if (nfq_unbind_pf(h, AF_INET) < 0) { fprintf(stderr, "Error during nfq_unbind_pf()\n"); exit(1); } if (nfq_bind_pf(h, AF_INET) < 0) { fprintf(stderr, "Error during nfq_bind_pf()\n"); exit(1); } qh = nfq_create_queue(h, 0, &cb, NULL); // 使用队列0 if (!qh) { fprintf(stderr, "Error during nfq_create_queue()\n"); exit(1); } if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) < 0) { // 拷贝整个数据包 fprintf(stderr, "Can't set packet copy mode\n"); exit(1); } fd = nfq_fd(h); while ((rv = recv(fd, buf, sizeof(buf), 0)) >= 0) { nfq_handle_packet(h, buf, rv); } nfq_destroy_queue(qh); nfq_close(h); return 0; }
编译:
gcc -o nfq_forward nfq_forward.c -lnetfilter_queue
运行:
sudo ./nfq_forward
浙公网安备 33010602011771号