基于 Libnetfilter_queue 开发静态包过滤防火墙

该防火墙系统具备完善的规则处理机制,能够对 INPUT、OUTPUT、FORWARD 添加的规则链进行指定清理或全面清理,保证了系统的安全性和稳定性。

1 系统设计

1.1 系统的组成结构

1.1.1 命令行参数解析模块

负责解析用户通过命令行输入的参数,包括协议类型、源 IP 地址、目标 IP 地址、源端口、目标端口以及帮助信息等参数。将这些参数进行处理后,存储到相应的数据结构中,为后续的规则设置和数据包过滤提供依据。

1.1.2 iptables 规则添加与清理模块

该模块根据用户设置的过滤规则,构建相应的 iptables 命令字符串,并通过系统调用执行 iptables 命令,将符合规则的流量导向 NFQUEUE 队列。并能够对 INPUT、OUTPUT、FORWARD 添加的规则链进行指定清理或全面清理,保证了系统的安全性和稳定性。

1.1.3 NFQUEUE 初始化与处理模块

初始化 NFQUEUE 相关的句柄和队列,设置队列的工作模式(如设置为复制数据包模式),并注册处理数据包的回调函数。当有数据包进入 NFQUEUE 队列时,通过回调函数对数据包进行处理。

1.1.4 数据包解析模块

在回调函数中,对从 NFQUEUE 接收到的数据包进行解析。首先解析 IP 头,获取协议类型、源 IP 地址、目标 IP 地址等信息。然后根据协议类型(TCP 或 UDP),进一步解析相应的传输层头部(TCP 头或 UDP 头),获取源端口和目标端口等信息。

1.1.5 规则匹配模块

将解析得到的数据包信息与用户设置的过滤规则进行匹配。根据规则中对协议类型、源 IP 地址、目标 IP 地址、源端口和目标端口的要求,逐一检查数据包的对应信息是否符合规则。

1.1.6 数据包处理模块

根据规则匹配的结果,对数据包进行相应的处理。如果数据包匹配过滤规则,则将其丢弃;如果不匹配,则允许数据包通过。

1.2 数据结构及关键算法的设计

1.2.1 数据结构

(1)全局变量

static struct FilterRule rules[100]; // 存储多条过滤规则
static int rule_count = 0;           // 当前规则数量
static int queue_num = 0;            // NFQUEUE队列号

(2)结构体

struct FilterRule
{
    uint8_t protocol;     // 协议类型:IPPROTO_TCP或IPPROTO_UDP
    struct in_addr src_ip; // 源IP(二进制形式)
    struct in_addr dst_ip; // 目标IP(二进制形式)
    uint16_t src_port;     // 源端口(网络字节序)
    uint16_t dst_port;     // 目标端口(网络字节序)
};

struct option
{
    const char *name;    // 长选项的名称(如"--protocol")
    int has_arg;         // 是否需要参数(required_argument, optional_argument, no_argument)
    int *flag;           // 用于返回值的标志(通常为0)
    int val;             // 短选项的字符(如'p')
};
1.2.2 关键算法的设计

(1)匹配过滤规则
根据数据包的源 IP,目的 IP,目的端口以及协议来判断是否符合设置好的规则

int match_rule(struct iphdr *ip_header, struct tcphdr *tcp_header, struct udphdr *udp_header)
{
    for (int i = 0; i < rule_count; i++)
    {
        struct FilterRule *rule = &rules[i];

        // 匹配源IP
        if (rule->src_ip.s_addr != 0 && rule->src_ip.s_addr != ip_header->saddr)
        {
            continue; // 源IP不匹配
        }

        // 匹配目的IP
        if (rule->dst_ip.s_addr != 0 && rule->dst_ip.s_addr != ip_header->daddr)
        {
            continue; // 目的IP不匹配
        }

        if (ip_header->protocol != IPPROTO_TCP && ip_header->protocol != IPPROTO_UDP)
        {
            return 0; // 只匹配TCP和UDP协议
        }

        // 匹配目的端口(TCP或UDP)
        if (rule->dst_port != 0)
        {
            if ((ip_header->protocol == IPPROTO_TCP && rule->dst_port != tcp_header->dest) ||
                (ip_header->protocol == IPPROTO_UDP && rule->dst_port != udp_header->dest))
            {
                continue; // 目的端口不匹配
            }
        }

        return 1; // 规则匹配
    }

    return 0; // 无规则匹配
}

(2)回调函数
解析 IP 层数据包,获得该包的源、目的 IP 地址、采用的协议,调用规则匹配函数,判断包是否要被拦截

