一、实验目的
这个实验的学习目标有两方面:学习防火墙如何工作,以及为网络建立一个简单的防火墙。学生们将首先实现一个简单的无状态包过滤防火墙,它检查数据包,并根据防火墙规则决定是否丢弃或转发数据包。通过这个实现任务,学生可以对防火墙的工作原理有基本的了解。
• Firewall
• Netfilter
• Loadable kernel module
• Using iptables to set up firewall rules
• Various applications of iptables
二、实验环境
1.去实验官网下载配置文件并在本地运行。
三、进行实验
Task 1: Implementing a Simple Firewall
在本任务中,我们将实现一种简单的包过滤类型的防火墙,它将检查每一个进出的包,并执行管理员设置的防火墙策略。因为包处理是在内核中完成的,所以过滤也必须在内核中完成。因此,实现这样的防火墙似乎需要我们修改Linux内核。在过去,这必须通过修改和重新构建内核来实现。现代Linux操作系统提供了几种新的机制来简化对包的操作,而无需重新构建内核映像。这两种机制是可加载内核模块(LKM)和Netfilter。
Task 1.A: Implement a Simple Kernel Module
LKM允许我们在运行时向内核添加一个新模块。这个新模块使我们能够扩展内核的功能,而无需重新构建内核,甚至无需重新启动计算机。防火墙的包过滤部分可以作为LKM来实现。在这个任务中,我们将熟悉LKM。
1.编译内核文件Makefile。
2.生成的内核模块在hello.ko中,可以进行查看删除模块。
3.把hello.ko插入到内核中,再开启一个窗口进行监控内核。
demsg -k -w
4.从内核中删除hello。
Task 1.B: Implement a Simple Firewall Using Netfilter
在这个任务中,我们将把我们的包过滤程序写成一个LKM,然后插入到内核内部的包处理路径中。
1.先测试网络,可以连上8.8.8.8.
2…运行实验中给定的内核程序,并插入到内核中。
3.测试结果。已经可以实现防火墙。
4.再实现两个钩子,可以实现以下功能:(1)防止其他计算机ping虚拟机;(2)防止其他计算机telnet到虚拟机。请实现两个不同的钩子函数,但将它们注册到同一个netfilter钩子。
我首先启动10.9.0.5容器。
结果表示可以实现ping和telnet。
5.
编写内核程序。这一步比较麻烦,我一开始在ping8.8.8.8是拦截的是UDP数据,在ping10.9.0.1是我们需要拦截icmp数据,在telnet10.9.0.1:23时需要拦截TCP数据,所以我们需要在原来程序的基础上在编写一个拦截TCP和ICMP的函数,最后再主函数里面运行即可。具体代码如下。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <linux/udp.h>
#include <linux/icmp.h>
#include <linux/if_ether.h>
#include <linux/inet.h>
static struct nf_hook_ops hook1, hook2,hook3,hook4;
//blocking ping 8.8.8.8
unsigned int blockUDP(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
struct udphdr *udph;
u16 port = 53;
char ip[16] = "8.8.8.8";
u32 ip_addr;
if (!skb) return NF_ACCEPT;
iph = ip_hdr(skb);
// Convert the IPv4 address from dotted decimal to 32-bit binary
in4_pton(ip, -1, (u8 *)&ip_addr, '\0', NULL);
if (iph->protocol == IPPROTO_UDP) {
udph = udp_hdr(skb);
if (iph->daddr == ip_addr && ntohs(udph->dest) == port){
printk(KERN_WARNING "*** Dropping %pI4 (UDP), port %d\n", &(iph->daddr), port);
return NF_DROP;
}
}
return NF_ACCEPT;
}
//blocking ping vm:10.9.0.1
unsigned int blockICMP(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
struct icmphdr *icmph;
//u16 port = 53;
char ip[16] = "10.9.0.1";
u32 ip_addr;
if (!skb) return NF_ACCEPT;
iph = ip_hdr(skb);
// Convert the IPv4 address from dotted decimal to 32-bit binary
in4_pton(ip, -1, (u8 *)&ip_addr, '\0', NULL);
if (iph->protocol == IPPROTO_ICMP) {
icmph = icmp_hdr(skb);
if (iph->daddr == ip_addr && icmph->type==ICMP_ECHO){
printk(KERN_WARNING "*** Dropping %pI4 (ICMP)\n", &(iph->daddr));
return NF_DROP;
}
}
return NF_ACCEPT;
}
//blcoking telnet 10.9.0.1:23
unsigned int blockTelnet(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
struct tcphdr *tcph;
u16 port = 23;
char ip[16] = "10.9.0.1";
u32 ip_addr;
if (!skb) return NF_ACCEPT;
iph = ip_hdr(skb);
// Convert the IPv4 address from dotted decimal to 32-bit binary
in4_pton(ip, -1, (u8 *)&ip_addr, '\0', NULL);
if (iph->protocol == IPPROTO_TCP) {
tcph = tcp_hdr(skb);
if (iph->daddr == ip_addr && ntohs(tcph->dest) == port){
printk(KERN_WARNING "*** Dropping %pI4 (Telnet), port %d\n", &(iph->daddr), port);
return NF_DROP;
}
}
return NF_ACCEPT;
}
unsigned int printInfo(void *priv, struct sk_buff *skb,
const struct nf_hook_state *state)
{
struct iphdr *iph;
char *hook;
char *protocol;
switch (state->hook){
case NF_INET_LOCAL_IN: hook = "LOCAL_IN"; break;
case NF_INET_LOCAL_OUT: hook = "LOCAL_OUT"; break;
case NF_INET_PRE_ROUTING: hook = "PRE_ROUTING"; break;
case NF_INET_POST_ROUTING: hook = "POST_ROUTING"; break;
case NF_INET_FORWARD: hook = "FORWARD"; break;
default: hook = "IMPOSSIBLE"; break;
}
printk(KERN_INFO "*** %s\n", hook); // Print out the hook info
iph = ip_hdr(skb);
switch (iph->protocol){
case IPPROTO_UDP: protocol = "UDP"; break;
case IPPROTO_TCP: protocol = "TCP"; break;
case IPPROTO_ICMP: protocol = "ICMP"; break;
default: protocol = "OTHER"; break;
}
// Print out the IP addresses and protocol
printk(KERN_INFO " %pI4 --> %pI4 (%s)\n",
&(iph->saddr), &(iph->daddr), protocol);
return NF_ACCEPT;
}
int registerFilter(void) {
printk(KERN_INFO "Registering filters.\n");
hook1.hook = printInfo;
hook1.hooknum = NF_INET_LOCAL_OUT;
hook1.pf = PF_INET;
hook1.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &hook1);
hook2.hook = blockUDP;
hook2.hooknum = NF_INET_POST_ROUTING;
hook2.pf = PF_INET;
hook2.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &hook2);
hook3.hook = blockICMP;
hook3.hooknum = NF_INET_PRE_ROUTING;
hook3.pf = PF_INET;
hook3.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &hook3);
hook4.hook = blockTelnet;
hook4.hooknum = NF_INET_PRE_ROUTING;
hook4.pf = PF_INET;
hook4.priority = NF_IP_PRI_FIRST;
nf_register_net_hook(&init_net, &hook4);
return 0;
}
void removeFilter(void) {
printk(KERN_INFO "The filters are being removed.\n");
nf_unregister_net_hook(&init_net, &hook1);
nf_unregister_net_hook(&init_net, &hook2);
nf_unregister_net_hook(&init_net, &hook3);
nf_unregister_net_hook(&init_net, &hook4);
}
module_init(registerFilter);
module_exit(removeFilter);
MODULE_LICENSE("GPL");
6.运行Makefile并插入到内核中去。
7.查看结果。
ping的结果表明所有icmp包均被防火墙拦截。
telnet结果表明所有TCP数据包也被防火墙拦截。