(CN) Development of Static Packet Filtering Firewall Based on Netfilter

Github仓库链接

基于Netfilter开发静态包过滤防火墙

1 开发要求

基于Netfilter或者Libnetfilter_queue开发一个静态包过滤防火墙,具体要求:

(1) 对符合指定的网络协议(TCP或UDP)、源IP地址、目的IP地址、源端口和目的端口的报文进行阻止。

(2) 再命令行参数指定过滤规则,例如指定需要阻止的网络协议、源IP地址、目的IP地址、源端口和目的端口等。

2 关键数据结构

定义了一个ban_status的结构体,设计相关变量用来记录指定的网络协议、IP地址、端口的封禁情况,以及设计数组用来存储当前处于封禁状态的IP地址和端口号,为后续用户态和核心态的通信、静态包过滤防火墙的开发提供便利。

#define MAX 1000

typedef struct ban_status{
	int tcp_status;
	int udp_status;
	int icmp_status;
	int dest_ip_status;
	int source_ip_status;
	int source_port_status;
	int dest_port_status;

	unsigned int ban_ip;
	unsigned short ban_port;
	unsigned int ban_source_ip_list[MAX + 1];
	unsigned short ban_source_port_list[MAX + 1];
	unsigned int ban_dest_ip_list[MAX + 1];
	unsigned short ban_dest_port_list[MAX + 1];
}ban_status;

3 内核层开发

3.1 自定义回调函数

  1. 结构体声明
static struct nf_hook_ops nf_hook_localin;
static struct nf_hook_ops nf_hook_localout;
static struct nf_hook_ops nf_hook_prerouting;
static struct nf_hook_ops nf_hook_forwarding;
static struct nf_hook_ops nf_hook_postrouting;
  1. LOACL_IN

对于LOCAL_IN部分,自定义了阻止从指定的源端口发来的数据包,阻止指定的本地端口接受对应的数据包,阻止ICMP、TCP、UDP报文的规则,对于符合以上规则的数据包均做DROP处理,其余数据包全部放行。

unsigned int hook_localin(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){
    printk(KERN_INFO "hook_localin");
    if(!skb)
        return NF_ACCEPT;
    struct iphdr *iph = ip_hdr(skb);
    struct tcphdr *tcph = NULL;
    struct udphdr *udph = NULL;
    int i;


    //block source port
    if(rules.source_port_status != 0){
		switch(iph->protocol){
			case IPPROTO_TCP:
				tcph = tcp_hdr(skb);
				for(i = 0;i <= MAX - 1;i++){
					if(tcph->source == ntohs(rules.ban_source_port_list[i])){
						return NF_DROP;
						break;
					}
				}
			case IPPROTO_UDP:
				udph = udp_hdr(skb);
				for(i = 0;i <= MAX - 1;i++){
					if(udph->source == ntohs(rules.ban_source_port_list[i])){
						return NF_DROP;
						break;
					}
				}
		}
	}

    //block dest port
    if(rules.dest_port_status != 0){
		switch(iph->protocol){
			case IPPROTO_TCP:
				tcph = tcp_hdr(skb);
				for(i = 0;i <= MAX - 1;i++){
					if(tcph->dest == ntohs(rules.ban_dest_port_list[i])){
						return NF_DROP;
						break;
					}
				}
			case IPPROTO_UDP:
				udph = udp_hdr(skb);
				for(i = 0;i <= MAX - 1;i++){
					if(udph->dest == ntohs(rules.ban_dest_port_list[i])){
						return NF_DROP;
						break;
					}
				}
		}
	}

    //block icmp
    if(iph->protocol == IPPROTO_ICMP && rules.icmp_status == 1)
		return NF_DROP;

    //block tcp
	if(iph -> protocol == IPPROTO_TCP && rules.tcp_status == 1)
		return NF_DROP;
	
	//block udp
	if(iph -> protocol == IPPROTO_UDP && rules.udp_status == 1)
		return NF_DROP;

    return NF_ACCEPT;
}
  1. LOCAL_OUT

对于LOCAL_OUT部分,自定义了阻止向指定的目的IP地址发送数据包的规则,对于符合以上规则的数据包均做DROP处理,其余数据包全部放行。

