ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于Netfilter与Qt的Linux内核防火墙设计与实现

基于Netfilter与Qt的Linux内核防火墙设计与实现 简介基于Netfilter框架的防火墙设计与实现是一套面向Linux内核网络编程与Qt桌面开发学习者的完整项目资料既适合作为毕业设计、课程设计或大作业也适合初学者从零理解Netfilter钩子机制。项目底层在内核3.13中实现一个驱动模块通过注册nf_hook钩子函数建立自定义过滤规则对来自应用层的IP地址和端口进行拦截处理上层则基于Qt5搭建可视化操作界面用户可直接输入需要过滤的IP与端口直观感受从界面配置到底层网卡数据包过滤的完整链路。压缩包共19个文件、总大小927KB主要包含C/Qt源码cpp、h、ui、C语言驱动源码、pro工程文件、已编译内核模块、Makefile构建脚本以及docx格式的用户手册和README说明结构清晰可支撑从驱动编译、模块加载到Qt界面运行调试的整套流程。已有203人学习下载技术路线完整、代码注释与文档配套读者可据此快速搭建环境、复现内核态与用户态联动的防火墙原型并在此基础上扩展自定义规则或管理功能。1. 基于 Netfilter 框架的防火墙不依赖 iptables自己挂钩子拦 IP 和端口先说结论这套资源不是玩具。它把防火墙拆成内核驱动加 Qt 界面两部分底层在 Linux 3.13 的 Netfilter 框架里挂 nf_hook 钩子函数自己维护一份过滤规则应用层用 Qt5 做可视化界面填 IP、端口、协议规则下到内核进出本机的包就被直接丢掉。一句话讲清它的定位iptables 只是配置工具Netfilter 才是内核里真正干活的那一层这个项目跳过前者把钩子部分做成了一套能演示的毕设。它适合两类人课程设计或大作业想做有内核参与项目的学生以及用熟 iptables、想搞懂数据包在内核里经过哪些检查点的进阶学习者。下面按代码骨架、编译加载、规则下发、踩坑、答辩验证五个环节拆透命令和参数都能直接抄。2. 先读代码骨架驱动、Qt 应用和一份值得细看的文件清单拿到压缩包先别急着编译把文件过一遍能看出这个项目的真实结构。整套东西分三层内核驱动、Qt 应用、文档与杂项。驱动只在 Linux 3.13 内核里工作应用负责把用户填的规则送到内核文档负责答辩时撑场面。这三块各自独立又通过一条规则传递链路串起来。先看清单再讲每个文件为什么存在。2.1 文件清单拆解哪些是源码哪些是编译垃圾文件归属作用要不要进仓库filter.c驱动内核模块源码注册钩子、规则匹配都在这里必须要Makefile驱动kbuild 构建脚本决定怎么编出 filter.ko必须要filter.ko驱动编译产物作者在 3.13 环境下编出来的模块不用重新编filter.o / filter.mod.o / filter.mod.c驱动kbuild 中间产物和模块信息文件不用main.cpp应用Qt 入口先拉起登录框再进主窗口是netfilter.cpp / netfilter.h应用主窗口逻辑规则下发、表格刷新都在这里是netfilter.ui应用Qt Designer 画的主界面布局是login.cpp / login.h / login.ui应用登录界面及其逻辑是netfilter.pro应用qmake 工程文件命令行构建入口是netfilter.pro.user应用Qt Creator 个人配置记录打开时的界面状态不用README.md / Users manual.docx文档项目说明和使用手册答辩材料可以直接改是.gitattributes / .DS_Store杂项git 属性配置和 macOS 元数据都不重要这张表里有几个一眼就能读出来的信息。filter.ko已经存在说明作者至少在一台 3.13 内核的机器上成功 make 过这份资源不是纯理论凑出来的filter.mod.c和filter.mod.o是 kbuild 编译时自动生成的模块信息文件留着它们反而说明作者没养成make clean的习惯。.DS_Store是 macOS 在文件夹里自动生成的元数据说明文件在 Mac 上拷过——但内核模块只能在 Linux 上编所以真正的开发环境还是 LinuxMac 只是中转。注意内核模块和普通可执行程序不一样它严格绑定编译时的内核版本、架构和编译器。别人编好的 filter.ko 在你机器上大概率 insmod 失败这不是资源的 bug是内核模块的固有特性。正确动作是只把 filter.c 和 Makefile 当真源码filter.ko 当作者跑通过的证明。2.2 钩子原理nf_hook 挂在哪、返回什么、优先级怎么定Netfilter 是 Linux 内核的包过滤框架iptables 的底层就是它。在 3.13 时代这个框架的核心是struct nf_hook_ops注册的一个钩子函数每个经过协议栈的数据包都会在固定位置停下来问你一句要不要拦这五个位置是钩子位置宏名拦截时机路由前NF_INET_PRE_ROUTING报文刚进协议栈还没做路由决策本机入NF_INET_LOCAL_IN目的地是本机的报文比如别人访问你的 Web 服务转发NF_INET_FORWARD本机作为路由器转发的报文本机出NF_INET_LOCAL_OUT本机自己发出的报文比如你 ping 外网路由后NF_INET_POST_ROUTING报文马上要出网卡了对单机防火墙来说真正有意义的只有NF_INET_LOCAL_IN和NF_INET_LOCAL_OUT两个点一个管别人访问我一个管我访问别人。很多课程设计只挂了前者导致过滤本机主动发出去的包完全不生效。为什么选 Netfilter 而不是用户态抓包因为 raw socket 抓到的包已经完成了协议栈处理用户态程序只能观察、很难真正把包丢弃在网卡入口而 Netfilter 钩子跑在内核协议栈的热路径上返回NF_DROP就能干脆利落地让包消失。这就是选型理由内核态、低开销、真拦截。钩子函数和注册代码长这样static unsigned int filter_hook(const struct nf_hook_ops *ops, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)); static struct nf_hook_ops filter_ops { .hook filter_hook, .pf PF_INET, /* 只管 IPv4 */ .hooknum NF_INET_LOCAL_IN, /* 管入站 */ .priority NF_IP_PRI_FILTER, /* 和 iptables filter 表同一档 */ }; static int __init filter_init(void) { nf_register_hook(filter_ops); /* 模块加载时挂上去 */ return 0; } static void __exit filter_exit(void) { nf_unregister_hook(filter_ops); /* 模块卸载时摘下来 */ } module_init(filter_init); module_exit(filter_exit); MODULE_LICENSE(GPL);这段骨架和包里的 filter.c 对照着看重点看三处。第一hooknum决定钩子管哪个方向想双向过滤就得注册两个nf_hook_ops一个LOCAL_IN一个LOCAL_OUT。第二钩子返回值只有两个有意义NF_ACCEPT放行NF_DROP丢包其余值在防火墙场景基本用不到。第三priority决定同一钩子点上多个处理函数谁先执行取NF_IP_PRI_FILTER表示和 iptables 的 filter 表排在同一优先级数值越小越先执行。提示3.13 时代用nf_register_hook()注册单个钩子4.14 之后这个接口被移除改成nf_register_net_hook()加钩子数组的方式。这套资源绑定 3.13想在 4.x、5.x 内核跑必须改注册接口这是选型时先要知道的兼容性边界。2.3 用户态和内核态怎么通信/proc、ioctl 还是 netlink驱动把钩子挂好只是第一步还得让 Qt 界面能把我要拦 192.168.1.100 的 8080 端口这句话送进内核。用户态到内核态的通道常见有三条课程设计里出现频率从高到低排序/proc文件、misc 设备加 ioctl、netlink 套接字。/proc方式最简单驱动里用proc_create()注册一个/proc/filter文件用户态往里 echo 一行字符串驱动在 write 回调里解析字符串、更新规则链表。优点是代码量小、直观缺点是只能写进去不能异步通知。ioctl 方式要注册一个 misc 设备Qt 侧open()加ioctl()传结构体比字符串解析规范但 Qt 侧要写一层封装。netlink 是内核态和用户态的标准双向通道实时性和扩展性最好但代码量翻倍新手容易卡在消息格式上。判断这套资源用的哪条路打开 filter.c 搜三个关键词就够proc_create、misc_register、netlink_kernel_create命中哪个就是哪条路。我拆过不少同类毕设九成走/proc因为驱动侧代码短、Qt 侧一个QFile就能写正好卡在能干活和好讲之间。下面第 4 章的规则下发代码就按/proc这条最可能的路线展开如果你手上代码实际是 ioctl替换成open(/dev/filter)加ioctl即可规则结构体不用变。Qt 侧构建则更省事qmake netfilter.pro make或者直接用 Qt Creator 打开.pro文件编译前提是机器上装了 Qt5 运行库Ubuntu 下apt install qt5-default。3. 把 filter.ko 编出来并加载Makefile、insmod 与 dmesg 验证一条龙很多同学拿到这个包的第一步是直接insmod filter.ko然后被Invalid module format拍在脸上。这一章把从零编译到加载验证的完整流程过一遍照着敲就行。核心思路一句话不要用别人编好的模块在你的机器上重新编一遍。3.1 编译环境三件套内核、头文件、工具链内核模块编译不是写个 gcc 就行它需要当前内核的构建树也就是/lib/modules/$(uname -r)/build这个目录。先确认三件事内核版本、内核头文件、编译工具。# 查看当前内核版本 uname -r # Ubuntu / Debian 系安装头文件与工具链 sudo apt-get install build-essential linux-headers-$(uname -r) # CentOS 7 / RHEL 系内核 3.10和项目同属 3.x 大版本 sudo yum install gcc make kernel-devel # 确认构建目录真的存在 ls -l /lib/modules/$(uname -r)/builduname -r输出的就是当前内核版本比如3.13.0-32-generic头文件必须和它完全一致差一个小版本都不行。linux-headers-$(uname -r)会自动匹配版本但要注意如果机器上装过多个内核重启后没进对应的那个/lib/modules/$(uname -r)可能指向一个不存在的目录ls 结果为空就是没装对。build目录通常是指向/usr/src/linux-headers-3.13.0-32-generic的软链接它存在kbuild 才能找到内核源码树里的 Makefile 和头文件。3.2 Makefile 拆解obj-m 和 KDIR 是两道关键门这套资源自带的 Makefile 是标准 kbuild 写法核心就这几行obj-m : filter.o # 目标内核模块 KDIR : /lib/modules/$(shell uname -r)/build PWD : $(shell pwd) all: $(MAKE) -C $(KDIR) M$(PWD) modules clean: $(MAKE) -C $(KDIR) M$(PWD) clean逐行说。obj-m : filter.o告诉 kbuild把 filter.c 编成内核模块注意写的是.o不是.kokbuild 会基于这个 obj 自动生成同名.ko。KDIR指向内核构建树$(shell uname -r)保证它永远匹配当前内核。真正执行编译的是$(MAKE) -C $(KDIR) M$(PWD) modules从内核构建树目录切入把当前目录作为外部模块目录执行 modules 目标。如果KDIR路径不存在make 会直接报目录相关的错误先回头看 3.1。两个执行细节容易翻车。第一Makefile 里all和clean下面的命令必须以 Tab 开头复制自网页时 Tab 容易变成空格然后 make 报missing separator没有别的办法手动删掉重敲一次 Tab。第二建议先make clean再make把压缩包里残留的.o和.ko清掉避免新旧产物混在一起。编译成功后目录里会多出filter.ko以及Module.symvers、modules.order这些 kbuild 副产品前者记录模块导出的符号表后者记录构建顺序都属于编译产生的文件不用管。这时候做两个检查能省掉后面一半的加载报错# 看架构x86_64 的机器编出 ARM 的模块是另一场灾难 file filter.ko # 看模块信息重点看 vermagic 这一行 modinfo filter.komodinfo filter.ko输出的vermagic会带一串信息比如3.13.0-32-generic SMP mod_unload modversions 686记录编译时的内核版本和配置选项。insmod 时内核会拿 vermagic 和当前运行内核比对任何一项不匹配都会拒绝加载。这就是为什么我 insmod 报 Invalid module format的根源——不是你抄错了命令是版本对不上。3.3 insmod、dmesg、lsmod、rmmod四个命令走完加载闭环编译通过后就进入加载环节。加载、查询、看日志、卸载四个命令各司其职# 加载模块需要 root 权限 sudo insmod filter.ko # 确认是否加载成功成功则列表里出现 filter lsmod | grep filter # 看内核日志模块 init 函数里的 printk 输出在这里 dmesg | tail -20 # 有 /proc 接口的话看规则文件是否存在 cat /proc/filter # 卸载模块 sudo rmmod filterinsmod是最朴素的加载命令只做一件事把指定的 .ko 装进内核不管依赖。lsmod | grep filter看的是/proc/modules的内容模块加载成功后能看到它Usage 列是 0 表示没有进程占用。dmesg | tail是关键驱动里printk打出来的日志默认走这里加载失败原因、钩子注册是否成功、规则命中调试输出都在这里第 5 章反复要用它。如果资源驱动里注册了/proc/filter加载后cat /proc/filter能看到规则文件初始状态一般是空。这时候可以做最朴素的冒烟测试往里写一条拦 ICMP 的规则然后 ping 网关地址看丢包和 dmesg。规则格式放到第 4 章讲因为它是 Qt 和驱动约定好的两边对不上也会翻车。提示insmod不会自动处理模块依赖如果 filter.c 还引用了其他符号建议换成modprobe filter它会查依赖。但 modprobe 需要把 .ko 放到/lib/modules/$(uname -r)/extra/下并先执行depmod -a课程设计一般用 insmod 就够了。4. 界面规则下到内核Qt5 到 /proc 的转发链路与协议参数细节驱动只能拦截不能表达我想拦什么。这章讲清楚 Qt 应用怎么把用户的意图变成内核认识的数据。整条链路是用户在 netfilter.ui 上填 IP 和端口 → netfilter.cpp 拼规则字符串 → 写 /proc/filter → 驱动 write 回调解析 → 存入驱动的规则数组 → 钩子函数每次收包遍历比对。链路不长但有四个细节决定它能不能跑通。4.1 main.cpp 与登录模块一套典型的 Qt 应用骨架先看入口。main.cpp 的逻辑通常是先出登录框通过后进主窗口int main(int argc, char *argv[]) { QApplication app(argc, argv); LoginDialog login; // login.ui 对应的登录窗口 if (login.exec() ! QDialog::Accepted) return 0; // 没登录成功就直接退出 MainWindow w; // netfilter.ui 对应的主窗口 w.show(); return app.exec(); }登录框是这套资源里最包装的部分login.cpp 里通常会写死一个账号密码或者拿输入字符串和某个常量比对。注意这是课程设计的常见做法不代表安全设计——答辩被问密码存哪了时答仅供演示生产环境要用数据库或 PAM就行。主窗口 netfilter.ui 上常见控件是IP 输入框QLineEdit、端口输入QSpinBox范围 0~65535、协议下拉框QComboBoxTCP/UDP/ICMP、规则列表QTableWidget和添加、删除、清空按钮。理解这个布局就理解了需求的全部这个防火墙的规则模型就是IP 端口 协议三元组没有更多了。4.2 规则下发QFile 写 /proc/filter 的实际代码Qt 侧写/proc文件一个QFile就够代码大概是下面这样void MainWindow::onAddRule() { // 拼规则字符串add IP PORT PROTO空格分隔 QString rule QString(add %1 %2 %3) .arg(ui-ipEdit-text().trimmed()) .arg(ui-portSpin-value()) .arg(ui-protoCombo-currentText().toLower()); QFile f(/proc/filter); if (!f.open(QIODevice::WriteOnly | QIODevice::Append)) { QMessageBox::warning(this, 提示, 写 /proc/filter 失败先确认驱动已加载且程序以 root 运行); return; } QTextStream out(f); out rule \n; f.close(); }代码里两个细节比代码本身重要。第一打开模式用QIODevice::Append追加写而不是覆盖写连续加三条规则不会互相顶掉如果驱动侧 write 回调按一次一行解析追加写是必须的。第二写入前把协议转小写toLower()避免驱动比对字符串时因为大写 TCP 和小写 tcp 不一致而漏判——这类问题完全没有报错提示是最典型的静默翻车。规则字符串是 Qt 和驱动之间的约定常见格式包括写入内容含义add 192.168.1.100 8080 tcp拦截目标 IP 192.168.1.100、目标端口 8080 的 TCP 包del 192.168.1.100 8080 tcp删除这条规则flush清空全部规则add 0.0.0.0 0 icmp端口 0 不限端口、IP 全零匹配任意地址这里就是拦所有 ICMP驱动侧对应的解析逻辑一般就是在/proc/filter的 write 回调里用sscanf把字符串拆开add进链表del删链表节点flush清空。字符串解析虽然糙但胜在直观答辩时一句话就能讲完整个数据流。提示/proc/filter属主是 root普通用户运行 Qt 程序会 open 失败。本地调试可以sudo ./netfilter跑更优雅的做法是在驱动里对/proc/filter设mode 0666或者在 Qt 侧加权限检测提醒用户用 root 运行。4.3 驱动侧的匹配循环从 skb 里扒出 IP 和端口规则存下来了钩子函数每次收包都要把数据包的关键字段抠出来比对。示意代码如下和包里的 filter.c 对照看static unsigned int filter_hook(struct nf_hook_ops *ops, struct sk_buff *skb, ...) { struct iphdr *iph ip_hdr(skb); /* 得到 IP 头 */ unsigned int daddr ntohl(iph-daddr); unsigned short dport 0; int i; if (iph-protocol IPPROTO_TCP) { struct tcphdr *tcph tcp_hdr(skb); dport ntohs(tcph-dest); /* 网络字节序转主机字节序 */ } else if (iph-protocol IPPROTO_UDP) { struct udphdr *udph udp_hdr(skb); dport ntohs(udph-dest); } for (i 0; i rule_count; i) { if (rules[i].proto rules[i].proto ! iph-protocol) continue; /* 协议不匹配就查下一条 */ if (rules[i].ip rules[i].ip ! daddr) continue; if (rules[i].port rules[i].port ! dport) continue; return NF_DROP; /* 三项全命中丢弃 */ } return NF_ACCEPT; /* 否则放行 */ }这段代码藏着三个最常见的匹配 bug。第一个是字节序IP 头里的地址和端口都是网络字节序大端直接拿它和界面上输入的十进制数比较永远不相等。IP 用ntohl转主机序再比端口用ntohs。第二个是端口方向tcph-dest是目标端口tcph-source是源端口。要拦外部对本机服务的访问用 dest要拦本机对外访问比如防止某种主动外联就得同时考虑 source。第三个是协议空判断规则里协议存 0 表示不限协议时if (rules[i].proto ...)的短路判断让 0 值直接跳过协议过滤这是有意为之的写法别手改。匹配模型方面这套资源本质是一个黑名单命中就丢没命中就放行和防火墙黑白名单的概念对得上——add一条规则就是在黑名单里加一项。规则存储一般用固定数组或链表数组简单但删除要挪元素链表删除方便但遍历慢几十条规则无所谓量大了就得按协议分桶或者哈希。再补一句钩子函数在数据包热路径上每次收包都循环规则数组越大每个包的开销越大这是答辩必问的性能问题提前有数。5. 避坑与排查五个真实翻车点从加载失败到 SSH 断开这章直接上硬货。以下五个坑是我拆这类内核模块 Qt毕设时反复见到的前两个属于编不过、加载不上后三个属于加载了但行为诡异。每条按现象、原因、解决写方便你对着自己的报错定位。5.1 编译与加载阶段版本不对一切白搭坑一执行sudo insmod filter.ko报Invalid module format或version magic mismatch。 现象命令敲下去终端回一行错误dmesg里写着version magic 3.13.0-32-generic should be 4.15.0-...甚至直接说disagrees about version of symbol module_layout。 原因你加载的是压缩包里别人编好的 filter.ko它绑定 3.13 内核而当前机器是 4.x 或 5.x。内核模块的 vermagic 机制就是干这个的任何不匹配都拒绝加载。 解决不要用现成的 .ko。把 filter.c 和 Makefile 单独拷出来在自己内核环境下重新make用新编出来的 filter.ko 加载。如果代码用了 3.13 的旧接口新内核编译会报nf_register_hook未定义那就得先改代码换成nf_register_net_hook或者找一台 3.x 内核的机器或虚拟机跑。血泪经验拿到任何内核模块资源第一件事永远是modinfo xxx.ko看 vermagic而不是直接 insmod。坑二make报错Kernel source tree not found、No rule to make target modules或者 build 目录不存在。 现象执行 make 后内核构建树路径无效kbuild 直接退出。 原因/lib/modules/$(uname -r)/build不存在也就是没装对应版本的内核头文件。很多精简安装的 Linux 默认不带 kernel headers而这恰恰是编模块的唯一依赖。 解决按 3.1 的步骤装linux-headers-$(uname -r)Debian/Ubuntu或kernel-develCentOS/RHEL装完确认 build 软链接指向已存在的目录。如果机器上多个内核还要确认uname -r显示的内核和 build 指向的是同一个否则重启进对应内核再重新 make。5.2 使用阶段规则不生效和管理通道被切断坑三在界面加了规则但目标流量照样通。 现象添加拦截 192.168.1.100 的 8080 端口后从另一台机器访问 8080 依然建立连接dmesg 里也没有任何命中日志。 原因常见有四种。一是钩子只挂了NF_INET_LOCAL_IN对方访问你时包确实走这个点但本机主动发起的访问走LOCAL_OUT没挂就管不着。二是驱动把钩子挂在NF_INET_FORWARD上而测试机就是目的机转发路径根本不经过。三是匹配代码里端口方向搞反界面上写的 8080 是目的端口驱动却拿tcph-source去比自然不命中。四是/proc/filter写入成功是假象驱动 write 回调里sscanf格式和界面拼的字符串不一致解析失败被静默丢弃。 解决按顺序排查。先cat /proc/filter确认规则真的存进去了再看 filter.c 里nf_hook_ops的hooknum是哪个点双向过滤需要注册两个 ops再看匹配是比 dest 还是 source最后在驱动 parse 分支加printk打印解析结果用 dmesg 看是否进了错误分支。别信玄学一步步打日志。坑四测试把自己 SSH 断开了远程机器直接失联。 现象为了演示效果在界面上添加拦截 22 端口的规则保存瞬间 SSH 终端卡死之后再连接超时。此时没有任何后悔药因为规则是内存态的、立即生效的。 原因过滤规则不分亲疏把管理通道也一起拦了。这是防火墙类项目最经典的翻车演示现场断 SSH 比编译失败还尴尬。 解决三个办法任选。一是演示前先在测试机上开一个本地 root 终端断网了用本地终端rmmod filter恢复。二是写一个自动后悔脚本添加规则前先sleep 30 echo flush /proc/filter 放后台30 秒内出问题自动清空规则。三是在驱动里留一个应急开关比如写disable 1能立刻停用全部钩子。从那以后我做这类实验规则里永远先放一条放行自己管理网段的白名单规则再放拦截规则。坑五没加载驱动目标流量也丢包分不清是谁拦的。 现象新装的环境上测试sudo insmod filter.ko之前 ping 就丢包加规则后更乱dmesg 里根本没有驱动的输出。 原因系统自带的防火墙在起作用。Ubuntu 默认 ufwCentOS 7 默认 firewalld很多最小化安装还残留 iptables 规则。docker 环境下宿主机的 DOCKER-USER 链和端口映射配置也会产生同样的外部连不上现象——就像 docker 端口映射配置好后外部还是连不上先怀疑宿主防火墙思路完全一样。 解决做对照实验。第一步停掉系统防火墙Ubuntu 用sudo ufw disableCentOS 用sudo systemctl stop firewalld并iptables -L -n确认没有遗留规则。第二步在没加载模块的情况下 ping 目标地址确认基础通。第三步再 insmod、加规则、ping如果这时才不通才证明钩子生效。一层层排除不要一上来就归罪于自己的模块。6. 答辩前做一轮完整验证演示脚本和四个必问怎么接资源里的 Users manual.docx 和 README.md 能给文档部分兜底但答辩真正的分在演示给人看和问题接得住上。这章给一套我常用的验证流程照着走一遍你的系统在评委眼里就是能跑、有原理、有边界的完整作品。6.1 五分钟演示脚本加载、拦截、恢复三步走#!/bin/bash # 0. 停系统防火墙保证对照干净 sudo ufw disable 2/dev/null || sudo systemctl stop firewalld 2/dev/null # 1. 清空内核日志让演示输出干净可读 sudo dmesg -c # 2. 加载模块 sudo insmod filter.ko lsmod | grep filter # 3. 添加一条拦 ICMP 的规则0.0.0.0 表示任意 IP echo add 0.0.0.0 0 icmp | sudo tee /proc/filter # 4. 验证拦截把 192.168.1.1 换成你的网关地址 ping -c 3 192.168.1.1 # 5. 展示内核日志证明是钩子干的 dmesg | tail -10 # 6. 清空规则并恢复 echo flush | sudo tee /proc/filter ping -c 3 192.168.1.1脚本结构本身就是答辩讲稿先证明环境干净再证明模块加载成功然后加规则、验证拦截、展示日志、恢复放行每一步的输出都是证据。两个要点ping 目标用网关比外网稳定避免现场外网波动如果 dmesg 没有命中输出就在 filter.c 里加一行printk(KERN_INFO filter drop: %pI4:%d\n, iph-daddr, dport)重编后再演示。提示规则是内存态的驱动卸载或系统重启就全没了这不是 bug是内核模块的天然属性。答辩被问重启后防火墙还有效吗时接住写一个开机脚本insmod filter.ko后从配置文件逐行 echo 规则到/proc/filter就能做到规则持久化。这正好是扩展点。6.2 四个必问怎么接为什么不直接用 iptables这是必问题。标准答法iptables 只是用户态配置工具它真正干活的底层就是 Netfilter 框架本项目直接操作 nf_hook 钩子是为了理解包经过协议栈的具体路径并且能在钩子里做 iptables 规则不好表达的私有逻辑比如自定义审计日志、按特殊协议字段过滤。新内核还能编译吗诚实回答4.14 后nf_register_hook被移除需要把注册方式改成nf_register_net_hook加钩子数组这是已知边界。如果时间允许把适配代码作为后续工作写进答辩 PPT 的展望页是加分项不是减分项。规则多了会影响性能吗答当前实现是每次收包在钩子里遍历链表O(n) 复杂度规则几十条没问题优化方向是哈希表加 RCU 读锁或者按协议分桶每桶独立比较。黑白名单有什么区别答当前是黑名单模型——默认放行命中规则才丢白名单是默认丢弃只有匹配放行。反过来改造只需要把钩子的默认返回值从NF_ACCEPT改成NF_DROP但要注意先放行回环和管理网段否则自己都连不上。这个改动可以作为现场加餐演示。整套源码、编译产物和使用手册都在下载包里。拿到手先别急着跑按第 3 章在自己机器上重新 make 一遍再按 6.1 的脚本走一遍演示filter.c 和 netfilter.cpp 之间的规则格式约定是你整个答辩里最值得讲透的一段。另外规则不持久化不是缺陷写个开机脚本逐行 echo 规则进去就解决了这也解释了为什么有些防火墙每次重启后配置又回来了——持久化逻辑写在用户态内核只负责瞬时执行。我这个先看 modinfo 再动手的习惯就是被 Invalid module format 教出来的希望这份记录能帮你在同样的问题上少耗一个下午。希望帮到你。本文还有配套的精品资源点击获取
返回列表