简介:基于费尔防火墙 1.0 的源代码压缩包是一份面向网络安全开发者、高校学生及防火墙技术爱好者的学习资料,旨在帮助读者从底层理解防火墙的包过滤、规则控制与异常行为处理逻辑。压缩包仅约529KB,内部包含核心源码、功能说明文档(如“说明.htm”)以及指向代码中国社区的文本和网址快捷方式,整体体积小巧却覆盖了从代码研读到社区交流的完整路径。已有523人浏览学习,适合具备一定编程基础、希望深入安全工具实现细节的读者。通过阅读源码可以掌握恶意流量检测与拦截的具体实现,借助说明文档能快速了解功能配置与排错思路,利用附带网址还能获取编程与防火墙开发的共享经验,为后续优化或自主开发安全工具打下扎实基础。
1. 防火墙源代码.zip 该不该碰:一个压缩包值不值得你花两周时间
一份防火墙源代码.zip,解压之后到底是能直接 make 的工程,还是一堆散落的文档加半成品,其实打开的前十分钟就能判断出来。但真正值得你先想清楚的是另一件事:你拿它是为了毕业设计交差,还是为了在企业内网做一道透明的包过滤网关。目标不同,读代码的路径完全不同。这类 zip 里最常见的形态,是用户态的 libpcap 抓包加规则匹配,或者 Linux Netfilter 钩子做内核态过滤,前者编译门槛低,后者性能好但依赖内核版本。适合谁:想从零读懂一套防火墙实现的学生,以及需要在实验室或办公网做透明审计网关、又不想被商用硬件设备绑定的一线运维。别急着找密码,先花十分钟判断它值不值得你投入。
2. 解包与代码结构分析:从 zip 压缩包定位到可编译的工程目录
2.1 用 unzip 与 7z 安全解包:编码、权限和伪加密先扫一遍
拿到任何 zip,我的习惯是先看包内容清单,而不是直接解压。原因很直接:zip 里可能套着多层目录,也可能带着 Windows 下的中文文件名,不解压看一眼清单能提前知道该用哪个解压参数。
file 防火墙源代码.zip unzip -l 防火墙源代码.zip | head -40file先确认文件类型,有些下载站会把 zip 包改名或者加一段 HTTP 尾巴,导致 unzip 直接报 "not a zip archive";unzip -l只列目录不解压,适合快速看结构。如果看到中文文件名变成一团乱码,说明压缩时用的是 GBK 编码,Linux 默认按 UTF-8 解会出现文件名单个字符乱码。
mkdir -p fw-src unzip -O gbk 防火墙源代码.zip -d fw-src-O gbk是强制把压缩包内的文件名按 GBK 解码;如果你的 unzip 版本不支持-O参数(某些精简版确实不支持),退而求其次可以装 p7zip:
sudo apt-get install -y p7zip-full 7z x 防火墙源代码.zip -ofw-src7z 对中文编码的处理更宽容,只是解压出来的权限位需要重新整理,后面编译前建议统一执行chmod -R a+rX fw-src。
解压之后先扫一遍有没有伪加密。伪加密是 zip 格式里一个典型的脏手段:文件条目里加密标志位被置 1,但实际数据区并没有真正加密,用解压工具会一直提示输密码,用 Python 却能空密码读出来。这种包常见于某些资源分享场景,用来逼你去找"密码",其实数据本身不设防。判断方法很简单:
import zipfile z = zipfile.ZipFile("防火墙源代码.zip") for info in z.infolist(): try: data = z.read(info.filename, pwd=b"") print(f"[伪加密或空密码] {info.filename} -> {len(data)} bytes") except RuntimeError as e: print(f"[需要密码] {info.filename}: {e}")脚本逻辑是按pwd=b""去读每一个条目:如果空密码能读出来,说明加密标志位是虚的,属于伪加密,解压时不需要理会提示;如果抛RuntimeError,才是真加密。真加密的 zip 在密码遗忘时只能走字典或暴力恢复,耗时通常在数天量级,遇到这种情况我一般直接放弃解密,不值得投入。
2.2 识别工程类型:Makefile、CMakeLists 还是内核模块
同一个 zip 里能装完全不同的工程形态。我一般用一条 find 把目录结构拉出来,三十秒就能判断作者用的构建体系。
find fw-src -maxdepth 3 -type f | sed 's|fw-src/||' | sort | head -120maxdepth 3是为了避免把整个依赖树都打出来,头一百二十个文件足够看到主要结构。接着用file检查构建文件类型:
file fw-src/Makefile fw-src/CMakeLists.txt 2>/dev/null file fw-src/Kconfig 2>/dev/null这里会出现三种典型结果,应对思路完全不同。第一种是根目录有 Makefile 且包含cc、gcc开头的编译规则,这是经典的 C 工程,直接./configure && make或者make就能编;第二种是 CMakeLists.txt,需要cmake -B build && cmake --build build;第三种是 Kconfig 加 Makefile 且内部出现obj-m,这是 Linux 内核模块,必须放在内核源码树里按模块方式编译,不能拿系统 gcc 直接编。
内核模块的判断还要看目录名。常见套路是net/ipv4/netfilter/或者netfilter/下面的扩展,Makefile 里写一行:
obj-m += fw_netfilter.o这类模块的编译命令和普通工程完全不一样:
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules-C指定进入内核构建目录,M=$(pwd)告诉内核编译系统去当前目录找目标文件,modules目标生成.ko文件。这里最容易踩的坑是内核头文件没装,/lib/modules/$(uname -r)/build指向的路径不存在,后面避坑章节会展开。
除了构建文件,还要注意代码里有没有#include <pcap/pcap.h>。如果出现这个头文件,说明捕获层依赖 libpcap,编译前要先装libpcap-dev。如果是内核模块,依赖的是<linux/skbuff.h>、<linux/netfilter.h>这类内核头文件。
2.3 先读 README 和默认配置:理解作者的设计意图
任何 zip 里最容易被忽略的文件就是 README。我见过好几个工程师拿到源码先去看代码,花了半天才意识到工程里根本没有 Makefile,编译入口在 README 里写的是make -f build.mk。
head -120 fw-src/README.md 2>/dev/null || head -120 fw-src/README 2>/dev/nullREADME 里优先看四个信息:编译依赖和版本要求、默认监听网卡和 IP 段、规则文件的语法样例、以及加载和卸载方式。如果 README 缺这些,就去翻conf/、etc/目录下的示例配置文件。
find fw-src -name "*.conf" -o -name "*.example" -o -name "*.rules" | head -20配置示例能反映作者的真实使用场景:如果默认配置里监听的是eth0,说明这个防火墙原本跑在单网卡的主机上做本地防护;如果出现br0、tap0这类名字,说明原来就是透明桥接方案;如果出现ppp0,多半是给拨号服务器做流量控制用的。这些细节会直接影响你部署时的网卡规划,后面第 4 章部署步骤会回到这里。
读配置时还要顺手确认规则匹配语义。很多"源代码.zip"里自带的规则解析器只支持简单的五元组匹配,没有 conntrack 状态跟踪,这种代码拿去做双向 NAT 或 FTP 这类需要动态开放端口的协议会直接翻车。如果 README 里明确写了"仅支持无状态过滤",那就要降低预期,或者按第 6 章的方式补一个会话表。
3. 核心模块拆解:捕获、过滤规则与状态会话表如何协同工作
3.1 捕获层:libpcap 与原始套接字两条路,先看清楚它走哪条
防火墙源代码最底层的模块是数据包捕获。常见实现有两种:调用 libpcap 库在用户态抓包,或者自己创建 AF_PACKET 原始套接字。判断方法很简单,看代码里有没有pcap_open_live或者socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))。
libpcap 的优点是跨平台、自动处理链路层头部,适合快速开发和验证;原始套接字则省掉一层库依赖,更贴近内核,但链路层解析要自己写,常见的翻车点是没处理 VLAN 标签,抓到 802.1Q 的包解析出来的 IP 地址全是错的。下面这段是典型的 libpcap 捕获循环:
#include <pcap.h> #include <netinet/ip.h> #include <arpa/inet.h> void packet_handler(u_char *user, const struct pcap_pkthdr *hdr, const u_char *packet) { struct iphdr *ip = (struct iphdr *)(packet + 14); /* 跳过以太网头 */ char src[16], dst[16]; if (ip->version != 4) return; inet_ntop(AF_INET, &ip->saddr, src, sizeof(src)); inet_ntop(AF_INET, &ip->daddr, dst, sizeof(dst)); printf("%s -> %s proto=%d\n", src, dst, ip->protocol); } int main(int argc, char **argv) { pcap_t *handle; char errbuf[PCAP_ERRBUF_SIZE]; handle = pcap_open_live("eth1", 65535, 1, 1000, errbuf); if (!handle) { fprintf(stderr, "pcap_open_live: %s\n", errbuf); return 1; } pcap_loop(handle, -1, packet_handler, NULL); pcap_close(handle); return 0; }pcap_open_live的四个参数分别是网卡名、snaplen(抓包长度,65535 表示抓完整包)、promisc(1 为混杂模式,接收所有经过网卡的包)、超时毫秒数(1000 表示缓冲区最多等 1 秒)。这里要特别留意packet + 14:如果网卡开了 VLAN 摘除或者抓包口是 Linux 的 veth,偏移量会变成 18,解析前最好按hdr->len再兜底判断一下。
如果是 AF_PACKET 原始套接字,代码形态基本长这样:
int fd = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); struct sockaddr_ll sll = {0}; sll.sll_family = AF_PACKET; sll.sll_ifindex = if_nametoindex("eth1"); sll.sll_protocol = htons(ETH_P_ALL); bind(fd, (struct sockaddr *)&sll, sizeof(sll));sll_ifindex决定从哪个网卡收包,sll_protocol设成ETH_P_ALL表示接收所有以太网协议类型。两种方式各有适用场景,如果是网桥透明模式,我强烈建议用 libpcap 起步,后续再根据吞吐需求决定是否下沉到内核模块。
3.2 过滤引擎:从规则解析到 verdict 判定的完整链路
过滤引擎是防火墙的核心。无论是华为还是锐捷的商用防火墙配置命令,底层理念都一样:拿每条规则和包的五元组做匹配,匹配到了就返回动作(允许或丢弃),匹配不到就走默认策略。源代码里的实现通常是一个字符串解析器加一个匹配循环。
struct fw_rule { uint32_t src_ip, src_mask; uint32_t dst_ip, dst_mask; uint16_t sport, dport; uint8_t proto; /* 0 表示任意 */ int action; /* 0 drop, 1 accept */ }; static int filter_run(const struct iphdr *ip, uint16_t sport, uint16_t dport) { for (int i = 0; i < g_rule_count; i++) { struct fw_rule *r = &g_rules[i]; if ((ntohl(ip->saddr) & r->src_mask) != r->src_ip) continue; if ((ntohl(ip->daddr) & r->dst_mask) != r->dst_ip) continue; if (r->proto && ip->protocol != r->proto) continue; if (r->sport && sport != r->sport) continue; if (r->dport && dport != r->dport) continue; return r->action; } return 0; /* 默认丢弃 */ }匹配顺序非常关键。这段代码是 first-match 语义:规则列表前面命中优先,所以"放行白名单"必须放在"兜底拒绝"之前。常见误用是把这个顺序搞反,把deny all放在了最前面,结果所有流量都进不来,然后去查网卡配置、查路由表,折腾半天,其实只是规则顺序的问题。这也是黑白名单配置里最容易翻车的一个点。
规则解析器通常是把rules.conf里的文本行拆成 token,再填充到上面这个结构体。解析时要注意端口段写法,很多源码只支持单端口,不支持dport=80,443这种集合。如果你拿到的是这种简化版,建议在第 6 章改造时顺手把它升级成端口段匹配。
3.3 状态会话表:为什么无状态过滤扛不住真实流量
无状态过滤只能做单向判断。一个 TCP 连接建立后,回程包的方向是从服务器到客户端,如果规则只写了"允许客户端访问服务器 80 端口",回程的 SYN-ACK 和 ACK 包会被当成新连接拒绝掉。这就是为什么要加状态会话表:记录正向连接的五元组,允许反向流量匹配会话记录直接通过。
struct session { uint32_t saddr, daddr; uint16_t sport, dport; uint8_t proto; uint32_t expire; /* 到期时间戳 */ struct session *next; }; static struct session *session_lookup(struct session *head, uint32_t saddr, uint32_t daddr, uint16_t sport, uint16_t dport) { time_t now = time(NULL); for (struct session *s = head; s; s = s->next) { if (s->saddr == saddr && s->daddr == daddr && s->sport == sport && s->dport == dport && now < s->expire) { return s; } } return NULL; }expire的取值直接关系到长连接稳定性。TCP 连接通常设 300 到 600 秒,UDP 设 60 秒左右;对心跳类长连接,比如物联网设备上报或者即时通讯保活,会话过期时间太短会导致中间的 keepalive 包重新走规则匹配,如果规则恰好只放行了新建连接,就会出现"连接一会儿就断"的间歇性故障。这也是为什么心跳包重传的逻辑要跟会话超时对齐,不能各设各的。
没有会话表的无状态过滤还有一个致命问题:动态端口协议。FTP 主动模式的 data 连接端口是协商出来的,固定规则根本没法提前放开。商用防火墙靠应用层网关和会话表配合解决,源代码方案里如果找不到对应的助手模块,就只能对这个协议做特殊处理,或者干脆只支持被动模式并限制端口范围。
4. 编译与部署:把源代码变成双网卡透明防火墙的完整步骤
4.1 编译前依赖检查:内核头文件、libpcap 与编译器版本
拿到源码直接敲 make,十有八九会卡在缺依赖上。我一般先一次性装齐再编译,省得来回折腾。
sudo apt-get update sudo apt-get install -y build-essential libpcap-dev linux-headers-$(uname -r)build-essential提供 gcc 和 make,libpcap-dev提供 pcap.h 及链接库,linux-headers-$(uname -r)是内核模块编译的必需依赖,$(uname -r)会自动匹配当前内核版本。如果你的代码是内核模块,还要确认当前内核和头文件版本一致,用uname -r和/lib/modules/下的目录对比一下,版本对不上,编译出来的 .ko 加载时会报invalid module format。
依赖装齐后,看构建体系选择编译命令。最常见两种:
# 经典 autotools 或手写 Makefile 工程 ./configure --prefix=/usr/local --with-pcap make -j$(nproc) sudo make install# CMake 工程 cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build -j$(nproc) sudo cmake --install build--with-pcap这类 configure 开关是作者自己定义的编译选项,具体参数要看./configure --help输出,不要照抄。-j$(nproc)让编译用满 CPU 核数,如果编译时内存不足,可以改成-j2保守一点。CMake 的-DCMAKE_BUILD_TYPE=Release决定了优化级别,内核模块一般不适用 CMake,按第 2 章的make -C /lib/modules/... M=... modules来。
编译产物确认一下位置。用户态程序通常在src/或根目录生成一个可执行文件,名字可能是fw、fwctl、firewall;内核模块生成.ko文件,需要insmod加载。
4.2 双网卡桥接与转发模式:透明网关的搭建步骤
透明防火墙的部署方式和路由模式完全不同。路由模式要改网关、加静态路由;透明桥接模式是把防火墙像一根网线一样插进网络路径,不需要改任何终端的网关配置。常见做法是用 Linux bridge 把两个网卡桥接起来。
sudo ip link add name fwbr type bridge sudo ip link set fwbr up sudo ip link set eth1 master fwbr sudo ip link set eth2 master fwbr sudo ip link set eth1 promisc on sudo ip link set eth2 promisc on如果系统还保留旧的 bridge-utils 工具,也能用brctl addbr fwbr和brctl addif fwbr eth1 eth2达到同样效果,但新系统上ip命令更标准。四条ip link set的语义分别是:创建 bridge 设备、把 eth1 加入桥、把 eth2 加入桥;promisc on让网卡进入混杂模式,保证所有经过桥的流量都能被网卡接收,再交给上层的过滤程序处理。
如果是内核模块形态的防火墙,桥接之后还要确认 IP 转发开关。Netfilter 钩子在转发路径上,net.ipv4.ip_forward必须为 1,否则包不会进入转发链。
sysctl -w net.ipv4.ip_forward=1 cat /proc/sys/net/ipv4/ip_forward这里有一个细节:在 H3C HCL 模拟器里做 RBM 加 VRRP 的高可用方案,和 Linux 桥接软防火墙解决的是不同层面的问题——设备级高可用是拓扑冗余,而源代码防火墙解决的是流量可见性和策略自定义。把两者的概念混在一起,很容易在架构方案评审时被问住。
4.3 规则文件语法与黑白名单下发
编译产物里通常带一个命令行工具或者直接读配置文件。配置文件语法没有统一标准,我见过的大多数自定义防火墙用的是类似下面这种格式:
# rules.conf 示例,常用端口在前面先放行 # 前面是具体匹配,最后一条兜底 drop src=10.0.0.0/8 proto=icmp allow src=192.168.1.0/24 dst=192.168.10.0/24 proto=tcp dport=22,80,443 drop dst=any proto=any配置解释顺序是从上往下,first-match 命中即停。黑白名单在配置文件里的体现就是这条语义:要维护黑名单,就把敏感源 IP 段放在最上面;要维护白名单,就把"内部网段到关键服务"的 allow 规则前置,最后用drop any兜底。华为、锐捷的命令行防火墙也是同样的理念,只是它们的配置入口是security-policy或rule命令块,参数不是文件的文本行。
写好后通过管理工具下发:
./fwctl -f /etc/fwrules.conf ./fwctl --status-f指定配置文件路径,--status查看当前规则条数和命中计数。如果工具没有 status 子命令,可以看它有没有打印统计数据,或者直接看代码里g_rule_count对不对得上配置行数。
如果原始代码没有命令行工具,只有规则结构体,那就要自己写一个加载函数。第 6 章会演示怎么把规则文件热加载进去,这里先不过度展开。
5. 从源码到线上环境的避坑清单:五个必踩的翻车点
5.1 现象:解压后文件名为乱码,且 zip 一直提示要密码
拿到一个 zip,解压时提示输入密码,试了常见弱密码都进不去,同时文件名显示一串乱码,看起来在 Linux 下面根本没法正常使用。这时候要分两层看:文件名乱码是编码问题,密码提示则可能是伪加密——两者混在一起最容易让人误判成"这个包坏了"。
原因在于 Windows 下压缩的 zip 文件名是 GBK 编码,Linux 默认按 UTF-8 解析;而伪加密是 zip 条目里的 encryption flag 被置位,实际数据区没有真正加密,多数解压工具只看 flag 就要求密码。
解决方式是先用第 2 章的 python 脚本读一遍pwd=b"",如果能读出内容,说明伪加密,直接用7z x -y 防火墙源代码.zip强制解压,忽略密码提示;如果确认是真加密又丢了密码,就只能做字典恢复,时间成本极高,不如放弃这份资源,换一个来源再找同类代码。
5.2 现象:编译报错,'struct sk_buff' has no member named 'nh'
内核模块编译时报这种错,说明代码用的是老版本内核 API,现在的内核里sk_buff结构体已经重组成 network header 的统一访问方式,nh联合体被删掉了。
原因:Linux 2.6.22 之后,网络头部的访问从直接结构体成员改成了访问函数。老代码写的是skb->nh.iph,新内核要求用skb_network_header(skb)配合ip_hdr(skb)。同类的报错还有skb->mac.raw,也是同一个结构体重组的产物。
解决方法是把这类访问全部改成新 API:
/* 旧写法 */ struct iphdr *iph = skb->nh.iph; /* 新写法 */ struct iphdr *iph = ip_hdr(skb);如果是老内核上编的模块拿到新系统用,报错内容会更难查,比如Unknown symbol或者disagrees about version of symbol。这时用modinfo看模块的 vermagic 标记,和当前uname -r对比,不一致就直接放弃旧模块,在源码层面做 API 迁移,别在 insmod 参数上浪费时间。
5.3 现象:规则显示已下发,但流量就是不通,而且只丢一个方向
规则说放行了 192.168.1.0/24 到 10.0.0.0/8 的 80 端口,从抓包看请求包确实到了 eth1,但 eth2 上没有任何转发动作。这种问题最容易让人怀疑代码本身,其实更多是底层转发链路的问题。
原因:在桥接模式下,Linux 的 bridge 不自动转发 IP 流量,数据包进入 bridge 后要走内核的 FORWARD 链,这个链被 iptables 的 filter 表默认规则拦截。还有一个隐蔽坑:某些发行版加载了br_netfilter模块,会让 bridge 上的包同时经过 iptables 的 FORWARD 链,防火墙自带的过滤规则和 iptables 的规则相互交叉,规则放行了但 iptables 又丢了,两边都在拦。
解决方式是先看清是谁在拦。用 LOG 规则辅助定位,同时检查br_netfilter是否被加载。
sudo iptables -I FORWARD 1 -j LOG --log-prefix "FW-DROP: " sudo tail -f /var/log/kern.log | grep FW-DROP lsmod | grep br_netfilter如果日志里大量出现FW-DROP,说明是 iptables 默认策略在丢包,用iptables -P FORWARD ACCEPT或补一条-J ACCEPT规则放开。如果br_netfilter在加载列表里且不是业务需要,就rmmod br_netfilter去掉这层干扰。这里的排查顺序也顺带回答了另一个常见疑问:流量不通时别急着关防火墙,先开日志看是哪个环节丢的,关掉整层防护只会让问题更不可见。
5.4 现象:双网卡桥接后丢包严重,交换机 MAC 表不断抖动
桥接模式丢包,tcpdump 看每个包都在,但延迟忽高忽低,交换机日志里全是 MAC flapping。
原因:两个网卡的 MAC 地址都暴露在同一个二层域里,交换机看到同一 MAC 在不同端口出现,会反复刷新 MAC 表,导致部分帧被错误泛洪或丢弃。尤其是两个网卡接的是同一台交换机的不同端口时,这个现象最明显。
解决办法是给两个网卡设置不同的 MAC 地址,避免桥两侧的源 MAC 互相干扰。网桥本身也要有独立的 MAC:
sudo ip link set eth1 address 00:11:22:33:44:55 sudo ip link set eth2 address 00:11:22:33:44:66 sudo ip link set fwbr address 00:11:22:33:44:77如果交换机支持端口隔离(port isolation),也可以把这两个端口设为互相隔离只向上联口转发,彻底解决 MAC 抖动。做链路层透明设备时,这一步通常要在部署文档里明确写出来,否则上线即丢包,很容易误判成防火墙性能问题。
5.5 现象:重启后防火墙规则全部丢失,每次都要重新配置
配置完规则、验证通过,结果机器一重启,规则和转发开关全回到默认状态,流量直接裸奔。很多新手会在"防火墙每次关机重启后都开启怎么回事"上反复纠结,其实原因只有一个:规则没有持久化,sysctl 没有持久化,或者程序没有注册成服务。
解决方法是三件事一起做。第一,把 IP 转发写进/etc/sysctl.conf:
grep -q "^net.ipv4.ip_forward" /etc/sysctl.conf || \ echo "net.ipv4.ip_forward=1" | sudo tee -a /etc/sysctl.conf sysctl -p第二,把规则加载写成 systemd 服务,确保网络就绪后再执行:
[Unit] Description=Custom Firewall Service After=network-online.target Wants=network-online.target [Service] Type=oneshot ExecStart=/usr/local/bin/fwctl -f /etc/fwrules.conf ExecStart=/usr/bin/sysctl -w net.ipv4.ip_forward=1 ExecStart=/usr/bin/ip link set eth1 promisc on RemainAfterExit=yes [Install] WantedBy=multi-user.target第三,systemctl enable让服务开机自启:
sudo systemctl daemon-reload sudo systemctl enable fw.service sudo systemctl start fw.serviceType=oneshot表示这是一次性任务没有守护进程,RemainAfterExit=yes让服务状态在命令执行完后仍标记为 active。做完这三步,重启后规则自动生效。如果还丢,就journalctl -u fw.service看自启执行时报了什么错。
6. 改造进阶:给源码加日志审计与规则热加载,并用测试包验证
6.1 加日志审计:把丢弃的包记录成结构化行
代码里的过滤函数命中丢弃动作时,目前大多是静默丢掉,这在线上是没法用的。我一般会给命中 drop 的分支补一个日志函数,把时间、来源、目的、端口写进文件。注意写日志时应该用O_APPEND追加模式,多线程回调时避免互相覆盖。
static void log_drop(const struct iphdr *ip, uint16_t sport, uint16_t dport) { FILE *fp = fopen("/var/log/fw_audit.log", "a"); if (!fp) return; char saddr[16], daddr[16]; inet_ntop(AF_INET, &ip->saddr, saddr, sizeof(saddr)); inet_ntop(AF_INET, &ip->daddr, daddr, sizeof(daddr)); fprintf(fp, "%ld %s:%u -> %s:%u proto=%d action=drop\n", time(NULL), saddr, sport, daddr, dport, ip->protocol); fclose(fp); }fopen(..., "a")的追加模式在单条记录写入上不会互相覆盖;如果要扛高并发,把 FILE 指针做成全局并在写之前加锁。日志按行写,方便接 Filebeat 或者直接用 awk 统计被拦最多的来源 IP。
6.2 规则热加载:用 SIGHUP 重新读取配置文件
线上调整黑白名单最忌讳重启防火墙,改一条规则需要维护窗口。给程序加一个信号处理,收到 SIGHUP 就重新加载规则文件:
static volatile sig_atomic_t g_reload = 0; void on_sighup(int sig) { g_reload = 1; } /* 主循环里收包,每 1 秒检查一次 */ if (g_reload) { load_rules("/etc/fwrules.conf"); g_reload = 0; }改完配置就kill -HUP <pid>,规则热生效,会话表不用动。比直接重启的好处是已建立的连接不会断,这在生产环境维护黑白名单时是刚需。
6.3 验证方法:用 tcpreplay 和 scapy 构造定向流量
代码改完必须验证,不能只靠 curl。用 tcpreplay 重放抓好的 pcap,或者用 scapy 构造特定五元组的测试包,观察日志里的命中记录:
sudo tcpreplay -i eth1 --pps=500 test_tcp_80.pcapfrom scapy.all import Ether, IP, TCP, sendp pkt = Ether() / IP(src="192.168.1.99", dst="10.0.0.5") / TCP(sport=12345, dport=80) sendp(pkt, iface="eth1")--pps=500是发包速率限制,避免一次把缓冲区打满;scapy 的sendp在二层发送,所以包会带上 Ether 头,直接走 eth1 出去。发完之后去/var/log/fw_audit.log看有没有对应的 drop 记录,再去对端网卡 eth2 上 tcpdump,确认没有转发,双向都验证过才算闭环。这些年我接手过不少"拿来的防火墙源码",真正能用住的不是把编译跑通那一刻,而是把它拆开、补上日志、改成自己能维护的状态。对网上这份防火墙源代码.zip 也是同样态度:先判断工程类型和过滤语义,再决定点亮哪些能力。能编译过只是起点,能说出规则怎么匹配、会话怎么过期、日志往哪写、重启后怎么恢复,这套源码才算真正属于你了。希望帮到你。
本文还有配套的精品资源,点击获取