static int callback(struct nfq_q_handle *qh, struct nfgenmsg *nfmsg, struct nfq_data *nfa, void *data)
{
    printf("Packet received");
    struct iphdr *ip_header;
    struct tcphdr *tcp_header = NULL;
    struct udphdr *udp_header = NULL;
    unsigned char *packet_data;
    int packet_len;
    uint32_t id = 0; // 数据包的ID

    // 从nfq_data中获取有效载荷
    packet_len = nfq_get_payload(nfa, &packet_data);
    if (packet_len < 0)
    {
        fprintf(stderr, "Failed to get packet payload\n");
        return NF_ACCEPT;
    }

    // 获取数据包的ID
    struct nfqnl_msg_packet_hdr *ph = nfq_get_msg_packet_hdr(nfa);
    if (ph)
    {
        id = ntohl(ph->packet_id);
    }

    // 解析IP头
    ip_header = (struct iphdr *)packet_data;

    // 解析传输层头(TCP/UDP)
    if (ip_header->protocol == IPPROTO_TCP)
    {
        tcp_header = (struct tcphdr *)(packet_data + (ip_header->ihl << 2));
    }
    else if (ip_header->protocol == IPPROTO_UDP)
    {
        udp_header = (struct udphdr *)(packet_data + (ip_header->ihl << 2));
    }

    // 匹配过滤规则
    if (match_rule(ip_header, tcp_header, udp_header))
    {
        write_log("DROP", ip_header, tcp_header, udp_header); // 记录拦截日志
        printf("Dropping packet ID: %u\n", id);               // 打印拦截的包ID
        return nfq_set_verdict(qh, id, NF_DROP, 0, NULL);   // 丢弃数据包
    }

    write_log("ACCEPT", ip_header, tcp_header, udp_header); // 记录放行日志
    printf("Accepting packet ID: %u\n", id);                // 打印放行的包ID
    return nfq_set_verdict(qh, id, NF_ACCEPT, 0, NULL);    // 放行数据包
}

(3)初始化 NFQUEUE

void setup_nfqueue()
{
    struct nfq_handle *h;
    struct nfq_q_handle *qh;

    // 打开NFQUEUE句柄
    h = nfq_open();
    if (!h)
    {
        fprintf(stderr, "Failed to open NFQUEUE handle\n");
        exit(EXIT_FAILURE);
    }

    // 创建队列并注册回调函数
    qh = nfq_create_queue(h, queue_num, &callback, NULL);
    if (!qh)
    {
        fprintf(stderr, "Failed to create NFQUEUE queue\n");
        nfq_close(h);
        exit(EXIT_FAILURE);
    }

    // 设置接收缓冲区大小
    if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xFFFF) < 0)
    {
        fprintf(stderr, "Failed to set NFQUEUE mode\n");
        nfq_destroy_queue(qh);
        nfq_close(h);
        exit(EXIT_FAILURE);
    }

    // 获取文件描述符并进入主循环
    int fd = nfq_fd(h);
    char buffer[4096] = {0};
    int rv;

    printf("NFQUEUE is running. Processing packets...\n");
    while ((rv = recv(fd, buffer, sizeof(buffer), 0)) >= 0)
    {
        nfq_handle_packet(h, buffer, rv); // 处理数据包
    }

    // 清理NFQUEUE
    nfq_destroy_queue(qh);
    nfq_close(h);
}

(4)添加规则

void add_rule()
{
    struct FilterRule rule;
    memset(&rule, 0, sizeof(rule)); // 初始化规则结构体

    char input[100];

    // 解析协议类型
    printf("Enter protocol (tcp/udp): ");
    scanf("%s", input);
    if (strcmp(input, "tcp") == 0)
    {
        rule.protocol = IPPROTO_TCP;
    }
    else if (strcmp(input, "udp") == 0)
    {
        rule.protocol = IPPROTO_UDP;
    }
    else
    {
        fprintf(stderr, "Invalid protocol: %s\n", input);
        return;
    }

    // 解析源IP → 转为二进制
    printf("Enter source IP: ");
    scanf("%s", input);
    if (inet_pton(AF_INET, input, &rule.src_ip) != 1)
    {
        fprintf(stderr, "Invalid source IP: %s\n", input);
        return;
    }

    // 解析目标IP
    printf("Enter destination IP: ");
    scanf("%s", input);
    if (inet_pton(AF_INET, input, &rule.dst_ip) != 1)
    {
        fprintf(stderr, "Invalid destination IP: %s\n", input);
        return;
    }

    // 解析目标端口
    printf("Enter destination port: ");
    scanf("%s", input);
    port = 0 port = strtoul(input, NULL, 10);
    if (port < 1 || port > 65535)
    {
        fprintf(stderr, "Invalid port: %s (must be 1-65535)\n", input);
        return;
    }
    rule.dst_port = htons((uint16_t)port);

    // 添加规则到规则数组
    rules[rule_count] = rule;
    rule_count++;

    printf("Rule added successfully!\n");
}

2 系统实现

2.1 开发环境

实验用虚拟机的 docker 环境

2.2 系统功能展示

2.2.1 添加 TCP 规则

添加 TCP 规则的相关命令及显示信息。

2.2.2 验证规则是否生效

(1)telnet <目标地址>,显示相关拦截信息。
(2)ping 目标机,显示能够 ping 通的信息。

2.2.3 添加 UDP 规则

(1)添加防火墙规则前能够利用 nc 命令建立连接。
(2)添加防火墙规则后输入相同的命令,目标机上不再输出内容。
(3)在规则删除后再利用 udp 协议发送包,目标机上又能接收到信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值