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

 

posted @ 2025-11-02 13:16  钟齐峰  阅读(9)  评论(0)    收藏  举报