概述

本实验主要就是基于LKM和Netfilter实现简单的防火墙,重点是后者。
另外实验中容器的基本情况是这样子的:
在这里插入图片描述
注意:

  • 路由器是有两个ip的,一个是外网ip,一个是内网ip
  • 每一次做完实验,都要做好收尾工作(比如:模块装入之后,要记得移除;iptables命令设置完规则之后,要把对应规则删掉)

资料:
iptables命令、规则、参数详解

Task 1: Implementing a Simple Firewall

Task 1.A: Implement a Simple Kernel Module

这个就直接按照任务提示的代码就可以了,首先编写模块和对应的编译文件,然后对代码进行编译

vim hello.c
vim Makefile
make
#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);
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

在这里插入图片描述
我们先观察内核输出的信息,然后将模块装载入内核,移出内核(发现我们编写的装入时代码和移除时代码都触发了)

dmesg -k -w
sudo insmod hello.ko
sudo rmmod hello

在这里插入图片描述

Task 1.B: Implement a Simple Firewall Using Netfilter

任务1

任务1直接使用样例代码就好了
blockUDP函数主要用于过滤目的地址为8.8.8.8,目的端口为53UDP数据包
printInfo函数用于打印数据包的信息

#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/if_ether.h>
#include <linux/inet.h>


static struct nf_hook_ops hook1, hook2; 


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;
}

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);

   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);
}

module_init(registerFilter);
module_exit(removeFilter);

MODULE_LICENSE("GPL");

编译样例代码,然后把模块装入,检查是否装入

make
sudo insmod seedFilter.ko
lsmod | grep seedFilter

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

此时给8.8.8.8UDP包发现发送不了

dig @8.8.8.8 www.example.com

在这里插入图片描述

将模块移除之后再发送,发送成功

sudo rmmod seedFilter
dig @8.8.8.8 www.example.com

在这里插入图片描述

任务2

任务2要做的是给netfilter的各个钩子函数都添加打印数据包信息的函数,来观察各个钩子函数执行的顺序之类的
我们只要对样例代码做一点改动就好了

#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/if_ether.h>
#include <linux/inet.h>


static struct nf_hook_ops hook1, hook2,hook3,hook4,hook5; 


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 = printInfo;
   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 = printInfo;
   hook3.hooknum = NF_INET_FORWARD;
   hook3.pf = PF_INET;
   hook3.priority = NF_IP_PRI_FIRST;
   nf_register_net_hook(&init_net, &hook3);

   hook4.hook = printInfo;
   hook4.hooknum = NF_INET_LOCAL_IN;
   hook4.pf = PF_INET;
   hook4.priority = NF_IP_PRI_FIRST;
   nf_register_net_hook(&init_net, &hook4);
   
   hook5.hook = printInfo;
   hook5.hooknum = NF_INET_PRE_ROUTING;
   hook5.pf = PF_INET;
   hook5.priority = NF_IP_PRI_FIRST;
   nf_register_net_hook(&init_net, &hook5);

   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);
   nf_unregister_net_hook(&init_net, &hook5);
}

module_init(registerFilter);
module_exit(removeFilter);

MODULE_LICENSE("GPL");

具体过程和上面一样,我当时的结果是下面这样的
在这里插入图片描述
各个钩子函数 具体的执行过程是:
在这里插入图片描述

任务3

任务三要做的就是禁止ping和telnet

#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; 


unsigned int preventPing(void *priv, struct sk_buff *skb,
                       const struct nf_hook_state *state)
{
   struct iphdr *iph;	//取出ip头
   struct tcphdr *tcph;	//取出tcp头
   struct icmphdr *icmph; 
   iph = ip_hdr(skb);
   tcph = tcp_hdr(skb);
   icmph = icmp_hdr(skb);
   unsigned char* saddr = (unsigned char*)&iph->saddr;	//源IP地址
   if (iph->protocol == IPPROTO_ICMP  && icmph->type==ICMP_ECHO &&
    (int)saddr[0] == 10 && saddr[1] == 0 && saddr[2] == 2 && saddr[3] == 6)
   { 
      printk(KERN_INFO "丢弃ping包\n");
      return NF_DROP;
   }
   return NF_ACCEPT;
}

