Linux内核防火墙设计与开发——报告版

1. 题目要求

基于netfilter框架,编写内核防火墙模块(驱动)和用户态防火墙控制程序,实现服务协议、IP地址、端口等的控制和过滤。

2. 基本设计思路

本次实验主要是针对于防火墙过滤功能的设计,通过在5个检查点NF_INET_PRE_ROUTING、NF_INET_LOCAL_IN、NF_INET_FORWARD、NF_INET_LOCAL_OUT、NF_INET_POST_ROUTING中hook自定义的对数据包的检查函数,即可实现对数据包的检查过滤。

本次实验基于实践学习的目的,对5个检查点分别注册相对应的过滤函数,最后对防火墙对的过滤功能进行检测。

  • 检查点NF_INET_PRE_ROUTING
    数据包刚刚进入网络层还没有进行路由前会通过这个检查点,并执行该检查点上注册的函数。

    本次实验在这个检查点中执行的对数据包的IP版本和校验和进行检测,若版本为非IPv4或者校验和错误,则直接丢弃该数据包。

  • 检查点NF_INET_LOCAL_IN
    在接收到的报文做路由后,确实是本机接收的报文之后,则会经过该检查点。

    本次实验在这个检查点中对数据包的源、目的IP地址和源、目的端口号以及协议类型进行黑名单过滤,若存在规则拒绝接收该数据包,则将该数据包丢弃。

  • 检查点NF_INET_FORWARD
    在接收到的数据包向另一个网卡进行转发之前会经过该检查点。

    本次实验中在该点对数据包的源、目的IP地址和协议类型进行黑名单过滤。若存在规则拒绝对该数据包进行转发,则直接丢弃该数据包。

  • 检查点NF_INET_LOCAL_OUT
    在本地数据包做发送路由之前会经过该检查点。

    本次实验在该点进行了对数据包的源、目的IP地址和协议类型进行白名单过滤。只有规则中允许发送的数据包,才能够从该检查点经过,否则直接将数据包丢弃。

  • 检查点NF_INET_POST_ROUTING
    任何马上要通过网络设备出去的包都会经过该检查点,这也是netfilter的最后一个检查点,在该点能够进行内置的目的地址的转换(地址伪装)等。

    本次实验不对该检查点进行数据包的过滤。

3.开发过程

3.1 基础结构

根据设计的需要编写头文件simpleFw.h,在头文件中定义结构体和一些常量参数。

##define CMD_MIN		0X6000

##define CMD_DEBUG		CMD_MIN+1
##define CMD_RULE		CMD_MIN+2
##define CMD_RULE_DEL	CMD_MIN+3

##define CMD_MAX		0X6100

##define SPFW_ICMP	1	//IPPROTO_ICMP
##define SPFW_TCP	2	//IPPROTO_TCP
##define SPFW_UDP	3	//TPPROTO_UDP


typedef struct{
    unsigned int src_ip;	//源IP地址
    unsigned int dst_ip;	//目标IP地址
    unsigned short src_port;	//源端口
    unsigned short dst_port;	//目的端口
    unsigned int protocol;		//使用的协议号
    int action;		//动作是否允许
} Rule;

typedef struct{
    unsigned int count;
    Rule rule;
}RuleTable;

3.2 防火墙基本框架

根据防火墙的基本开发方式实现,先编写防火墙过滤系统设计的基本框架。

基本框架结构如下:

##include <linux/module.h>
##include <linux/kernel.h>
##include <linux/skbuff.h>
##include <net/tcp.h>
##include <linux/netdevice.h>
##include <linux/netfilter.h>
##include <linux/netfilter_ipv4.h>

##include "simpleFw.h"

static struct nf_hook_ops nfhoLocalIn;		//设置 NF_INET_LOCAL_IN 的 hook 钩子点函数
static struct nf_hook_ops nfhoLocalOut;		//设置 NF_INET_LOCAL_OUT 的 hook 钩子点函数
static struct nf_hook_ops nfhoPreRouting;	//设置 NF_INET_PRE_ROUTING 的 hook 钩子点函数
static struct nf_hook_ops nfhoForward;		//设置 NF_INET_FORWARD 的 hook 钩子点函数
static struct nf_hook_ops nfhoPostRouting;	//设置 NF_INET_POST_ROUTING 的 hook 钩子点函数

static struct nf_sockopt_ops nfhoSockopt;	//设置 Socket Option 的属性

unsigned int hookLocalIn(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{

}

unsigned int hookLocalOut(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{

}

unsigned int hookPreRouting(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{

}

unsigned int hookForward(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{

}

unsigned int hookPostRouting(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{

}

int hookSockoptSet(struct sock *sock,
				   int cmd,
				   sockptr_t userPtr,
				   unsigned int len)
{

}

int hookSockoptGet(struct sock *sock,
				   int cmd,
				   void __user *user
				   int *len)
{

}

int init_module(){
	//将 hookLocalIn 函数注册到 NF_INET_LOCAL_IN 的 hook 钩子点
	nfhoLocalIn.hook = hookLocalIn;
	ntholocalIn.hooknum = NF_INET_LOCAL_IN;
	nfhoLocalIn.pf = PF_INET;
	nfhoLocalIn.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoLocalIn);
	//将 hookLocalOut 函数注册到 NF_INET_LOCAL_OUT 的 hook 钩子点
	nfhoLocalOut.hook = hookLocalOut;
	nfhoLocalOut.hooknum = NF_INET_LOCAL_OUT;
	nfhoLocalOut.pf = PF_INET;
	nfhoLocalOut.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoLocalOut);
	//将 hookPreRouting 函数注册到 NF_INET_PRE_ROUTING 的 hook 钩子点
	nfhoPreRouting.hook = hookPreRouting;
	nfhoPreRouting.hooknum = NF_INET_PRE_ROUTING;
	nfhoPreRouting.pf = PF_INET;
	nfhoPreRouting.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoPreRouting);
	//将 hookForward 函数注册到 NF_INET_FORWARD 的 hook 钩子点
	nfhoForward.hook = hookForward;
	nfhoForward.hooknum = NF_INET_FORWARD;
	nfhoForward.pf = PF_INET;
	nfhoForward.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoForward);
	//将 hookPostRouting 函数注册到 NF_INET_POST_ROUTING 的 hook 钩子点
	nfhoPostRouting.hook = hookPostRouting;
	nfhoPostRouting.hooknum = NF_INET_POST_ROUTING;
	nfhoPostRouting.pf = PF_INET;
	nfhoPostRouting.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoPostRouting);

	//注册nfhoSockopt
	nfhoSockopt.pf = PF_INET;
	nfhoSockopt.set_optmin = CMD_MIN;
	nfhoSockopt.set_optmax = CMD_MAX;
	nfhoSockopt.set = hookSockoptSet;
	nfhoSockopt.get_optmin = CMD_MIN;
	nfhoSockopt.get_optmax = CMD_MAX;
	nfhoSockopt.get = hookSockoptGet;
	nf_register_sockopt(&nfhoSockopt);
	
	
	printk("simpleFw started!\n");
	return 0;
}

