该防火墙系统具备完善的规则处理机制,能够对 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 协议发送包,目标机上又能接收到信息。