unsigned int hook_localout(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){
    printk(KERN_INFO "hook_localout");
    if(!skb)
        return NF_ACCEPT;
    struct iphdr *iph = ip_hdr(skb);
    int i;

    //block dest ip
    if(rules.dest_ip_status != 0){
        for(i = 0;i <= MAX - 1;i++){
            if(rules.ban_dest_ip_list[i] == iph->daddr)
                return NF_DROP;
        }
    }
    return NF_ACCEPT;
}
  1. PRE_ROUTING

对于PRE_ROUTING部分,自定义了阻止从指定的源IP地址发送来的数据包的规则,对于符合以上规则的数据包均做DROP处理,其余数据包全部放行。

unsigned int hook_prerouting(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){
    printk(KERN_INFO "hook_prerouting!");
    if(!skb)
        return NF_ACCEPT;
    struct iphdr *iph = ip_hdr(skb);
    
    //block source ip
    int i;
    if(rules.source_ip_status != 0){
        for(i = 0;i <= MAX - 1;i++){
            if(rules.ban_source_ip_list[i] == iph->saddr)
                return NF_DROP;
        }
    }
    return NF_ACCEPT;
}
  1. POST_ROUTING

对于POST_ROUTING部分,自定义了阻止符合指定协议为UDP,指定目的IP地址和目的端口的报文发出的规则,对于符合以上规则的数据包均做DROP处理,其余数据包全部放行。

unsigned int hook_postrouting(void *priv, struct sk_buff *skb, const struct nf_hook_state *state){
    printk(KERN_INFO "hook_postrouting!");
    if(!skb)
        return NF_ACCEPT;
    struct iphdr *iph = ip_hdr(skb);

    //block UDP out
    struct udphdr *udph = udp_hdr(skb);
    unsigned short port = ntohs(udph->dest);
    int i;
    if(iph->protocol == IPPROTO_UDP){
        for(i = 0;i <= MAX -1 ;i++){
            if(rules.ban_dest_ip_list[i] == iph->daddr && rules.ban_dest_port_list[i] == udph->dest){
                printk(KERN_WARNING "*** Dropping %pI4 (UDP), port %d\n", &(iph->daddr), port);
                return NF_DROP;
            }
        }
    }
    return NF_ACCEPT;
}
  1. FORWARDING

对于FORWARDING部分,并未设置任何规则,默认放行。

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

3.2 Hook 注册与注销

仅在此举一个简单的例子:

	//注册
	nf_hook_localin.hook = hook_localin;
    nf_hook_localin.hooknum = NF_INET_LOCAL_IN;
    nf_hook_localin.pf = PF_INET;
    nf_hook_localin.priority = NF_IP_PRI_FIRST;
    nf_register_net_hook(&init_net, &nf_hook_localin);

	//注销
	nf_unregister_net_hook(&init_net, &nf_hook_localin);

3.3 内核层与用户层通信

img
  1. 内核态函数

自定义sockopt的回调函数,并于钩子函数挂钩、注册、注销。整体逻辑与Netfilter Hook相同。

回调函数:其功能为将内核态与用户态共同维护的ban_status结构体数据进行同步,以达到内核态依据用户输入的相关信息控制禁用规则以及用户态根据已有禁用规则返回给用户当前禁用状态的效果。

