防火墙设计与开发
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 过滤功能实现
- 检查点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;
}
- 检查点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;
}
- 检查点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;
}
- 检查点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
- simpleFw编译安装防火墙过滤模块
make
sudo insmod simpleFw.ko
lsmod
查看防火墙过滤规则Rule和debug_level,并设置debug_level为1
在为添加任何规则的情况下,我们尝试使用Linux系统ping主机Windows系统,发现无法ping通。相反地,我们使用windows系统pingLinux系统,发现同样也无法ping通。
在此我们可以进行初步的判定是由于检查点NF_INET_LOCAL_OUT的白名单过滤导致,Linux系统中无法发出任何数据包导致系统双方无法ping通。通过wireshark抓包,可以发现windows系统确实将数据包发送出去了。
注:由于使用的是wsl2子系统的linux,主机pingLinux时,主机的数据包会经过一个网关发送给Linux,Linux的回应数据包也是发送到网关处,故主机的数据包的源地址会被修改为相应的网关地址。
此时我们向防火墙中加入允许双方向windows10(10.3.160.48)发送ICMP协议数据包的规则,再使用windows10尝试是否能够pingLinux系统。
发现Windows10主机已经能够成功ping通Linux系统,但是Linux系统仍然无法ping通Windows10主机,通过wireshark抓包发现Linux系统ping主机Windows的ICMP数据包并没有发出,可以推测是被检查点NF_INET_LOCAL_OUT过滤(原因是,目的地址为10.3.160.48并没有加入规则中)。
将相应目的地址允许通过加入规则中,可以发现Linux也成功ping通主机Windows10
查看此时的防火墙系统的规则
接下来,测试检查点NF_INET_LOCAL_IN的过滤效果,我们向规则中添加过滤来自Window10主机的数据包的规则。尝试使用Windows10主机pingLinux系统。
sudo ./simpleFwctl rule add 172.23.144.1 any 172.23.157.70 any ICMP r
通过wireshark抓包发现,windows10主机ping命令的数据包确实已经发送,但是却无法ping通Linux系统,通过查看Linux内核输出缓冲区,可以发现,确实存在四个数据包被过滤了。
但是此时使用Linux系统pingwindows10主机,是能够ping通的,原因是因为Windows10回应的数据包的源IP地址不是172.23.144.1,不会被检查点NF_INET_LOCAL_IN过滤。
在添加来自10.3.160.48的数据包过滤规则,再尝试使用Linux系统pingWindows10主机。
sudo ./simpleFwctl rule add 10.3.160.48 any 172.23.157.70 any ICMP r
可见Linux发送ICMP数据确实已经发出,但是来自Windows10操作系统的数据包被Linux的防火墙过滤。
由此可知,我们所设计的防火墙能够实现基本的过滤功能。