测试内核版本: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
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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);
可以看到数据包能到达网络层,但由于防火墙启用的相应检查规则,浏览器等应用层软件无法联网,数据包被丢弃。