int hook_sockopt_set(struct sock *sk, int optval, void __user *user, unsigned int len){
    int res, i;
    printk(KERN_INFO "hook_sockopt_set!");
    res = copy_from_user(&recv, user, sizeof(recv));

    switch(optval){
        case BANICMP:
            rules.icmp_status = recv.icmp_status;
            break;
        case BANIP:
            rules.source_ip_status = recv.source_ip_status;
            rules.dest_ip_status = recv.dest_ip_status;
            rules.ban_ip = recv.ban_ip;
            for(i = 0;i <= MAX - 1;i++){
                rules.ban_source_ip_list[i] = recv.ban_source_ip_list[i];
                rules.ban_dest_ip_list[i] = recv.ban_dest_ip_list[i];
            }
            break;
        case BANPORT:
            rules.source_port_status = recv.source_port_status;
            rules.dest_port_status = recv.dest_port_status;
            rules.ban_port = recv.ban_port;
            for(i = 0;i <= MAX - 1;i++){
                rules.ban_source_port_list[i] = recv.ban_source_port_list[i];
                rules.ban_dest_port_list[i] = recv.ban_dest_port_list[i];
            }
            break;
        case BANTCP:
            rules.tcp_status = recv.tcp_status;
            break;
        case BANUDP:
            rules.udp_status = recv.udp_status;
            break;
        case FLUSH:
            rules.tcp_status = 0;
            rules.udp_status = 0;
            rules.icmp_status = 0;
            rules.dest_ip_status = MAX + 1;
            rules.source_ip_status = MAX + 1;
            rules.dest_port_status = MAX + 1;
            rules.source_port_status = MAX + 1;
            rules.ban_ip = 0;
            rules.ban_port = 0;
            for(i = 0; i <= MAX - 1; i++){
                rules.ban_source_ip_list[i] = 0;
                rules.ban_source_port_list[i] = 0;
                rules.ban_dest_ip_list[i] = 0;
                rules.ban_dest_port_list[i] = 0;
            }
            break;
        default:
            break;
    }
    if(res != 0){
        res = -EINVAL;
        printk(KERN_ERR "copy_from_user error!");
    }
    return res;
}

int hook_sockopt_get(struct sock *sk, int optval, void __user *user, int *len){
    int res;
    printk(KERN_INFO "hook_sockopt_get!");
    res = copy_to_user(user, &rules, sizeof(rules));
    if(res != 0){
        res = -EINVAL;
        printk(KERN_ERR "copy_to_user error!");
    }
    return res;
}
  1. 注册与注销
	//register nf_socket
	nf_hook_sockopt.pf = PF_INET;
	//option signal range
    nf_hook_sockopt.set_optmin = SOC_MIN;
    nf_hook_sockopt.set_optmax = SOC_MAX;
	nf_hook_sockopt.set = hook_sockopt_set;

    nf_hook_sockopt.get_optmin = SOC_MIN;
    nf_hook_sockopt.get_optmax = SOC_MAX;
    nf_hook_sockopt.get = hook_sockopt_get;
    nf_register_sockopt(&nf_hook_sockopt);

	//unregister
    nf_unregister_sockopt(&nf_hook_sockopt);
  1. 用户态函数

发送:int setsockopt(int sockfd, int proto, int cmd, void *data, int datelen);

接收:int getsockopt(int sockfd, int proto, int cmd, void *data, int datalen);

其中,第一个参数是socket描述符;第二个参数proto是sock协议,IP RAW则用SOL_SOCKET/SOL_IP等,TCP/UDP socket可用SOL_SOCKET/SOL_IP/SOL_TCP/SOL_UDP等,即高层的socket是都可以使用低层socket的命令字IPPROTO_IP;第三个参数cmd是操作命令字,由自己定义;第四个参数是数据缓冲区起始位置指针,set操作时是将缓冲区数据写入内核,get的时候是将内核中的数据读入该缓冲区;第五个参数为数据长度。

4 用户层开发

4.1 基于getopt_long()函数开发命令行界面

  1. 结构如下:
struct option {  
     const char *name;  
     int         has_arg;  
     int        *flag;  
     int         val;  
};

static const struct option long_options [] = {
	{ "protocol",   required_argument,      NULL,           'p' },
	{ "jump",       required_argument,      NULL,           'j' },
	{ "source",     required_argument,      NULL,           's' },
	{ "dest",       required_argument,      NULL,           'd' },
	{ "flush",      no_argument,            NULL,           'f' },
	{ "help",       no_argument,            NULL,           'h' },
	{ "list",       no_argument,            NULL,           'l' },
	{ "sourceport", required_argument,      NULL,           'z' },
	{ "destport",   required_argument,      NULL,           'x' },
	{ 0, 0, 0, 0 }
};
  1. 具体处理函数

依据不同的参数,将获取的参数值赋值给定义好的变量或者根据参数来调用相关函数,以达到预期的效果。

