防火墙实验:实现一个简单防火墙
章节大纲
-
-
Labsetup.zip 文件 ZIP
把 zip 文件下载到 VM 里, 然后用 unzip 命令解压缩。
-
-
在本任务中,我们将实现一种简单的包过滤类型防火墙,它会检查每个传入和传出的数据包,并执行管理员设置的防火墙策略。由于数据包的处理是在内核中完成的,因此过滤过程也必须在内核中完成。因此,要实现这样的防火墙,似乎我们需要修改 Linux 内核。在过去,这的确需要通过修改和重新编译内核来完成。然而,现代的 Linux 操作系统提供了几种机制,使得无需重新编译内核就可以实现防火墙。这些机制包括可加载内核模块(Loadable Kernel Module, LKM)和 Netfilter。LKM(可加载内核模块)允许我们在系统运行时向内核添加一个新模块。通过这个新模块,我们可以扩展内核的功能,而无需重新编译内核甚至重启计算机。防火墙的包过滤部分可以通过 LKM 实现。在本任务中,我们将熟悉 LKM 的基本用法。
以下是一个简单的可加载内核模块示例。当该模块被加载时,它会打印出 "Hello World!"。当模块从内核中移除时,它会打印出 "Bye-bye World!"。这些信息不会显示在屏幕上,而是记录在 /var/log/syslog 文件中。你可以使用 "dmesg" 命令查看这些信息。
/* hello.c(在实验设置文件中已包含)*/ #include <linux/module.h> #include <linux/kernel.h> int initialization(void) { printk(KERN_INFO "Hello World!\n"); return 0; } void cleanup(void) { printk(KERN_INFO "Bye-bye World!.\n"); } module_init(initialization); module_exit(cleanup);
接下来我们需要创建一个 Makefile 文件,其内容如下(文件已包含在实验设置文件中)。只需运行 make,上述程序就会被编译成一个可加载的内核模块(如果将以下内容粘贴到 Makefile 中,请确保将 make 命令前的空格替换为 Tab)。
obj-m += hello.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
生成的内核模块文件名为 hello.ko。你可以使用以下命令加载模块、列出所有模块以及移除模块。此外,可以使用 "modinfo hello.ko" 查看有关该内核模块的信息。
$ sudo insmod hello.ko (插入模块) $ lsmod | grep hello (列出模块) $ sudo rmmod hello (移除模块) $ dmesg (查看信息) $ sudo dmesg (在 Ubuntu 22.04 中,此命令需要超级用户权限)
任务: 请在你的虚拟机上编译此简单内核模块,并在虚拟机上运行它。在本任务中,我们不使用容器。请在实验报告中展示你的运行结果。
注意:如果你在 Apple Silicon 机器上(通过 Ubuntu 22.04 虚拟机)编译内核模块,可能会看到一条错误消息,提示 gcc-12 未安装。可以使用以下命令安装它:"sudo apt install gcc-12" -
在本任务中,我们将通过 LKM 编写一个数据包过滤程序,并将其插入到内核中的数据包处理路径中。在 Netfilter 被引入 Linux 之前,这种操作很难实现。Netfilter 的设计目的是帮助授权用户操作数据包。它通过在 Linux 内核中实现多个钩子(hooks)来实现这一目标。这些钩子分布在不同的位置,包括数据包的输入和输出路径。如果我们想操作输入的数据包,只需将我们的程序(通过 LKM 实现)连接到相应的钩子上。一旦有输入数据包到达,我们的程序就会被调用。通过我们的程序,可以决定是否允许数据包经过,此外,还可以修改数据包内容。在本任务中,你需要使用 LKM 和 Netfilter 实现一个数据包过滤模块。该模块将从一个数据结构中获取防火墙策略,并使用这些策略决定是否阻止数据包。为了让学生专注于过滤部分(即防火墙的核心功能),允许学生直接把防火墙策略写在程序中(在真正的防火墙软件中,策略和代码是分开的)。关于如何使用 Netfilter,请参考下面的示例。完整的示例代码名为 seedFilter.c,包含在实验设置文件的 Files/packet_filter 文件夹中。请完成以下任务(分别完成每个任务):
- 使用提供的 Makefile 编译代码。将结果加载到内核中,并演示防火墙是否按预期工作。你可以使用以下命令生成到 8.8.8.8 的UDP数据包,即 Google 的 DNS 服务器。如果你的防火墙工作正常,请求将被阻止;否则,你将收到响应。
dig @8.8.8.8 www.example.com
- 将 printInfo 函数挂接到所有 Netfilter 钩子上。以下是钩子编号的宏定义。 通过实验结果解释每个钩子函数在何种条件下会被调用。
NF_INET_PRE_ROUTING NF_INET_LOCAL_IN NF_INET_FORWARD NF_INET_LOCAL_OUT NF_INET_POST_ROUTING
- 实现另外两个钩子以实现以下功能:(1) 阻止其他计算机 ping 虚拟机,(2) 阻止其他计算机 telnet 到虚拟机。请实现两个不同的钩子函数,但将它们注册到相同的 Netfilter 钩子上。你需要决定使用哪个钩子。Telnet 的默认端口为 TCP 端口 23。为了测试,你可以启动容器,进入 10.9.0.5,运行以下命令 (10.9.0.1 是分配给虚拟机的IP地址。为了简化,可以将此 IP 地址写在到防火墙规则中):
ping 10.9.0.1 telnet 10.9.0.1
重要提示。由于我们对内核进行了修改,系统可能会崩溃。请务必经常备份文件,以免丢失数据。系统崩溃的常见原因之一是忘记取消注册的钩子。当模块被移除时,这些钩子仍然会被触发,但模块已经不存在于内核中,从而导致系统崩溃。为避免这种情况,请确保在 removeFilter 中添加相应的代码,以便在移除模块时删除所有注册的钩子函数。 - 使用提供的 Makefile 编译代码。将结果加载到内核中,并演示防火墙是否按预期工作。你可以使用以下命令生成到 8.8.8.8 的UDP数据包,即 Google 的 DNS 服务器。如果你的防火墙工作正常,请求将被阻止;否则,你将收到响应。
-
你需要提交一份带有截图的详细实验报告来描述你所做的工作和你观察到的现象。你还需要对一些有趣或令人惊讶的观察结果进行解释。请同时列出重要的代码段并附上解释。只是简单地附上代码不加以解释不会获得学分。实验报告的提交方式会由你的老师进行具体安排。