使用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號