void cli(int rec, int sockfd, socklen_t len, int argc, char *argv[]){
	bool ip_check;
	bool port_check;
    switch(rec){
        case 0:
            break;
        case 'p':
            protocol = optarg;
			protocol = lower(protocol);
            break;
        case 'j':
            jump = optarg;
			jump = lower(jump);
            break;
        case 's':
            sourceip = optarg;
			ip_check = ip_vaild_check(sourceip);
			if(ip_check == false){
				printf("IP INVAILD!\n");
				exit(EXIT_FAILURE);
			}
            break;
        case 'd':
            destip = optarg;
			ip_check = ip_vaild_check(destip);
			if(ip_check == false){
				printf("IP INVAILD!\n");
				exit(EXIT_FAILURE);
			}
            break;
        case 'f':
            flush(sockfd, len);
            exit(EXIT_SUCCESS);
        case 'h':
            print_usage(stderr, argc, argv);
            exit(EXIT_SUCCESS);
        case 'l':
            get_status();
            exit(EXIT_SUCCESS);
        case 'z':
            sourceport = optarg;
			port_check = port_vaild_check(sourceport);
			if(port_check == false){
				printf("PORT INVAILD!\n");
				exit(EXIT_FAILURE);
			}
            break;
        case 'x':
            destport = optarg;
			port_check = port_vaild_check(destport);
			if(port_check == false){
				printf("PORT INVAILD!\n");
				exit(EXIT_FAILURE);
			}
            break;
		default:
			break;
	}
}

4.2 防火墙功能函数介绍

  1. 使用方法的提示函数
static void print_usage(FILE *fp, int argc, char *argv[]){
    fprintf(fp,
        "Usage: %s [options]\n"
        "[Options]:\n"
        "-p | --protocol name       Protocol need to filter\n"
        "-j | --jump drop/DROP      Specify the target of the rule\n"
        "-s | --source address      Source address need to filter\n"
        "-d | --dest address        Destination address need to filter\n"
        "-f | --flush               Flush the rules existed\n"
        "-h | --help                Print help message\n"
        "-l | --list                List rules existed\n"
        "-z | --sourceport port     Source port need to filter\n"
        "-x | --destport port       Destination port need to filter\n"
		"[Examples]:\n"
		"%s 	-s 8.8.8.8 -j drop\n"
		"		-d 8.8.8.8 -j drop\n"
		"		-p tcp -j drop\n"
		" 		-z 53 -j drop\n"
		"		-x 53 -j drop\n"
		" ",
        argv[0], argv[0]);
}
  1. 展示当前防火墙状态函数
void get_status(){
		int i;
		printf("[current firewall status]:\n");
		//ICMP
		if(rules.icmp_status == 1)
			printf("PING STATUS: DROP\n");
		else
			printf("PING STATUS: ALLOW\n");
		
		//TCP
		if(rules.tcp_status == 1)
			printf("TCP STATUS: DROP\n");
		else
			printf("TCP STATUS: ALLOW\n");
		
		//UDP
		if(rules.udp_status == 1)
			printf("UDP STATUS: DROP\n");
		else
			printf("UDP STATUS: ALLOW\n");
		
		//source IP
		if(rules.source_ip_status != 0){
			for(i = 0;i <= MAX -1;i++){
				if(rules.ban_source_ip_list[i] == 0)
					continue;
				printf("SOURCE IP DROP:%d.%d.%d.%d\n", 
					(rules.ban_source_ip_list[i] & 0x000000ff) >> 0,
					(rules.ban_source_ip_list[i] & 0x0000ff00) >> 8,
					(rules.ban_source_ip_list[i] & 0x00ff0000) >> 16,
					(rules.ban_source_ip_list[i] & 0xff000000) >> 24);
			}
		}else{
			printf("SOURCE IP ALL ALLOWED\n");
		}

		//dest IP
		if(rules.dest_ip_status != 0){
			for(i = 0;i <= MAX - 1;i++){
				if(rules.ban_dest_ip_list[i] == 0)
					continue;
				printf("DESTINATION IP DROP:%d.%d.%d.%d\n", 
					(rules.ban_dest_ip_list[i] & 0x000000ff) >> 0,
					(rules.ban_dest_ip_list[i] & 0x0000ff00) >> 8,
					(rules.ban_dest_ip_list[i] & 0x00ff0000) >> 16,
					(rules.ban_dest_ip_list[i] & 0xff000000) >> 24);
			}
		}else{
			printf("DESTINATION IP ALL ALLOWED\n");
		}

		//source port
		if(rules.source_port_status != 0){
			for(i = 0;i <= MAX - 1;i++){
				if(rules.ban_source_port_list[i] == 0)
					continue;
				printf("SOURCE PORT DROP: %hu\n", rules.ban_source_port_list[i]);
			}
		}
		else{
			printf("SOURCE PORT ALL ALLOWED\n");
		}

		//dest port
		if(rules.dest_port_status != 0){
			for(i = 0;i <= MAX - 1;i++){
				if(rules.ban_dest_port_list[i] == 0)
					continue;
				printf("DESTINATION PORT DROP: %hu\n", rules.ban_dest_port_list[i]);
			}
		}
		else{
			printf("DESTINATION PORT ALL ALLOWED\n");
		}
}
  1. 清除当前已有的防火墙规则函数
