Linux内核--基于Netfilter的内核级包过滤防火墙实现
测试内核版本: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;
- }
先从文件读取配置文件,加载到内核,然后注册钩子操作结构my_netfilter[0],my_netfilter[1]
下图是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
- }
下面是打开文件并读取信息的函数,这里以打开IP地址的配置文件为例
- 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;
- }
这里配置文件中不仅支持具体的IP地址,还支持IP地址网段,例如
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("yming0221@gmail.com");
该防火墙支持命令参数设置,根据参数设置防火墙的工作模式,只需定义和声明
- static int work_mode = 0;//default mode
- module_param(work_mode,int,S_IRUGO);
目前测试,防火墙正常工作。
可以看到数据包能到达网络层,但由于防火墙启用的相应检查规则,浏览器等应用层软件无法联网,数据包被丢弃。
Linux内核--基于Netfilter的内核级包过滤防火墙实现的更多相关文章
- 戴文的Linux内核专题:07内核配置(3)
转自Linux中国 OK,我们还继续配置内核.还有更多功能等待着去配置. 下一个问题(Enable ELF core dumps (ELF_CORE))询问的是内核是否可以生成内核转储文件.这会使内核 ...
- Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7)
http://blog.chinaunix.net/uid-20543672-id-3157283.html Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3 ...
- Linux与其它类Unix内核的比较
单块结构的内核:由几个逻辑上独立的成分构成,单块结构,大多数据商用Unix变体也是单块结构: 编译并静态连接的传统Unix内核:Linux能自动按需动态地装载和卸载部分内核代码(模块),而传统Unix ...
- linux内存(三)内核与用户空间交互
来自网址http://www.kerneltravel.net/jiaoliu/005.htm 用户程序和内核的信息交换是双向的,也就是说既可以主动从用户空间向内核空间发送信息,也可以从内核空间向用户 ...
- linux下TCP/IP及内核参数优化调优(转)
Linux下TCP/IP及内核参数优化有多种方式,参数配置得当可以大大提高系统的性能,也可以根据特定场景进行专门的优化,如TIME_WAIT过高,DDOS攻击等等. 如下配置是写在sysctl.con ...
- Linux内核@系统组成与内核配置编译
Linux系统由什么组成? 由用户空间(应用程序+GNU C标准库)和内核空间(系统调用接口+内核+内核架构代码)组成. Linux内核到底是什么?以及组成. ARM的七种操作级别? 内核网络协议栈( ...
- linux进程用户内存空间和内核空间
When a process running in user mode requests additional memory, pages are allocated from the list of ...
- 如何在Linux上通过grub添加内核参数
转自Linux中国 我们可以在linux内核启动时为其提供各种各样的参数.这些参数可以自定义内核默认的行为,或者通知内核关于硬件的配置信息.内核参数应在内核启动时通过引导装载程序,如GRUB或LILO ...
- 戴文的Linux内核专题:08内核配置(5)
转自Linux中国 Linux内核拥有许多可以配置的特性,接下来我们还有许多要配置. 下一个可以配置的特性是x86的随机数生成器(x86 architectural random number gen ...
随机推荐
- DEV Express中ImageCollection的使用
1, ImageCollection作为窗体组件的一种,位于Components分类下,拖进窗体以后,显示在界面的底部. 2, 注意ImageCollection的Imag ...
- PL/SQL编程接触
1.认识PL/SQL 结构化查询语言(Structured Query Language,SQL)是用来访问和操作关系型数据库的一种标准通用语言,它属于第四代语言(4GL),简单易学,使用它可以很方便 ...
- 大数据学习——hdfs客户端操作
package cn.itcast.hdfs; import org.apache.commons.io.IOUtils; import org.apache.hadoop.conf.Configur ...
- php引入PHPMailer发送邮件
昨天做了一个发送邮件的功能,如果直接用mail()函数,需要拥有自己的邮件服务器,所有引入PHPMailer类方便快捷,简单写一下开发步骤: 一.拥有自己的邮箱账号(作为发件人邮箱) 分两种情况: 1 ...
- [luoguP1227] [JSOI2008]完美的对称(sort)
传送门 排序! #include <cstdio> #include <iostream> #include <algorithm> #define N 20001 ...
- bzoj4568 [Scoi2016]幸运数字 线性基+树链剖分
A 国共有 n 座城市,这些城市由 n-1 条道路相连,使得任意两座城市可以互达,且路径唯一.每座城市都有一个 幸运数字,以纪念碑的形式矗立在这座城市的正中心,作为城市的象征.一些旅行者希望游览 A ...
- SpringData JPA进阶查询—JPQL/原生SQL查询、分页处理、部分字段映射查询
上一篇介绍了入门基础篇SpringDataJPA访问数据库.本篇介绍SpringDataJPA进一步的定制化查询,使用JPQL或者SQL进行查询.部分字段映射.分页等.本文尽量以简单的建模与代码进行展 ...
- 【收藏】下载Chrome商店插件的方法,万恶的gwd
以下是下载离线插件包的方法: 第一步: 每个Google Chrome扩展都有一个固定的ID,例如https://chrome.google.com/webstore/detail/bfbmjmiod ...
- 深入理解计算机操作系统——第10章:UNIX IO,打开,关闭,读写文件
系统级IO:输入输出是主存与外部设备(磁盘,终端,网络)之间拷贝数据的过程 输入:从IO设备拷贝数据到主存中 输出:从主存中拷贝数据到IO设备中 10.1 unix IO 所有的IO设备都被模型化为文 ...
- msp430项目编程17
msp430中项目---红外遥控系统 1.定时器工作原理 2.电路原理说明 3.代码(显示部分) 4.代码(功能实现) 5.项目总结 msp430项目编程 msp430入门学习