unsigned int preventTelnet(void *priv, struct sk_buff *skb,
                 const struct nf_hook_state *state)
{
   struct iphdr *iph;	//取出ip头
   struct tcphdr *tcph;	//取出tcp头
   iph = ip_hdr(skb);
   tcph = tcp_hdr(skb);
   unsigned char* saddr = (unsigned char*)&iph->saddr;	//源IP地址
   if (iph->protocol == IPPROTO_TCP && tcph->dest == htons(23) && 
    (int)saddr[0] == 10 && saddr[1] == 0 && saddr[2] == 2 && saddr[3] == 6)
   { 
      printk(KERN_INFO "丢弃telnet包\n");
      return NF_DROP;
   }
   return NF_ACCEPT;
}


int registerFilter(void) {
   printk(KERN_INFO "Registering filters.\n");

   hook1.hook = preventPing;
   hook1.hooknum = NF_INET_PRE_ROUTING;
   hook1.pf = PF_INET;
   hook1.priority = NF_IP_PRI_FIRST;
   nf_register_net_hook(&init_net, &hook1);

   hook2.hook = preventTelnet;
   hook2.hooknum = NF_INET_PRE_ROUTING;
   hook2.pf = PF_INET;
   hook2.priority = NF_IP_PRI_FIRST;
   nf_register_net_hook(&init_net, &hook2);

   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);
}

module_init(registerFilter);
module_exit(removeFilter);

MODULE_LICENSE("GPL");


在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Task 2: Experimenting with Stateless Firewall Rules

Task 2.A: Protecting the Router

这个任务要做的就是允许ping,但是不允许其他操作
可以直接使用样例代码
命令解释:允许icmp数据包的发送和接受,其他的输入输出数据包全部丢弃

iptables -A INPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT
iptables -P OUTPUT DROP 
iptables -P INPUT DROP 

# 清除我们定义的规则
iptables -F
iptables -P FORWARD ACCEPT

在这里插入图片描述
在这里插入图片描述

Task 2.B: Protecting the Internal Network

这个任务的要求是

  • 外网机器不能ping内网
  • 外网机器能ping路由器
  • 内网机器能ping外网机器
  • 内网与外网之间的其他数据包需要被丢弃

命令解释:外网的icmp请求丢弃;内网的icmp请求放行;外网的icmp回复放行;其他数据包都丢弃

iptables -A FORWARD -i eth0 -p icmp --icmp-type echo-request -j DROP
iptables -A FORWARD -i eth1 -p icmp --icmp-type echo-request -j ACCEPT
iptables -A FORWARD -i eth0 -p icmp --icmp-type echo-reply -j ACCEPT
iptables -P FORWARD DROP

# 清除我们定义的规则
iptables -F
iptables -P FORWARD ACCEPT

内部主机可以ping外部主机
在这里插入图片描述
外部主机ping不通内部主机,但是ping的通路由器
在这里插入图片描述
内部主机和外部主机无法通信(这里是telnet包)
在这里插入图片描述

在这里插入图片描述

Task 2.C: Protecting Internal Servers

任务要求:

  • 外网只能远程登陆192.168.60.5,不能登陆其他内网机子
  • 外网机子不能连接内网服务
  • 内网机子可以使用其他内网机子的服务
  • 内网机子无法连接外网服务

命令解释:外网可以远程登陆192.168.60.5(第一行是远程登陆请求以及命令传输可以,第二行是192.168.60.5数据返回可以);其他数据包被丢弃

iptables -A FORWARD -i eth0 -p tcp -d 192.168.60.5 --dport 23 -j ACCEPT
iptables -A FORWARD -i eth1 -p tcp -s 192.168.60.5 --sport 23 -j ACCEPT
iptables -P FORWARD DROP

# 清除我们定义的规则
iptables -F
iptables -P FORWARD ACCEPT

外部机子可以连接上192.168.60.5,但是连接不上其他内部机子
在这里插入图片描述
在这里插入图片描述
内部机子可以连接其他内部机子,但是无法连接外部机子
在这里插入图片描述

Task 3: Connection Tracking and Stateful Firewall

Task 3.A: Experiment with the Connection Tracking

这个任务主要就是测试连接记录的情况,直接就运行样例代码就好了

// On 10.9.0.5, send out ICMP packets
# ping 192.168.60.5