void flush(int sockfd, socklen_t len){
	printf("FLUSH ALL THE RULES EXISTED\n");
	int i = 0;
	rules.tcp_status = 0;
	rules.udp_status = 0;
	rules.icmp_status = 0;
	rules.dest_ip_status = MAX + 1;
    rules.source_ip_status = MAX + 1;
    rules.dest_port_status = MAX + 1;
	rules.source_port_status = MAX + 1;
	rules.ban_ip = 0;
	rules.ban_port = 0;
	for(i = 0;i <= MAX - 1;i++){
		rules.ban_source_ip_list[i] = 0;
        rules.ban_source_port_list[i] = 0;
        rules.ban_dest_ip_list[i] = 0;
        rules.ban_dest_port_list[i] = 0;
	}
	if(setsockopt(sockfd, IPPROTO_IP, FLUSH, &rules, len))
		printErr("setsockopt_Error!\n");
}
  1. 项目中已设计的所有可用的防火墙规则函数
void block_ICMP_in(int sockfd, socklen_t len){
    rules.icmp_status = 1;
	if(setsockopt(sockfd, IPPROTO_IP, BANICMP, &rules, len))
		printErr("setsockopt_Error!\n");
}

void block_TCP_in(int sockfd, socklen_t len){
	rules.tcp_status = 1;
	if(setsockopt(sockfd, IPPROTO_IP, BANTCP, &rules, len))
		printErr("setsockopt_Error_TCP!\n");
}

void block_UDP_in(int sockfd, socklen_t len){
	rules.udp_status = 1;
	if(setsockopt(sockfd, IPPROTO_IP, BANUDP, &rules, len))
		printErr("setsockopt_Error_UDP!\n");
}

void block_IP_in(int sockfd, socklen_t len){
	if(rules.source_ip_status != 0){
		rules.ban_ip = inet_addr(sourceip);
		if(idx != 0)
			rules.ban_source_ip_list[--idx] = rules.ban_ip;
		if(setsockopt(sockfd, IPPROTO_IP, BANIP, &rules, len))
			printErr("setsockopt_IP_IN_ERROR!\n");
		rules.source_ip_status--;
	}else{
		rules.source_ip_status = 0;
		rules.ban_ip = 0;
		if(setsockopt(sockfd, IPPROTO_IP, BANIP, &rules, len))
			printErr("setsockopt_IP_IN_ERROR!\n");
	}
}

void block_IP_out(int sockfd, socklen_t len){
	if(rules.dest_ip_status != 0){
		rules.ban_ip = inet_addr(destip);
		if(idx != 0)
			rules.ban_dest_ip_list[--idx] = rules.ban_ip;
		if(setsockopt(sockfd, IPPROTO_IP, BANIP, &rules, len))
			printErr("setsockopt_IP_OUT_ERROR!\n");
		rules.dest_ip_status--;
	}else{
		rules.dest_ip_status = 0;
		rules.ban_ip = 0;
		if(setsockopt(sockfd, IPPROTO_IP, BANIP, &rules, len))
			printErr("setsockopt_IP_OUT_ERROR!\n");
	}
}

