测试内核版本:Linux Kernel 2.6.35----Linux Kernel 3.2.1
原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7572382
更多请查看专栏http://blog.csdn.net/column/details/linux-kernel-net.html
作者:闫明
知识基础:本防火墙的开发基于对Linux内核网络栈有个良好的概念,本人对网络栈的分析是基于早期版本(Linux 1.2.13),在明确了网络栈架构的前提下,上升一步分析高级版本内核中的Netfilter防火墙实现原理,然后进行模块或内核编程,开发一款基于包过滤的个人防火墙。
包过滤防火墙:包过滤防火墙是用一个软件查看所流经的数据包的包头(header),由此决定整个包的命运。它可能会决定丢弃(DROP)这个包,可能会接受(ACCEPT)这个包(让这个包通过),也可能执行其它更复杂的动作。工作于网络层,能对IP数据报进行首部检查。例如:IP源地址,目的地址,源端口和目的端口等。
本防火墙的包过滤功能如下:
* 拒绝来自某主机或某网段的所有连接。
* 允许来自某主机或某网段的所有连接。
* 拒绝来自某主机或某网段的指定端口的连接。
* 允许来自某主机或某网段的指定端口的连接。
* 拒绝发去某主机或某网段的所有连接。
* 允许发去某主机或某网段的所有连接。
* 拒绝发去某主机或某网段的指定端口的连接。
* 允许发去某主机或某网段的指定端口的连接。
Netfilter框架是Linux内核分析和过滤特定协议数据包处理框架,为其他模块动态参与网络层数据包处理提供了方便的途径。
该防火墙的总体结构如下:
本防火墙的简单功能就是检查数据包是否符合过滤的条件,如果不符合就舍弃(Drop),否则就接受(Accept),这里定义八个链表头结点
struct ip_node ip_allowed_in_node_head;/*允许的远程主机或网络IP地址头节点*/ struct ip_node ip_denied_in_node_head;/*拒绝的远程主机或网络IP地址头节点*/ struct ip_node ip_allowed_out_node_head;/*允许的本地主机或网络IP地址头节点*/ struct ip_node ip_denied_out_node_head;/*拒绝的本地主机或网络IP地址头节点*/ struct port_node port_allowed_in_node_head;/*允许的远程主机或网络传输层端口号头节点*/ struct port_node port_denied_in_node_head;/*拒绝的远程主机或网络传输层端口号头节点*/ struct port_node port_allowed_out_node_head;/*允许的本地主机或网络传输层端口号头节点*/ struct port_node port_denied_out_node_head;/*拒绝的本地主机或网络传输层端口号头节点*/
用于保存配置文件中的地址或端口信息。
定义两个钩子函数hook_func_in和hook_func_out,分别将其挂载到INET协议族的入口NF_INET_LOCAL_IN和出口NF_INET_LOCAL_OUT:
static struct nf_hook_ops my_netfilter[] = { { .hook =hook_func_in, .owner =THIS_MODULE, .pf =PF_INET, .hooknum =NF_INET_LOCAL_IN, .priority =100 }, { .hook =hook_func_out, .owner =THIS_MODULE, .pf =PF_INET, .hooknum =NF_INET_LOCAL_OUT, .priority =100 } };
说明一下自己定义的一些宏和引用的头文件:
#ifndef MODULE #define MODULE #endif #ifndef __KERNEL__ #define __KERNEL__ #endif //#define NET_DOWN #define MY_FIREWALL_DEBUG #include <asm/system.h> #include <linux/module.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/string.h> #include <linux/net.h> #include <linux/socket.h> #include <linux/sockios.h> #include <linux/in.h> #include <linux/inet.h> #include <net/ip.h> #include <net/protocol.h> #include <linux/skbuff.h> #include <net/sock.h> #include <net/icmp.h> #include <net/raw.h> #include <net/checksum.h> #include <linux/netfilter_ipv4.h> #include <linux/tcp.h> #include <linux/udp.h> #include <linux/igmp.h> #include <linux/fs.h> #include <linux/mm.h> #include <asm/uaccess.h> #define YES 1 #define NO 0 #define IP_MAX_LEN 20 #define PORT_MAX_LEN 20 #define ALLOWED_IP_IN 0 #define DENIED_IP_IN 1 #define ALLOWED_IP_OUT 2 #define DENIED_IP_OUT 3 #define ALLOWED_PORT_IN 0 #define DENIED_PORT_IN 1 #define ALLOWED_PORT_OUT 2 #define DENIED_PORT_OUT 3 #define ALLOWED_IN_IP_CONF_FILE_DIR "/etc/my_firewall/ip_allowed_in" #define DENIED_IN_IP_CONF_FILE_DIR "/etc/my_firewall/ip_denied_in" #define ALLOWED_IN_PORT_CONF_FILE_DIR "/etc/my_firewall/port_allowed_in" #define DENIED_IN_PORT_CONF_FILE_DIR "/etc/my_firewall/port_denied_in" #define ALLOWED_OUT_IP_CONF_FILE_DIR "/etc/my_firewall/ip_allowed_out" #define DENIED_OUT_IP_CONF_FILE_DIR "/etc/my_firewall/ip_denied_out" #define ALLOWED_OUT_PORT_CONF_FILE_DIR "/etc/my_firewall/port_allowed_out" #define DENIED_OUT_PORT_CONF_FILE_DIR "/etc/my_firewall/port_denied_out" //DEFINE FOR WORK_MODE /*不工作状态,默认*/ #define MODE_FREE 0 /*允许来自某主机或某网段的所有连接*/ #define MODE_IP_ONLY_ALLOWED_IN 1 /*拒绝来自某主机或某网段的所有连接*/ #define MODE_IP_ONLY_DENIED_IN 2 /*允许来自某主机或某网段指定端口的连接*/ #define MODE_IP_PORT_ALLOWED_IN 3 /*拒绝来自某主机或某网段的指定端口的连接*/ #define MODE_IP_PORT_DENIED_IN 4 /*允许本地主机或本地网络与其他主机或网络的所有连接*/ #define MODE_IP_ONLY_ALLOWED_OUT 5 /*拒绝本地主机或本地网络与其他主机或网络的所有连接*/ #define MODE_IP_ONLY_DENIED_OUT 6 /*允许本地主机或网络与其他主机或其他网络的指定端口的连接*/ #define MODE_IP_PORT_ALLOWED_OUT 7 /*拒绝本地主机或网络与其他主机或其他网络的指定端口的连接*/ #define MODE_IP_PORT_DENIED_OUT 8
下面是防火墙模块的初始化函数:
int init_firewall() { (&ip_allowed_in_node_head)->next = NULL; (&ip_denied_in_node_head)->next = NULL; (&port_allowed_in_node_head)->next = NULL; (&port_denied_in_node_head)->next = NULL; (&ip_allowed_out_node_head)->next = NULL; (&ip_denied_out_node_head)->next = NULL; (&port_allowed_out_node_head)->next = NULL; (&port_denied_out_node_head)->next = NULL; switch(work_mode) { case MODE_IP_ONLY_ALLOWED_IN: open_ip_cfg_file(ALLOWED_IN_IP_CONF_FILE_DIR,ALLOWED_IP_IN); break; case MODE_IP_ONLY_DENIED_IN: open_ip_cfg_file(DENIED_IN_IP_CONF_FILE_DIR,DENIED_IP_IN); break; case MODE_IP_PORT_ALLOWED_IN: open_port_cfg_file(ALLOWED_IN_PORT_CONF_FILE_DIR,ALLOWED_PORT_IN); open_ip_cfg_file(ALLOWED_IN_IP_CONF_FILE_DIR,ALLOWED_IP_IN); break; case MODE_IP_PORT_DENIED_IN: open_port_cfg_file(DENIED_IN_PORT_CONF_FILE_DIR,DENIED_PORT_IN); open_ip_cfg_file(ALLOWED_IN_IP_CONF_FILE_DIR,ALLOWED_IP_IN); break; case MODE_IP_ONLY_ALLOWED_OUT: open_ip_cfg_file(ALLOWED_OUT_IP_CONF_FILE_DIR,ALLOWED_IP_OUT); break; case MODE_IP_ONLY_DENIED_OUT: open_ip_cfg_file(DENIED_OUT_IP_CONF_FILE_DIR,DENIED_IP_OUT); break; case MODE_IP_PORT_ALLOWED_OUT: open_port_cfg_file(ALLOWED_OUT_PORT_CONF_FILE_DIR,ALLOWED_PORT_OUT); open_ip_cfg_file(ALLOWED_OUT_IP_CONF_FILE_DIR,ALLOWED_IP_OUT); break; case MODE_IP_PORT_DENIED_OUT: open_port_cfg_file(DENIED_OUT_PORT_CONF_FILE_DIR,DENIED_PORT_OUT); open_ip_cfg_file(ALLOWED_OUT_IP_CONF_FILE_DIR,ALLOWED_IP_OUT); break; default:break; } //open_port_cfg_file(DENIED_PORT_CONF_FILE,DENIED_PORT); nf_register_hook(&my_netfilter[0]); nf_register_hook(&my_netfilter[1]); printk("INIT my firewall OK!\n"); return 0; }
下图是Netfilter的IPV4下的结构
上述的两个函数挂载位置NF_INET_LOCAL_IN和NF_INET_LOCAL_OUT,分别处理从本机发出和到达本机的数据包。
下面就是挂载到这两个挂载点的钩子函数,用于数据包的检查
static unsigned int hook_func_in(unsigned int hook, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { #ifdef NET_DOWN return NF_DROP; #else struct iphdr *iph = ip_hdr(skb); __be32 saddr = ntohl(iph->saddr); __be16 sport = 0,dport = 0; if(trans_port(iph,skb,&sport,&dport) == NO && \ work_mode == MODE_IP_PORT_ALLOWED_IN || \ work_mode == MODE_IP_PORT_DENIED_IN) return NF_ACCEPT; #ifdef MY_FIREWALL_DEBUG __be32 daddr = ntohl(iph->daddr); printk("saddr= %u : %u daddr= %u : %d\n",saddr,sport,daddr,dport); #endif switch(work_mode) { case MODE_FREE: return NF_ACCEPT; case MODE_IP_ONLY_ALLOWED_IN: if(ip_in_cfg_file(saddr,&ip_allowed_in_node_head)) return NF_ACCEPT; else return NF_DROP; break; case MODE_IP_ONLY_DENIED_IN: if(ip_in_cfg_file(saddr,&ip_denied_in_node_head)) return NF_DROP; else return NF_ACCEPT; break; case MODE_IP_PORT_ALLOWED_IN: if(ip_in_cfg_file(saddr,&ip_allowed_in_node_head) && \ port_in_cfg_file(sport,&port_allowed_in_node_head)) return NF_ACCEPT; else return NF_DROP; break; case MODE_IP_PORT_DENIED_IN: if(ip_in_cfg_file(saddr,&ip_allowed_in_node_head) && \ !port_in_cfg_file(sport,&port_denied_in_node_head)) return NF_ACCEPT; else return NF_DROP; break; default: return NF_DROP; break; } #endif } static unsigned int hook_func_out(unsigned int hook, struct sk_buff *skb, const struct net_device *in, const struct net_device *out, int (*okfn)(struct sk_buff *)) { #ifdef NET_DOWN return NF_DROP; #else struct iphdr *iph = ip_hdr(skb); __be32 daddr = ntohl(iph->daddr); __be16 sport = 0,dport = 0; if(trans_port(iph,skb,&sport,&dport) == NO && \ work_mode == MODE_IP_PORT_ALLOWED_OUT && \ work_mode == MODE_IP_PORT_DENIED_OUT) return NF_ACCEPT; #ifdef MY_FIREWALL_DEBUG __be32 saddr = ntohl(iph->saddr); printk("saddr= %u : %u daddr= %u : %d\n",saddr,sport,daddr,dport); #endif switch(work_mode) { case MODE_FREE: return NF_ACCEPT; case MODE_IP_ONLY_ALLOWED_OUT: if(ip_in_cfg_file(daddr,&ip_allowed_out_node_head)) return NF_ACCEPT; else return NF_DROP; break; case MODE_IP_ONLY_DENIED_OUT: if(ip_in_cfg_file(daddr,&ip_denied_out_node_head)) return NF_DROP; else return NF_ACCEPT; break; case MODE_IP_PORT_ALLOWED_OUT: if(ip_in_cfg_file(daddr,&ip_allowed_out_node_head) && \ port_in_cfg_file(dport,&port_allowed_out_node_head)) return NF_ACCEPT; else return NF_DROP; break; case MODE_IP_PORT_DENIED_OUT: if(ip_in_cfg_file(daddr,&ip_allowed_out_node_head) && \ !port_in_cfg_file(dport,&port_denied_out_node_head)) return NF_ACCEPT; else return NF_DROP; break; default: return NF_DROP; break; } #endif }
static int open_ip_cfg_file(char * file_dir,int flag) { struct file * filp = NULL; char str[IP_MAX_LEN]; int i = 0; if((filp = filp_open(file_dir,O_RDONLY,0)) < 0) return NO; mm_segment_t fs; fs = get_fs(); set_fs(KERNEL_DS); struct ip_node * work = NULL; while((filp->f_op->read(filp,&str[i],1,&filp->f_pos)) == 1) { if(str[i] == '\n')//next line { str[i] = '\0';//the end of a string i = 0; #ifdef MY_FIREWALL_DEBUG printk("%s\n",str); #endif work = (struct ip_node *)kmalloc(sizeof(ip_allowed_in_node_head),GFP_ATOMIC); if( ip_to_unsigned(str,&(work->ip_start),&(work->ip_end)) == 0 ) return NO; switch(flag) { case ALLOWED_IP_IN: work->next = (&ip_allowed_in_node_head)->next; (&ip_allowed_in_node_head)->next = work;//head insert break; case DENIED_IP_IN: work->next = (&ip_denied_in_node_head)->next; (&ip_denied_in_node_head)->next = work;//head insert break; case ALLOWED_IP_OUT: work->next = (&ip_allowed_out_node_head)->next; (&ip_allowed_out_node_head)->next = work;//head insert break; case DENIED_IP_OUT: work->next = (&ip_denied_out_node_head)->next; (&ip_denied_out_node_head)->next = work;//head insert break; default:break; } filp->f_op->read(filp,&str[0],1,&filp->f_pos);//eat the '\r' } if(i > IP_MAX_LEN) return NO; i++; } return YES; }
192.168.1.1
192.168.1.*
192.168.*.*
192.*.*.*
下面是处理函数
/************************************************ str:The IP Address like 192.168.1.1 start:The pointer to the start IP end:The pointer to the end IP ***********************************************/ static int ip_to_unsigned(const char * str,unsigned int * start,unsigned int * end) { char cache[4][4]; /*split the IP address*/ int i; int k = 0; int j = 0; for(i = 0;str[i] != '\0';i++) { cache[j][k] = str[i]; if(str[i] == '.') { cache[j][k] = '\0'; k = 0; j++; } else k++; if(j > 3) return NO; } cache[3][k] = '\0'; short int a[4]; for(i = 0;i < 4;i++) { if(cache[i][0] != '*') { a[i] = (short)simple_strtol(cache[i],NULL,0); if(a[i] < 0 || a[i] > 255) return NO; } else { break; } } switch(i) { case 4:/*Specific IP Address eg. 192.168.1.1 */ *start = *end = (a[0]<<24) + (a[1]<<16) + (a[2]<<8 )+a[3]; break; case 3:/* eg. 192.168.1.* */ *start = (a[0]<<24) + (a[1]<<16) + (a[2]<<8); *end = *start + (1<<8) - 1; break; case 2:/* eg. 192.168.*.* */ *start = (a[0]<<24) + (a[1]<<16); *end = *start + (1<<16) - 1; break; case 1:/* eg. 192.*.*.* */ *start = (a[0]<<24); *end = *start + (1<<24) - 1; break; default: *start = 0; *end = (1<<32) - 1; break; } return YES; }
void remove_firewall() { free_ip_list(&ip_allowed_in_node_head); free_ip_list(&ip_denied_in_node_head); free_ip_list(&ip_allowed_out_node_head); free_ip_list(&ip_denied_out_node_head); free_port_list(&port_allowed_in_node_head); free_port_list(&port_denied_in_node_head); free_port_list(&port_allowed_out_node_head); free_port_list(&port_denied_out_node_head); nf_unregister_hook(&my_netfilter[0]); nf_unregister_hook(&my_netfilter[1]); printk("CLEAN up my firewall OK!\n"); }
MODULE_LICENSE("Dual BSD/GPL"); MODULE_AUTHOR("[email protected]");该防火墙支持命令参数设置,根据参数设置防火墙的工作模式,只需定义和声明
static int work_mode = 0;//default mode module_param(work_mode,int,S_IRUGO);
可以看到数据包能到达网络层,但由于防火墙启用的相应检查规则,浏览器等应用层软件无法联网,数据包被丢弃。