void cleanup_module(){
	nf_unregister_net_hook(&init_net, &nfhoLocalIn);
	nf_unregister_net_hook(&init_net, &nfhoLocalOut);
	nf_unregister_net_hook(&init_net, &nfhoPreRouting);
	nf_unregister_net_hook(&init_net, &nfhoForward);
	nf_unregister_net_hook(&init_net, &nfhoPostRouting);

	nf_unregister_sockopt(&nfhoSockopt);

	printk("simpleFw stopped!\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("o_o'"); 
MODULE_DESCRIPTION("It's a simple software filter firewall!"); 
MODULE_VERSION("0.0.1"); 

3.3 内核通信用户实现

通过自定义get和set函数使用户能够与内核进行通信,使用户能够根据自己的需要设计防火墙过滤规则。

编写simpleFwctl.c程序使用户能够自定义防火墙过滤规则。

本次实验设计五种命令供用户使用

分别是添加规则Rule、删除规则Rule、查看规则、设计Debug等级、查看Debug等级

使用方式:./simpoleFwctl rule add 192.168.1.1 80 any any ICMP r(any 代表 任意)

  • ./simpleFwctl rule add src_ip src_port dst_ip dst_port protocol action //添加规则, aciton指拦截block还是放行forward
  • ./simpleFwctl rule del rule_number //删除规则
  • ./simpleFwctl rule //查看规则
  • ./simpleFwctl debug debug_level //设计debug等级
  • ./simpleFwctf debug //查看debug等级
##include <stdio.h>
##include <stdlib.h>
##include <string.h>
##include <sys/types.h>
##include <sys/socket.h>
##include <arpa/inet.h>
##include <unistd.h>
##include <pthread.h>
##include <signal.h>
##include <errno.h>

##include "simpleFw.h"

void usage(char *program)
{
	printf("%s debug\n", program);
	printf("%s debug debug_level\n", program);
	printf("%s rule add src_ip src_port dst_ip dst_port protocol a|r\n", program);
	printf("%s rule del rule_number\n", program);
	printf("%s rule\n", program);
}

void printError(char *msg)
{
	printf("%s error %d: %s\n", msg, errno, strerror(errno));
}

void printSuccess(char *msg)
{
	printf("%s success\n", msg);
}

unsigned int str2Ip(char *ipstr)
{
	unsigned int ip;
	if (!strcmp(ipstr, "any"))
	{
		ip = 0;
	}
	else
	{
		inet_pton(AF_INET,ipstr, &ip);
	}
	return ip;
}

char *ip2Str(unsigned int ip, char buf[32])
{
	if (ip)
	{
		unsigned char *c = (unsigned char *)&ip;
		sprintf(buf, "%d.%d.%d.%d", *c, *(c + 1), *(c + 2), *(c + 3));
	}
	else
	{
		sprintf(buf, "any");
	}
	return buf;
}

unsigned short str2Port(char *portstr)
{
	unsigned short port;
	if (!strcmp(portstr, "any"))
	{
		port = 0;
	}
	else
	{
		port = atoi(portstr);
	}
	return port;
}

char *port2Str(unsigned short port, char buf[16])
{
	if (port)
	{
		sprintf(buf, "%d", port);
	}
	else
	{
		sprintf(buf, "any");
	}
	return buf;
}

char *protocol2Str(unsigned short protocol, char buf[16])
{
	switch (protocol)
	{
	case 0:
		strcpy(buf, "any");
		break;
	case SPFW_ICMP:
		strcpy(buf, "ICMP");
		break;
	case SPFW_TCP:
		strcpy(buf, "TCP");
		break;
	case SPFW_UDP:
		strcpy(buf, "UDP");
		break;
	default:
		strcpy(buf, "Unknown");
	}
	return buf;
}

unsigned short str2Protocol(char *protstr)
{
	unsigned short protocol = 0;

	if (!strcmp(protstr, "any"))
	{
		protocol = 0;
	}
	else if (!strcmp(protstr, "ICMP"))
	{
		protocol = SPFW_ICMP;
	}
	else if (!strcmp(protstr, "TCP"))
	{
		protocol = SPFW_TCP;
	}
	else if (!strcmp(protstr, "UDP"))
	{
		protocol = SPFW_UDP;
	}

	return protocol;
}

int parseArgs(int argc, char** argv, int *cmd, void *val, int *val_len){
	int ret = 0;

	//若用户近输入了两个参数,则一定是查看命令
	if(argc == 2){
		if(!strcmp(argv[1], "debug")){
			*cmd = CMD_DEBUG;
			ret = -1;
		}
		else if(!strcmp(argv[1], "rule")){
			*cmd = CMD_RULE;
			ret = -1;
		}
	}
	// 若用户输入的参数大于2,则应该是添加规则Rule、删除规则Rule、修改debug等级其中之一
	else if(argc > 2){
		//若argc等于3且第2个参数为 debug,则应为修改debug等级
		if(!strcmp(argv[1], "debug") && argc == 3){
			*cmd = CMD_DEBUG;
			*(int *)val = atoi(argv[2]);
			*val_len = sizeof(int);
			ret = 1;
		}
		// 判断是否是与规则的添加和删除有关的命令
		else if(!strcmp(argv[1], "rule")){
			//若输入的参数数量为4,则只可能是规则删除指令
			if(argc == 4){
				if(!strcmp(argv[2], "del")){
					*cmd = CMD_RULE_DEL;
					*(int *)val = atoi(argv[3]);
					ret = 1;
				}
			}
			//若输入的参数数量为9,则只可能是添加规则Rule的指令
			else if(argc == 9){
				if(!strcmp(argv[2], "add")){
					*cmd = CMD_RULE;
					Rule *r = (Rule *)val;
					*val_len = sizeof(Rule);
					r->src_ip = str2Ip(argv[3]);
					r->src_port = str2Port(argv[4]);
					r->dst_ip = str2Ip(argv[5]);
					r->dst_port = str2Port(argv[6]);
					r->protocol = str2Protocol(argv[7]);
					r->action = strcmp(argv[8], "a") ? 0 : 1;
					ret = 1;
				}
			}
		}
	}
	return ret;
}

void printRuleTable(RuleTable *rtb)
{
	char src_ip[32], dst_ip[32], src_port[16], dst_port[16], protocol[16];
	Rule *r = &(rtb->rule);
	printf("Rules count: %d\n", rtb->count);
	for (int i = 0; i < rtb->count; i++)
	{
		ip2Str(r->src_ip, src_ip);
		ip2Str(r->dst_ip, dst_ip);
		port2Str(r->src_port, src_port);
		port2Str(r->dst_port, dst_port);
		protocol2Str(r->protocol, protocol);
		printf("%d\t%s:%s -> %s:%s, %s is %s\n", i + 1, src_ip, src_port, dst_ip, dst_port, protocol, r->action ? "allow" : "reject");
		r = r + 1;
	}
}

int set(int cmd ,void* val, int val_len, int sockfd){
	int ret = -1;

	if (setsockopt(sockfd, IPPROTO_IP, cmd, val, val_len))
	{
		printError("setsockopt()");
	}
	else
	{
		printf("setsockopt() success\n");
		ret = 0;
	}

	return ret;
}

int get(int cmd, int sockfd){
	int ret = -1;
	int val_len = 1024 * 1024;
	void *val = malloc(val_len);
	if (getsockopt(sockfd, IPPROTO_IP, cmd, val, &val_len))
	{
		printError("getsockopt");
	}
	else
	{
		switch (cmd)
		{
		case CMD_DEBUG:
			printf("debug level=%d\n", *(int *)val);
			break;
		case CMD_RULE:
			printRuleTable((RuleTable *)val);
			break;
		}
	}
	return ret;
}

int main(int argc, char** argv){
	int ret = -1;
	int cmd;				//记录用户输入的命令的值
	char val[sizeof(Rule)];	//存储用户输入相关数据	规则、debug等级或者规则索引
	int val_len = 0;		//记录相关数据长度
	int get_set = parseArgs(argc, argv, &cmd, &val, &val_len);
	if(get_set){
		int sockfd;
		if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1){
			printError("socket()");
		}
		else {
			// get_set > 0, 表示需要向内核中发送数据
			if(get_set > 0){
				ret = set(cmd, val, val_len, sockfd);
			}
			else { 
				ret = get(cmd, sockfd);
			}
		}
		close(sockfd);
	}
	else{
		usage(argv[0]);
	}
	return ret;
}

3.4 内核通信内核实现

要实现内核的通过需要通过使用自定义的setSockOpt()和getSockOpt()函数,通过注册自定义函数的set和get函数接收用户发送到内核的数据,判断数据类型再进行相应的处理。

根据用户相内核发送的信息内容,内核执行相应的响应。对用户对规则的增删改查,在内核中实现相应的功能。

实现代码如下:

void addRule(Rule *rule){
	int cnt = g_rules_cnt + 1;	//记录添加一条规则后的规则总数
	Rule *rules_t = (Rule *)vmalloc(cnt*sizeof(Rule));	//开辟一个必规则数量大 1 的空间, 使之能够添加一条规则
	memcpy(rules_t, rule, sizeof(Rule));	//将新的规则先添加到开辟的空间中
	if(g_rules_cnt > 0){
		memcpy(rules_t + 1, g_rules, g_rules_cnt*sizeof(Rule));	//再将原先的规则也添加的新开辟的空间中
		vfree(g_rules);		//释放原先的规则空间
	}
	g_rules = rules_t;	//令规则数组的首地址为新开辟的空间
	g_rules_cnt = cnt;	//更新规则数
}

void delRule(int rule_num){
	int i;
	//判断规则序号是否存在
	if(rule_num>0 && rule_num<g_rules_cnt){
		//存在则将该序号及其以后的规则用下一条规则的内容覆盖
		for(i = rule_num; i < g_rules_cnt; i++){
			memcpy(g_rules+i-1, g_rules+i, sizeof(Rule));
		}
		g_rules_cnt++;	//规则数减 1
	}
}

void setDebug_Level(int level){
	debug_level = level;
}

int hookSockoptSet(struct sock *sock,
				   int cmd,
				   sockptr_t userPtr,
				   unsigned int len)
{
	int ret = 0;
	int level;
	Rule r;
	int r_num;

	debugInfo("hookSockoptSet");
	//根据 cmd 的不同,接受不同大小的数据,并执行相应的操作
	switch(cmd){
		case CMD_DEBUG:
			//从用户空间中拷贝接收用户设置的用户等级,即一个整形int数据
			ret = copy_from_user(&level, userPtr.user, sizeof(debug_level));
			setDebug_Level(level);	//执行debug等级修改函数
			printk("set debug level to %d", debug_level);
			break;
		case CMD_RULE:
			//从用户空间中拷贝接收用户设置的规则Rule内容
			ret = copy_from_user(&r, userPtr.user, sizeof(Rule));
			addRule(&r);	//执行添加规则函数
			printk("add rule!");
			break;
		case CMD_RULE_DEL:
			//从用户空间中拷贝接收用户所需要删除的规则序号,一个整形int数据
			ret = copy_from_user(&r_num, userPtr.user, sizeof(r_num));
			delRule(r_num);	//执行删除规则函数
			printk("del rule");
			break;
	}

	if(ret != 0){
		printk("copy_from_user error!");
		ret = -EINVAL;
	}
	return ret;
}

int hookSockoptGet(struct sock *sock,
				   int cmd,
				   void __user *user,
				   int *len)
{
	int ret;

	debugInfo("hookSockoptGet");
	//根据用户的命令,向用户空间发送相应的数据
	switch(cmd){
		case CMD_DEBUG:
			//向用户空间发送debug_level
			ret = copy_to_user(user, &debug_level, sizeof(debug_level));
			break;
		case CMD_RULE:
			//向用户空间发送规则条数
			ret = copy_to_user(user, &g_rules_cnt, sizeof(g_rules_cnt));
			//向用户空间发送规则数组(注意:由于规则条数发送过一次数据,所以再次发送数据需要添加已发送的数据偏移)
			ret = copy_to_user(user+sizeof(g_rules_cnt), g_rules, g_rules_cnt*sizeof(Rule));
			break;
	}

	if(ret != 0){
		ret = -EINVAL;
		debugInfo("copy_to_user error");
	}

	return ret;
}

3.5 过滤功能实现

  1. 检查点NF_INET_PRE_ROUTING

该检查点中执行自定义函数对数据包的IP版本和校验和进行检测,若版本为非IPv4或者校验和错误,则直接丢弃该数据包。

unsigned int hookPreRouting(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_ACCEPT;
	struct iphdr *iph = ip_hdr(skb);
	//判断IP数据包是否为IPv4版本数据包, 不是则丢弃
	if(iph->version != 4){
		ret = NF_DROP;	
	}
	//校验 IPv4 数据报头的正确性, 不正确则丢弃
	if(iph->check){
		if(ip_fast_csum((unsigned char *)iph, iph->ihl)){
			ret = NF_DROP;
		}
	}
	debugInfo("hookPreRouting");
	return ret;
}
  1. 检查点NF_INET_LOCAL_IN

该检查点中执行的自定义函数对数据包的源、目的IP地址和源、目的端口以及协议类型进行过滤。

根据不同类型的协议,获取不同协议的协议头查看端口号,判断是否与规则中的端口号相匹配。

int matchRule_IP_PORT_PROTOCOL(struct iphdr *iph){
	int action = 1;
	int i;
	Rule *r;
	for (i = 0; i < g_rules_cnt; i++) {
		r = g_rules + i;
		//判断是否符合源、目的地址IP地址
		if((!r->src_ip || r->src_ip == iph->saddr) &&
		   (!r->dst_ip || r->dst_ip == iph->daddr)){
			if(!r->protocol){
				action = r->action;
			}
			else {
				//ICMP协议不需要过滤端口
				if(r->protocol == SPFW_ICMP){
					action = r->action;
					break;
				}
				//对TCP协议的端口过滤
				else if(r->protocol == SPFW_TCP){
					struct tcphdr *tcph = (struct tcphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取TCP头
					if((!r->src_port || r->src_port == ntohs(tcph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(tcph->dest))){
						action = r->action;
					}
					break;
				}
				//对UDP协议的端口过滤
				else if (r->protocol == SPFW_UDP) {
					struct udphdr *udph = (struct udphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取UDP头
					if((!r->src_port || r->src_port == ntohs(udph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(udph->dest))){
						action = r->action;
					}
					break;
				}
				//若规则的协议未非TCP、UDP、ICMP其中之一, 则丢弃该数据包
				else{
					action = 0;
					break;
				}
			}
		}
		else{
			action = 0;
		}
	}
	return action;
}

unsigned int hookLocalIn(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_ACCEPT;
	//将数据包结构体skb转换成struct iphdr
	struct iphdr *iph = ip_hdr(skb);
	//与规则逐一匹配, 判断规则是否拦截该数据包, 默认数据包不被拦截
	//黑名单过滤方式
	if(matchRule_IP_PORT_PROTOCOL(iph) <= 0){
		printk("NF_INET_LOCAL_IN检查点过滤了来自数据包\n源地址:%d\t目的地址:%d\t协议:%d\n", iph->saddr, iph->daddr, iph->protocol);
		ret = NF_DROP;
	}
	debugInfo("hookLocalIn");
	return ret;
}
  1. 检查点NF_INET_LOCAL_FORWARD

该检查点中执行的自定义过滤函数仅对数据包的源、目的IP地址和协议类型进行过滤,不对端口号进行过滤。

int matchRule_IP_PROTOCOL(struct iphdr *iph){
	int action = 1;
	int i;
	Rule *r;
	//对规则进行逐一检查, 若存在相应的规则则返回相应的动作
	for (i = 0; i < g_rules_cnt; i++) { 
		r = g_rules + i;
		//若规则中未定义源IP和目的IP, 则默认规则值为0
		if((!r->src_ip || r->src_ip==iph->saddr) &&
		   (!r->dst_ip || r->dst_ip==iph->daddr) &&
		   (!r->protocol || r->protocol==iph->protocol))
		{
			action = r->action;	
			break;
		}
	}
	return action;
}

unsigned int hookForward(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_ACCEPT;
	//将数据包结构体skb转换成struct iphdr
	struct iphdr *iph = ip_hdr(skb);
	//与规则逐一匹配, 判断规则是否拦截该数据包, 默认数据包不被拦截
	//黑名单过滤方式
	if(matchRule_IP_PROTOCOL(iph) <= 0){
		ret = NF_DROP;
	}
	debugInfo("hookForwoad");
	return ret;
}
  1. 检查点NF_INET_LOCAL_OUT

该检查点执行的自定义函数对数据包的源、目的IP地址和协议类型进行白名单过滤。只有规则中允许发送的数据包,才能够从该检查点经过,否则直接将数据包丢弃。

int whiteListFilterRule(struct iphdr *iph){
	int action = 0;
	int i;
	Rule *r;
	//匹配所有规则, 若存在规则允许数据包通过则允许该数据包通过, 否则丢弃该数据包
	for (i = 0; i < g_rules_cnt; i++) {
		r = g_rules + i;
		//判断是否符合源、目的地址IP地址
		if((!r->src_ip || r->src_ip == iph->saddr) &&
		   (!r->dst_ip || r->dst_ip == iph->daddr)){
			if(!r->protocol){
				action = r->action;
			}
			else {
				//对ICMP协议的端口过滤
				if(r->protocol == SPFW_ICMP){
					struct icmphdr *icmph = (struct icmphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取ICMP头
					if((!r->src_port || r->src_port == ntohs(icmph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(icmph->dest))){
						action = r->action;
					}
				}
				//对TCP协议的端口过滤
				else if(r->protocol == SPFW_TCP){
					struct tcphdr *tcph = (struct tcphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取TCP头
					if((!r->src_port || r->src_port == ntohs(tcph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(tcph->dest))){
						action = r->action;
					}
				}
				//对UDP协议的端口过滤
				else if (r->protocol == SPFW_UDP) {
					struct udphdr *udph = (struct udphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取UDP头
					if((!r->src_port || r->src_port == ntohs(udph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(udph->dest))){
						action = r->action;
					}
				}
			}
		}
		if(action == 1)
			break;
	}
	return action;
}

unsigned int hookLocalOut(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_ACCEPT;
	//将数据包结构体skb转换成struct iphdr
	struct iphdr *iph = ip_hdr(skb);
	//与规则逐一匹配, 判断规则是否拦截该数据包, 默认数据包不被拦截
	//黑名单过滤方式
	if(whiteListFilterRule(iph) >= 1){
		ret = NF_DROP;
	}
	debugInfo("hookLocalOut");
	return ret;
}

4. 源代码

4.1 simpleFwctl.c

##include <stdio.h>
##include <stdlib.h>
##include <string.h>
##include <sys/types.h>
##include <sys/socket.h>
##include <arpa/inet.h>
##include <unistd.h>
##include <pthread.h>
##include <signal.h>
##include <errno.h>

##include "simpleFw.h"

void usage(char *program)
{
	printf("%s debug\n", program);
	printf("%s debug debug_level\n", program);
	printf("%s rule add src_ip src_port dst_ip dst_port protocol a|r\n", program);
	printf("%s rule del rule_number\n", program);
	printf("%s rule\n", program);
}

void printError(char *msg)
{
	printf("%s error %d: %s\n", msg, errno, strerror(errno));
}

void printSuccess(char *msg)
{
	printf("%s success\n", msg);
}

unsigned int str2Ip(char *ipstr)
{
	unsigned int ip;
	if (!strcmp(ipstr, "any"))
	{
		ip = 0;
	}
	else
	{
		inet_pton(AF_INET,ipstr, &ip);
	}
	return ip;
}

char *ip2Str(unsigned int ip, char buf[32])
{
	if (ip)
	{
		unsigned char *c = (unsigned char *)&ip;
		sprintf(buf, "%d.%d.%d.%d", *c, *(c + 1), *(c + 2), *(c + 3));
	}
	else
	{
		sprintf(buf, "any");
	}
	return buf;
}

unsigned short str2Port(char *portstr)
{
	unsigned short port;
	if (!strcmp(portstr, "any"))
	{
		port = 0;
	}
	else
	{
		port = atoi(portstr);
	}
	return port;
}

char *port2Str(unsigned short port, char buf[16])
{
	if (port)
	{
		sprintf(buf, "%d", port);
	}
	else
	{
		sprintf(buf, "any");
	}
	return buf;
}

char *protocol2Str(unsigned short protocol, char buf[16])
{
	switch (protocol)
	{
	case 0:
		strcpy(buf, "any");
		break;
	case SPFW_ICMP:
		strcpy(buf, "ICMP");
		break;
	case SPFW_TCP:
		strcpy(buf, "TCP");
		break;
	case SPFW_UDP:
		strcpy(buf, "UDP");
		break;
	default:
		strcpy(buf, "Unknown");
	}
	return buf;
}

unsigned short str2Protocol(char *protstr)
{
	unsigned short protocol = 0;

	if (!strcmp(protstr, "any"))
	{
		protocol = 0;
	}
	else if (!strcmp(protstr, "ICMP"))
	{
		protocol = SPFW_ICMP;
	}
	else if (!strcmp(protstr, "TCP"))
	{
		protocol = SPFW_TCP;
	}
	else if (!strcmp(protstr, "UDP"))
	{
		protocol = SPFW_UDP;
	}

	return protocol;
}

int parseArgs(int argc, char** argv, int *cmd, void *val, int *val_len){
	int ret = 0;

	//若用户近输入了两个参数,则一定是查看命令
	if(argc == 2){
		if(!strcmp(argv[1], "debug")){
			*cmd = CMD_DEBUG;
			ret = -1;
		}
		else if(!strcmp(argv[1], "rule")){
			*cmd = CMD_RULE;
			ret = -1;
		}
	}
	// 若用户输入的参数大于2,则应该是添加规则Rule、删除规则Rule、修改debug等级其中之一
	else if(argc > 2){
		//若argc等于3且第2个参数为 debug,则应为修改debug等级
		if(!strcmp(argv[1], "debug") && argc == 3){
			*cmd = CMD_DEBUG;
			*(int *)val = atoi(argv[2]);
			*val_len = sizeof(int);
			ret = 1;
		}
		// 判断是否是与规则的添加和删除有关的命令
		else if(!strcmp(argv[1], "rule")){
			//若输入的参数数量为4,则只可能是规则删除指令
			if(argc == 4){
				if(!strcmp(argv[2], "del")){
					*cmd = CMD_RULE_DEL;
					*(int *)val = atoi(argv[3]);
					ret = 1;
				}
			}
			//若输入的参数数量为9,则只可能是添加规则Rule的指令
			else if(argc == 9){
				if(!strcmp(argv[2], "add")){
					*cmd = CMD_RULE;
					Rule *r = (Rule *)val;
					*val_len = sizeof(Rule);
					r->src_ip = str2Ip(argv[3]);
					r->src_port = str2Port(argv[4]);
					r->dst_ip = str2Ip(argv[5]);
					r->dst_port = str2Port(argv[6]);
					r->protocol = str2Protocol(argv[7]);
					r->action = strcmp(argv[8], "a") ? 0 : 1;
					ret = 1;
				}
			}
		}
	}
	return ret;
}

void printRuleTable(RuleTable *rtb)
{
	char src_ip[32], dst_ip[32], src_port[16], dst_port[16], protocol[16];
	Rule *r = &(rtb->rule);
	printf("Rules count: %d\n", rtb->count);
	for (int i = 0; i < rtb->count; i++)
	{
		ip2Str(r->src_ip, src_ip);
		ip2Str(r->dst_ip, dst_ip);
		port2Str(r->src_port, src_port);
		port2Str(r->dst_port, dst_port);
		protocol2Str(r->protocol, protocol);
		printf("%d\t%s:%s -> %s:%s, %s is %s\n", i + 1, src_ip, src_port, dst_ip, dst_port, protocol, r->action ? "allow" : "reject");
		r = r + 1;
	}
}

int set(int cmd ,void* val, int val_len, int sockfd){
	int ret = -1;

	if (setsockopt(sockfd, IPPROTO_IP, cmd, val, val_len))
	{
		printError("setsockopt()");
	}
	else
	{
		printf("setsockopt() success\n");
		ret = 0;
	}

	return ret;
}

int get(int cmd, int sockfd){
	int ret = -1;
	int val_len = 1024 * 1024;
	void *val = malloc(val_len);
	if (getsockopt(sockfd, IPPROTO_IP, cmd, val, &val_len))
	{
		printError("getsockopt");
	}
	else
	{
		switch (cmd)
		{
		case CMD_DEBUG:
			printf("debug level=%d\n", *(int *)val);
			break;
		case CMD_RULE:
			printRuleTable((RuleTable *)val);
			break;
		}
	}
	return ret;
}

int main(int argc, char** argv){
	int ret = -1;
	int cmd;				//记录用户输入的命令的值
	char val[sizeof(Rule)];	//存储用户输入相关数据	规则、debug等级或者规则索引
	int val_len = 0;		//记录相关数据长度
	int get_set = parseArgs(argc, argv, &cmd, &val, &val_len);
	if(get_set){
		int sockfd;
		if((sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW)) == -1){
			printError("socket()");
		}
		else {
			// get_set > 0, 表示需要向内核中发送数据
			if(get_set > 0){
				ret = set(cmd, val, val_len, sockfd);
			}
			else { 
				ret = get(cmd, sockfd);
			}
		}
		close(sockfd);
	}
	else{
		usage(argv[0]);
	}
	return ret;
}

4.2 simpleFw.c

##include <linux/module.h>
##include <linux/kernel.h>
##include <linux/skbuff.h>
##include <net/tcp.h>
##include <linux/netdevice.h>
##include <linux/netfilter.h>
##include <linux/netfilter_ipv4.h>

##include "simpleFw.h"

static struct nf_hook_ops nfhoLocalIn;		//设置NF_INET_LOCAL_IN的hook点函数
static struct nf_hook_ops nfhoLocalOut;		//设置NF_INET_LOCAL_OUT的hook点函数
static struct nf_hook_ops nfhoPreRouting;	//设置NF_INET_PRE_ROUTING的hook点函数
static struct nf_hook_ops nfhoForward;		//设置NF_INET_FORWARD的hook点函数
static struct nf_hook_ops nfhoPostRouting;	//设置NF_INET_POST_ROUTING的hook点函数

static struct nf_sockopt_ops nfhoSockopt;	//设置 Socket Option 的属性

static int debug_level = 0;
static int nfcount = 0;

static Rule *g_rules;			//规则数组的头地址
static int g_rules_cnt = 0;		//记录当前定义的规则数

void addRule(Rule *rule){
	int cnt = g_rules_cnt + 1;	//记录添加一条规则后的规则总数
	Rule *rules_t = (Rule *)vmalloc(cnt*sizeof(Rule));	//开辟一个必规则数量大 1 的空间, 使之能够添加一条规则
	memcpy(rules_t, rule, sizeof(Rule));	//将新的规则先添加到开辟的空间中
	if(g_rules_cnt > 0){
		memcpy(rules_t + 1, g_rules, g_rules_cnt*sizeof(Rule));	//再将原先的规则也添加的新开辟的空间中
		vfree(g_rules);		//释放原先的规则空间
	}
	g_rules = rules_t;	//令规则数组的首地址为新开辟的空间
	g_rules_cnt = cnt;	//更新规则数
}

void delRule(int rule_num){
	int i;
	//判断规则序号是否存在
	if(rule_num>0 && rule_num<g_rules_cnt){
		//存在则将该序号及其以后的规则用下一条规则的内容覆盖
		for(i = rule_num; i < g_rules_cnt; i++){
			memcpy(g_rules+i-1, g_rules+i, sizeof(Rule));
		}
		g_rules_cnt++;	//规则数减 1
	}
}

int matchRule_IP_PROTOCOL(struct iphdr *iph){
	int action = 1;
	int i;
	Rule *r;
	//对规则进行逐一检查, 若存在相应的规则则返回相应的动作
	for (i = 0; i < g_rules_cnt; i++) { 
		r = g_rules + i;
		//若规则中未定义源IP和目的IP, 则默认规则值为0
		if((!r->src_ip || r->src_ip==iph->saddr) &&
		   (!r->dst_ip || r->dst_ip==iph->daddr) &&
		   (!r->protocol || r->protocol==iph->protocol))
		{
			action = r->action;	
			break;
		}
	}
	return action;
}

int matchRule_IP_PORT_PROTOCOL(struct iphdr *iph){
	int action = 1;
	int i;
	Rule *r;
	for (i = 0; i < g_rules_cnt; i++) {
		r = g_rules + i;
		//判断是否符合源、目的地址IP地址
		if((!r->src_ip || r->src_ip == iph->saddr) &&
		   (!r->dst_ip || r->dst_ip == iph->daddr)){
			if(!r->protocol){
				action = r->action;
			}
			else {
				//ICMP协议不需要过滤端口
				if(r->protocol == SPFW_ICMP){
					action = r->action;
					break;
				}
				//对TCP协议的端口过滤
				else if(r->protocol == SPFW_TCP){
					struct tcphdr *tcph = (struct tcphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取TCP头
					if((!r->src_port || r->src_port == ntohs(tcph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(tcph->dest))){
						action = r->action;
					}
					break;
				}
				//对UDP协议的端口过滤
				else if (r->protocol == SPFW_UDP) {
					struct udphdr *udph = (struct udphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取UDP头
					if((!r->src_port || r->src_port == ntohs(udph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(udph->dest))){
						action = r->action;
					}
					break;
				}
				//若规则的协议未非TCP、UDP、ICMP其中之一, 则丢弃该数据包
				else{
					action = 0;
					break;
				}
			}
		}
	}
	return action;
}

int whiteListFilterRule(struct iphdr *iph){
	int action = 0;
	int i;
	Rule *r;
	//匹配所有规则, 若存在规则允许数据包通过则允许该数据包通过, 否则丢弃该数据包
	for (i = 0; i < g_rules_cnt; i++) {
		r = g_rules + i;
		//判断是否符合源、目的地址IP地址
		if((!r->src_ip || r->src_ip == iph->saddr) &&
		   (!r->dst_ip || r->dst_ip == iph->daddr)){
			if(!r->protocol){
				action = r->action;
			}
			else {
				//ICMP协议不需要过滤端口
				if(r->protocol == SPFW_ICMP){
					action = r->action;
				}
				//对TCP协议的端口过滤
				else if(r->protocol == SPFW_TCP){
					struct tcphdr *tcph = (struct tcphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取TCP头
					if((!r->src_port || r->src_port == ntohs(tcph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(tcph->dest))){
						action = r->action;
					}
				}
				//对UDP协议的端口过滤
				else if (r->protocol == SPFW_UDP) {
					struct udphdr *udph = (struct udphdr *)((unsigned char *)iph + iph->ihl*4);	// 获取UDP头
					if((!r->src_port || r->src_port == ntohs(udph->source)) &&
					   (!r->dst_port || r->dst_port == ntohs(udph->dest))){
						action = r->action;
					}
				}
			}
		}
		if(action == 1)
			break;
	}
	return action;
}

void setDebug_Level(int level){
	debug_level = level;
}

void debugInfo(char *msg)
{
	if (debug_level)
	{
		nfcount++;
		printk("%s, nfcount: %d\n", msg, nfcount);
	}
}


unsigned int hookLocalIn(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_ACCEPT;
	//将数据包结构体skb转换成struct iphdr
	struct iphdr *iph = ip_hdr(skb);
	//与规则逐一匹配, 判断规则是否拦截该数据包, 默认数据包不被拦截
	//黑名单过滤方式
	if(matchRule_IP_PORT_PROTOCOL(iph) <= 0){
		printk("NF_INET_LOCAL_IN过滤了数据包");
		ret = NF_DROP;
	}
	debugInfo("hookLocalIn");
	return ret;
}

unsigned int hookLocalOut(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_DROP;
	//将数据包结构体skb转换成struct iphdr
	struct iphdr *iph = ip_hdr(skb);
	//与规则逐一匹配, 判断规则是否拦截该数据包, 默认数据包不被拦截
	//白名单过滤方式
	if(whiteListFilterRule(iph) >= 1){
		printk("NF_INET_LOCAL_OUT允许通过了数据包");
		ret = NF_ACCEPT;
	}
	debugInfo("hookLocalOut");
	return ret;
}

unsigned int hookPreRouting(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_ACCEPT;
	struct iphdr *iph = ip_hdr(skb);
	//判断IP数据包是否为IPv4版本数据包, 不是则丢弃
	if(iph->version != 4){
		printk("NF_INET_PRE_ROUTING过滤了数据包");
		ret = NF_DROP;	
	}
	//校验 IPv4 数据报头的正确性, 不正确则丢弃
	if(iph->check){
		if(ip_fast_csum((unsigned char *)iph, iph->ihl)){
			ret = NF_DROP;
		}
	}
	debugInfo("hookPreRouting");
	return ret;
}	

unsigned int hookForward(void *priv,
						 struct sk_buff *skb,
						 const struct nf_hook_state *state)
{
	unsigned int ret = NF_ACCEPT;
	//将数据包结构体skb转换成struct iphdr
	struct iphdr *iph = ip_hdr(skb);
	//与规则逐一匹配, 判断规则是否拦截该数据包, 默认数据包不被拦截
	//黑名单过滤方式
	if(matchRule_IP_PROTOCOL(iph) <= 0){
		printk("NF_INET_FORWARD过滤了数据包");
		ret = NF_DROP;
	}
	debugInfo("hookForwoad");
	return ret;
}

unsigned int hookPostRouting(void *priv,
						 	 struct sk_buff *skb,
						 	 const struct nf_hook_state *state)
{
	debugInfo("hookPostRouting");
	return NF_ACCEPT;
}

int hookSockoptSet(struct sock *sock,
				   int cmd,
				   sockptr_t userPtr,
				   unsigned int len)
{
	int ret = 0;
	int level;
	Rule r;
	int r_num;

	debugInfo("hookSockoptSet");
	//根据 cmd 的不同,接受不同大小的数据,并执行相应的操作
	switch(cmd){
		case CMD_DEBUG:
			//从用户空间中拷贝接收用户设置的用户等级,即一个整形int数据
			ret = copy_from_user(&level, userPtr.user, sizeof(debug_level));
			setDebug_Level(level);	//执行debug等级修改函数
			printk("set debug level to %d", debug_level);
			break;
		case CMD_RULE:
			//从用户空间中拷贝接收用户设置的规则Rule内容
			ret = copy_from_user(&r, userPtr.user, sizeof(Rule));
			addRule(&r);	//执行添加规则函数
			printk("add rule!");
			break;
		case CMD_RULE_DEL:
			//从用户空间中拷贝接收用户所需要删除的规则序号,一个整形int数据
			ret = copy_from_user(&r_num, userPtr.user, sizeof(r_num));
			delRule(r_num);	//执行删除规则函数
			printk("del rule");
			break;
	}

	if(ret != 0){
		printk("copy_from_user error!");
		ret = -EINVAL;
	}
	return ret;
}

int hookSockoptGet(struct sock *sock,
				   int cmd,
				   void __user *user,
				   int *len)
{
	int ret;

	debugInfo("hookSockoptGet");
	//根据用户的命令,向用户空间发送相应的数据
	switch(cmd){
		case CMD_DEBUG:
			//向用户空间发送debug_level
			ret = copy_to_user(user, &debug_level, sizeof(debug_level));
			break;
		case CMD_RULE:
			//向用户空间发送规则条数
			ret = copy_to_user(user, &g_rules_cnt, sizeof(g_rules_cnt));
			//向用户空间发送规则数组(注意:由于规则条数发送过一次数据,所以再次发送数据需要添加已发送的数据偏移)
			ret = copy_to_user(user+sizeof(g_rules_cnt), g_rules, g_rules_cnt*sizeof(Rule));
			break;
	}

	if(ret != 0){
		ret = -EINVAL;
		debugInfo("copy_to_user error");
	}

	return ret;
}

int init_module(){
	//将 hookLocalIn 函数注册到 NF_INET_LOCAL_IN 的 hook 钩子点
	nfhoLocalIn.hook = hookLocalIn;
	nfhoLocalIn.hooknum = NF_INET_LOCAL_IN;
	nfhoLocalIn.pf = PF_INET;
	nfhoLocalIn.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoLocalIn);
	//将 hookLocalOut 函数注册到 NF_INET_LOCAL_OUT 的 hook 钩子点
	nfhoLocalOut.hook = hookLocalOut;
	nfhoLocalOut.hooknum = NF_INET_LOCAL_OUT;
	nfhoLocalOut.pf = PF_INET;
	nfhoLocalOut.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoLocalOut);
	//将 hookPreRouting 函数注册到 NF_INET_PRE_ROUTING 的 hook 钩子点
	nfhoPreRouting.hook = hookPreRouting;
	nfhoPreRouting.hooknum = NF_INET_PRE_ROUTING;
	nfhoPreRouting.pf = PF_INET;
	nfhoPreRouting.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoPreRouting);
	//将 hookForward 函数注册到 NF_INET_FORWARD 的 hook 钩子点
	nfhoForward.hook = hookForward;
	nfhoForward.hooknum = NF_INET_FORWARD;
	nfhoForward.pf = PF_INET;
	nfhoForward.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoForward);
	//将 hookPostRouting 函数注册到 NF_INET_POST_ROUTING 的 hook 钩子点
	nfhoPostRouting.hook = hookPostRouting;
	nfhoPostRouting.hooknum = NF_INET_POST_ROUTING;
	nfhoPostRouting.pf = PF_INET;
	nfhoPostRouting.priority = NF_IP_PRI_FIRST;
	nf_register_net_hook(&init_net, &nfhoPostRouting);

	//注册nfhoSockopt
	nfhoSockopt.pf = PF_INET;
	nfhoSockopt.set_optmin = CMD_MIN;
	nfhoSockopt.set_optmax = CMD_MAX;
	nfhoSockopt.set = hookSockoptSet;
	nfhoSockopt.get_optmin = CMD_MIN;
	nfhoSockopt.get_optmax = CMD_MAX;
	nfhoSockopt.get = hookSockoptGet;
	nf_register_sockopt(&nfhoSockopt);

	printk("simpleFw started!\n");
	return 0;
}

void cleanup_module(){
	nf_unregister_net_hook(&init_net, &nfhoLocalIn);
	nf_unregister_net_hook(&init_net, &nfhoLocalOut);
	nf_unregister_net_hook(&init_net, &nfhoPreRouting);
	nf_unregister_net_hook(&init_net, &nfhoForward);
	nf_unregister_net_hook(&init_net, &nfhoPostRouting);

	nf_unregister_sockopt(&nfhoSockopt);

	printk("simpleFw stopped!\n");
}

MODULE_LICENSE("GPL");
MODULE_AUTHOR("o_o'"); 
MODULE_DESCRIPTION("It's a simple software filter firewall!"); 
MODULE_VERSION("0.0.1"); 

5. 测试效果

  • simpleFwctl.c
gcc simpleFwctl.c -o simpleFwctl

image-20230505202956744

  • simpleFw编译安装防火墙过滤模块
make
sudo insmod simpleFw.ko
lsmod

image-20230505203109827

查看防火墙过滤规则Rule和debug_level,并设置debug_level为1

image-20230505203356267

在为添加任何规则的情况下,我们尝试使用Linux系统ping主机Windows系统,发现无法ping通。相反地,我们使用windows系统pingLinux系统,发现同样也无法ping通。

在此我们可以进行初步的判定是由于检查点NF_INET_LOCAL_OUT的白名单过滤导致,Linux系统中无法发出任何数据包导致系统双方无法ping通。通过wireshark抓包,可以发现windows系统确实将数据包发送出去了。

注:由于使用的是wsl2子系统的linux,主机pingLinux时,主机的数据包会经过一个网关发送给Linux,Linux的回应数据包也是发送到网关处,故主机的数据包的源地址会被修改为相应的网关地址。

image-20230505203851845

image-20230505203903936

image-20230505204558082

此时我们向防火墙中加入允许双方向windows10(10.3.160.48)发送ICMP协议数据包的规则,再使用windows10尝试是否能够pingLinux系统。image-20230505204517414

image-20230505215705446

发现Windows10主机已经能够成功ping通Linux系统,但是Linux系统仍然无法ping通Windows10主机,通过wireshark抓包发现Linux系统ping主机Windows的ICMP数据包并没有发出,可以推测是被检查点NF_INET_LOCAL_OUT过滤(原因是,目的地址为10.3.160.48并没有加入规则中)。

将相应目的地址允许通过加入规则中,可以发现Linux也成功ping通主机Windows10

image-20230505220054179

image-20230505220132496

查看此时的防火墙系统的规则

image-20230505220216892

接下来,测试检查点NF_INET_LOCAL_IN的过滤效果,我们向规则中添加过滤来自Window10主机的数据包的规则。尝试使用Windows10主机pingLinux系统。

sudo ./simpleFwctl rule add 172.23.144.1 any 172.23.157.70 any ICMP r

image-20230505220512184

image-20230505220641355

image-20230505220626661

通过wireshark抓包发现,windows10主机ping命令的数据包确实已经发送,但是却无法ping通Linux系统,通过查看Linux内核输出缓冲区,可以发现,确实存在四个数据包被过滤了。

image-20230505220724344

但是此时使用Linux系统pingwindows10主机,是能够ping通的,原因是因为Windows10回应的数据包的源IP地址不是172.23.144.1,不会被检查点NF_INET_LOCAL_IN过滤。

image-20230505221010271

image-20230505221424828

在添加来自10.3.160.48的数据包过滤规则,再尝试使用Linux系统pingWindows10主机。

sudo ./simpleFwctl rule add 10.3.160.48 any 172.23.157.70 any ICMP r

image-20230505221916233

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aLb0RMDr-1683462067182)(C:/Users/31576/AppData/Roaming/Typora/typora-user-images/image-20230505221827826.png)]

image-20230505221952177

可见Linux发送ICMP数据确实已经发出,但是来自Windows10操作系统的数据包被Linux的防火墙过滤。

由此可知,我们所设计的防火墙能够实现基本的过滤功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值