void block_port_in(int sockfd, socklen_t len){
	if(rules.source_port_status != 0){
		rules.ban_port = atoi(sourceport);
		if(idx != 0)
			rules.ban_source_port_list[--idx] = rules.ban_port;
		if(setsockopt(sockfd, IPPROTO_IP, BANPORT, &rules, len))
			printErr("setsockopt_port_in_ERROR!\n");
		rules.source_port_status -- ;
	 }else{ 
		rules.source_port_status = 0;
		rules.ban_port = 0;
		if(setsockopt(sockfd, IPPROTO_IP, BANPORT, &rules, len))
			printErr("setsockopt_port_in_ERROR!\n");
	}
}

void block_port_out(int sockfd, socklen_t len){
	if(rules.dest_port_status != 0){
		rules.ban_port = atoi(destport);
		if(idx != 0)
			rules.ban_dest_port_list[--idx] = rules.ban_port;
		if(setsockopt(sockfd, IPPROTO_IP, BANPORT, &rules, len))
			printErr("setsockopt_port_out_ERROR!\n");
		rules.dest_port_status -- ;
	 }else{ 
		rules.dest_port_status = 0;
		rules.ban_port = 0;
		if(setsockopt(sockfd, IPPROTO_IP, BANPORT, &rules, len))
			printErr("setsockopt_port_out_ERROR!\n");
	}
}
  1. 防火墙规则控制函数
void rules_detail(int sockfd, socklen_t len){
	if(protocol != NULL && jump != NULL){
		if((strcmp(protocol,"icmp") == 0) && (strcmp(jump, "drop") == 0)){
			block_ICMP_in(sockfd, len);
		}else if((strcmp(protocol,"tcp") == 0) && (strcmp(jump, "drop") == 0)){
			block_TCP_in(sockfd, len);
		}else if((strcmp(protocol,"udp") == 0) && (strcmp(jump, "drop") == 0))
			block_UDP_in(sockfd, len);
	}else if(jump != NULL){
		if((strcmp(jump, "drop") == 0) && (sourceip != NULL))
			block_IP_in(sockfd, len);
		else if((destip != NULL) && (strcmp(jump, "drop") == 0))
			block_IP_out(sockfd, len);
		else if((sourceport != NULL) && (strcmp(jump, "drop") == 0))
			block_port_in(sockfd, len);
		else if((destport != NULL) && (strcmp(jump, "drop") == 0))
			block_port_out(sockfd, len);
	}else
		printf("Type -h for help!\n");
}

4.3 相关参数合法性检测

  1. 将用户输入的参数全部转化为小写,防止大小写混用情况下导致的无法正确禁用:
char *lower(char *str){
	char *orign=str;
	for (; *str!='\0'; str++)
    	*str = tolower(*str);
	return orign;
}
  1. 对用户输入的IP地址进行合法性检测:
bool ip_vaild_check(const char *ip){
    int dots = 0; 										/*字符.的个数*/
    int setions = 0; 									/*ip每一部分总和(0-255)*/
    if (NULL == ip || *ip == '.') /*排除输入参数为NULL, 或者一个字符为'.'的字符串*/
        return false;
    while (*ip) {
        if(*ip == '.'){
            dots ++;
            if(setions >= 0 && setions <= 255){ 		/*检查ip是否合法*/
                setions = 0;
                ip++;
                continue;
			}
            return false;
        }
        else if(*ip >= '0' && *ip <= '9'){ 			/*判断是不是数字*/
            setions = setions * 10 + (*ip - '0');	 /*求每一段总和*/
        }else
            return false;
        ip++;
    } 
    if(setions >= 0 && setions <= 255) 				//判断IP最后一段是否合法
        if(dots == 3)
            return true;
    return false;
}
  1. 对用户输入的端口号进行合法性检测:
bool port_vaild_check(const char* port){
	int p = atoi(port);
	if(p >= 0 && p <= 65535)
		return true;
	return false;
}

5 程序运行效果展示

5.1 使用方法展示

img img img img

5.2 功能测试

img img img img img
Outis Yang
Outis Yang
2024 Undergraduate in Cyberspace Security

My research interests include Internet of Vehicles(IoV), Penetration Testing and Security research.