观察到,ping之前是没有Connection Tracking的,ping的过程中有,ping结束之后立马查看还是有的,ping结束之后一段时间(几秒)查看就没有了。
在这里插入图片描述
在这里插入图片描述

// On 192.168.60.5, start a netcat UDP server
# nc -lu 9090
// On 10.9.0.5, send out UDP packets
# nc -u 192.168.60.5 9090
<type something, then hit return>

观察到,发送消息之前是没有Connection Tracking的,发送消息之后有了,发送消息之后一段时间(一分钟不到)查看就没有了。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

// On 192.168.60.5, start a netcat TCP server
# nc -l 9090
// On 10.9.0.5, send out TCP packets
# nc 192.168.60.5 9090
<type something, then hit return>

观察到,连接建立之后,发送消息之前就有Connection Tracking,发送消息之后Connection Tracking还是存在,发送消息之后一段时间Connection Tracking依旧存在,并且状态是ESTABLISHED。
没有发现Connection Tracking的消失,于是就关闭了TCP连接,发现Connection Tracking还是存在,状态是TIME_WAIT,之后等了一段时间(2-3分钟),Connection Tracking终于消失了。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Task 3.B: Setting Up a Stateful Firewall

与2.c除最后一点不同,其他都相同
任务要求:

  • 外网只能远程登陆192.168.60.5,不能登陆其他内网机子
  • 外网机子不能连接内网服务
  • 内网机子可以使用其他内网机子的服务
  • 内网机子可以连接外网服务

命令解释:第一行表示允许外部机子首次连接192.168.60.5;第二行表示允许内部机子和外部机子首次建立连接;第三行表示允许在之前建立过连接的机子继续连接;第四行表示拒绝其余的tcp请求;第四行表示允许其他数据包的交互。

iptables -A FORWARD -i eth0 -p tcp -d 192.168.60.5 --dport 23 --syn  -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD -i eth1 -p tcp --syn  -m conntrack --ctstate NEW -j ACCEPT
iptables -A FORWARD  -p tcp  -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD  -p tcp -j DROP
iptables -P FORWARD ACCEPT

iptables -F
iptables -P FORWARD ACCEPT

效果和2.c除最后一点不同,其他都相同

Task 4: Limiting Network Traffic

主要就是进行网络数据传输的限制,跑一下样例代码

iptables -A FORWARD -s 10.9.0.5 -m limit \
--limit 10/minute --limit-burst 5 -j ACCEPT
iptables -A FORWARD -s 10.9.0.5 -j DROP

没有第二行命令时,感觉上好像没有成功完成我们想要的限制网络数据传输的功能。
在这里插入图片描述

有第二行命令时,最开始的数据传送是正常速度,后面就开始非常缓慢了
在这里插入图片描述

解释:可能是因为没有第二条命令就相当于不知道如何处理超过限制的数据包,就按照默认规则继续传输了,有了第二条命令,就讲超过限制的数据包直接丢弃了。

Task 5: Load Balancing

让我们使用两个机制实现负载均衡

第一种是均匀分配的机制:

iptables -t nat -A PREROUTING -p udp --dport 8080 -m statistic --mode nth --every 3 --packet 0 -j DNAT --to-destination 192.168.60.5:8080
iptables -t nat -A PREROUTING -p udp --dport 8080 -m statistic --mode nth --every 2 --packet 0 -j DNAT --to-destination 192.168.60.6:8080
iptables -t nat -A PREROUTING -p udp --dport 8080 -m statistic --mode nth --every 1 --packet 0 -j DNAT --to-destination 192.168.60.7:8080

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
注意:要把之前定义的规则删掉哟,比如iptables -t nat -D PREROUTING 1

第二种是按照概率分配的机制:

iptables -t nat -A PREROUTING -p udp --dport 8080 \
-m statistic --mode random --probability 0.1 \
-j DNAT --to-destination 192.168.60.5:8080

iptables -t nat -A PREROUTING -p udp --dport 8080 \
-m statistic --mode random --probability 0.3 \
-j DNAT --to-destination 192.168.60.6:8080

iptables -t nat -A PREROUTING -p udp --dport 8080 \
-m statistic --mode random --probability 0.6 \
-j DNAT --to-destination 192.168.60.7:8080

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