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),这里定义八个链表头结点

  1. struct ip_node  ip_allowed_in_node_head;/*允许的远程主机或网络IP地址头节点*/  
  2. struct ip_node  ip_denied_in_node_head;/*拒绝的远程主机或网络IP地址头节点*/  
  3. struct ip_node  ip_allowed_out_node_head;/*允许的本地主机或网络IP地址头节点*/  
  4. struct ip_node  ip_denied_out_node_head;/*拒绝的本地主机或网络IP地址头节点*/  
  5.   
  6. struct port_node port_allowed_in_node_head;/*允许的远程主机或网络传输层端口号头节点*/  
  7. struct port_node port_denied_in_node_head;/*拒绝的远程主机或网络传输层端口号头节点*/  
  8. struct port_node port_allowed_out_node_head;/*允许的本地主机或网络传输层端口号头节点*/  
  9. struct port_node port_denied_out_node_head;/*拒绝的本地主机或网络传输层端口号头节点*/  

用于保存配置文件中的地址或端口信息。

定义两个钩子函数hook_func_in和hook_func_out,分别将其挂载到INET协议族的入口NF_INET_LOCAL_IN和出口NF_INET_LOCAL_OUT:

  1. static struct nf_hook_ops my_netfilter[] =  
  2. {  
  3.     {  
  4.         .hook       =hook_func_in,  
  5.         .owner      =THIS_MODULE,  
  6.         .pf     =PF_INET,  
  7.         .hooknum    =NF_INET_LOCAL_IN,  
  8.         .priority   =100  
  9.     },  
  10.     {  
  11.         .hook       =hook_func_out,  
  12.         .owner      =THIS_MODULE,  
  13.         .pf     =PF_INET,  
  14.         .hooknum    =NF_INET_LOCAL_OUT,  
  15.         .priority   =100  
  16.     }  
  17. };  


说明一下自己定义的一些宏和引用的头文件:

  1. #ifndef MODULE  
  2. #define MODULE  
  3. #endif  
  4.   
  5. #ifndef __KERNEL__  
  6. #define __KERNEL__  
  7. #endif  
  8. //#define NET_DOWN  
  9. #define MY_FIREWALL_DEBUG  
  10.   
  11. #include <asm/system.h>  
  12. #include <linux/module.h>  
  13. #include <linux/types.h>  
  14. #include <linux/kernel.h>  
  15. #include <linux/string.h>  
  16.   
  17. #include <linux/net.h>  
  18. #include <linux/socket.h>  
  19. #include <linux/sockios.h>  
  20. #include <linux/in.h>  
  21. #include <linux/inet.h>  
  22.   
  23.   
  24. #include <net/ip.h>  
  25. #include <net/protocol.h>  
  26. #include <linux/skbuff.h>  
  27. #include <net/sock.h>  
  28. #include <net/icmp.h>  
  29. #include <net/raw.h>  
  30. #include <net/checksum.h>  
  31. #include <linux/netfilter_ipv4.h>  
  32. #include <linux/tcp.h>  
  33. #include <linux/udp.h>  
  34. #include <linux/igmp.h>  
  35.   
  36. #include <linux/fs.h>  
  37. #include <linux/mm.h>  
  38. #include <asm/uaccess.h>  
  39.   
  40. #define YES 1  
  41. #define NO 0  
  42.   
  43. #define IP_MAX_LEN 20  
  44. #define PORT_MAX_LEN 20  
  45.   
  46. #define ALLOWED_IP_IN 0  
  47. #define DENIED_IP_IN 1  
  48. #define ALLOWED_IP_OUT 2  
  49. #define DENIED_IP_OUT 3  
  50.   
  51. #define ALLOWED_PORT_IN 0  
  52. #define DENIED_PORT_IN 1  
  53. #define ALLOWED_PORT_OUT 2  
  54. #define DENIED_PORT_OUT 3  
  55.   
  56. #define ALLOWED_IN_IP_CONF_FILE_DIR "/etc/my_firewall/ip_allowed_in"  
  57. #define DENIED_IN_IP_CONF_FILE_DIR "/etc/my_firewall/ip_denied_in"  
  58. #define ALLOWED_IN_PORT_CONF_FILE_DIR "/etc/my_firewall/port_allowed_in"  
  59. #define DENIED_IN_PORT_CONF_FILE_DIR "/etc/my_firewall/port_denied_in"  
  60.   
  61. #define ALLOWED_OUT_IP_CONF_FILE_DIR "/etc/my_firewall/ip_allowed_out"  
  62. #define DENIED_OUT_IP_CONF_FILE_DIR "/etc/my_firewall/ip_denied_out"  
  63. #define ALLOWED_OUT_PORT_CONF_FILE_DIR "/etc/my_firewall/port_allowed_out"  
  64. #define DENIED_OUT_PORT_CONF_FILE_DIR "/etc/my_firewall/port_denied_out"  
  65.   
  66. //DEFINE FOR WORK_MODE  
  67.   
  68. /*不工作状态,默认*/  
  69. #define MODE_FREE 0  
  70. /*允许来自某主机或某网段的所有连接*/  
  71. #define MODE_IP_ONLY_ALLOWED_IN 1  
  72.   
  73. /*拒绝来自某主机或某网段的所有连接*/  
  74. #define MODE_IP_ONLY_DENIED_IN 2  
  75.   
  76. /*允许来自某主机或某网段指定端口的连接*/  
  77. #define MODE_IP_PORT_ALLOWED_IN 3  
  78.   
  79. /*拒绝来自某主机或某网段的指定端口的连接*/  
  80. #define MODE_IP_PORT_DENIED_IN 4  
  81.   
  82. /*允许本地主机或本地网络与其他主机或网络的所有连接*/  
  83. #define MODE_IP_ONLY_ALLOWED_OUT 5  
  84.   
  85. /*拒绝本地主机或本地网络与其他主机或网络的所有连接*/  
  86. #define MODE_IP_ONLY_DENIED_OUT 6  
  87.   
  88. /*允许本地主机或网络与其他主机或其他网络的指定端口的连接*/  
  89. #define MODE_IP_PORT_ALLOWED_OUT 7  
  90.   
  91. /*拒绝本地主机或网络与其他主机或其他网络的指定端口的连接*/  
  92. #define MODE_IP_PORT_DENIED_OUT 8  


下面是防火墙模块的初始化函数:

  1. int init_firewall()  
  2. {  
  3.     (&ip_allowed_in_node_head)->next = NULL;  
  4.     (&ip_denied_in_node_head)->next = NULL;  
  5.     (&port_allowed_in_node_head)->next = NULL;  
  6.     (&port_denied_in_node_head)->next = NULL;  
  7.   
  8.     (&ip_allowed_out_node_head)->next = NULL;  
  9.     (&ip_denied_out_node_head)->next = NULL;  
  10.     (&port_allowed_out_node_head)->next = NULL;  
  11.     (&port_denied_out_node_head)->next = NULL;  
  12.   
  13.     switch(work_mode)  
  14.     {  
  15.         case MODE_IP_ONLY_ALLOWED_IN:  
  16.             open_ip_cfg_file(ALLOWED_IN_IP_CONF_FILE_DIR,ALLOWED_IP_IN);  
  17.             break;  
  18.         case MODE_IP_ONLY_DENIED_IN:  
  19.             open_ip_cfg_file(DENIED_IN_IP_CONF_FILE_DIR,DENIED_IP_IN);  
  20.             break;  
  21.         case MODE_IP_PORT_ALLOWED_IN:  
  22.             open_port_cfg_file(ALLOWED_IN_PORT_CONF_FILE_DIR,ALLOWED_PORT_IN);  
  23.             open_ip_cfg_file(ALLOWED_IN_IP_CONF_FILE_DIR,ALLOWED_IP_IN);  
  24.             break;  
  25.         case MODE_IP_PORT_DENIED_IN:  
  26.             open_port_cfg_file(DENIED_IN_PORT_CONF_FILE_DIR,DENIED_PORT_IN);  
  27.             open_ip_cfg_file(ALLOWED_IN_IP_CONF_FILE_DIR,ALLOWED_IP_IN);  
  28.             break;  
  29.         case MODE_IP_ONLY_ALLOWED_OUT:  
  30.             open_ip_cfg_file(ALLOWED_OUT_IP_CONF_FILE_DIR,ALLOWED_IP_OUT);  
  31.             break;  
  32.         case MODE_IP_ONLY_DENIED_OUT:  
  33.             open_ip_cfg_file(DENIED_OUT_IP_CONF_FILE_DIR,DENIED_IP_OUT);  
  34.             break;  
  35.         case MODE_IP_PORT_ALLOWED_OUT:  
  36.             open_port_cfg_file(ALLOWED_OUT_PORT_CONF_FILE_DIR,ALLOWED_PORT_OUT);  
  37.             open_ip_cfg_file(ALLOWED_OUT_IP_CONF_FILE_DIR,ALLOWED_IP_OUT);  
  38.             break;  
  39.         case MODE_IP_PORT_DENIED_OUT:  
  40.             open_port_cfg_file(DENIED_OUT_PORT_CONF_FILE_DIR,DENIED_PORT_OUT);  
  41.             open_ip_cfg_file(ALLOWED_OUT_IP_CONF_FILE_DIR,ALLOWED_IP_OUT);  
  42.             break;  
  43.         default:break;  
  44.     }  
  45.       
  46.       
  47.       
  48.     //open_port_cfg_file(DENIED_PORT_CONF_FILE,DENIED_PORT);  
  49.     nf_register_hook(&my_netfilter[0]);  
  50.     nf_register_hook(&my_netfilter[1]);  
  51.     printk("INIT my firewall OK!\n");  
  52.     return 0;  
  53. }  

先从文件读取配置文件,加载到内核,然后注册钩子操作结构my_netfilter[0],my_netfilter[1]

下图是Netfilter的IPV4下的结构

上述的两个函数挂载位置NF_INET_LOCAL_IN和NF_INET_LOCAL_OUT,分别处理从本机发出和到达本机的数据包。

下面就是挂载到这两个挂载点的钩子函数,用于数据包的检查

  1. static unsigned int hook_func_in(unsigned int hook,  
  2.                 struct sk_buff *skb,  
  3.                 const struct net_device *in,  
  4.                 const struct net_device *out,  
  5.                 int (*okfn)(struct sk_buff *))  
  6. {  
  7. #ifdef NET_DOWN  
  8.     return NF_DROP;  
  9. #else  
  10.     struct iphdr *iph = ip_hdr(skb);  
  11.     __be32 saddr = ntohl(iph->saddr);  
  12.       
  13.       
  14.     __be16 sport = 0,dport = 0;  
  15.     if(trans_port(iph,skb,&sport,&dport) == NO && \  
  16.         work_mode == MODE_IP_PORT_ALLOWED_IN || \  
  17.             work_mode == MODE_IP_PORT_DENIED_IN)  
  18.         return NF_ACCEPT;  
  19.   
  20. #ifdef MY_FIREWALL_DEBUG  
  21.     __be32 daddr = ntohl(iph->daddr);  
  22.     printk("saddr= %u : %u  daddr= %u : %d\n",saddr,sport,daddr,dport);  
  23. #endif  
  24.   
  25.     switch(work_mode)  
  26.     {  
  27.         case MODE_FREE:  
  28.             return NF_ACCEPT;  
  29.         case MODE_IP_ONLY_ALLOWED_IN:  
  30.             if(ip_in_cfg_file(saddr,&ip_allowed_in_node_head))  
  31.                 return NF_ACCEPT;  
  32.             else return NF_DROP;  
  33.             break;  
  34.         case MODE_IP_ONLY_DENIED_IN:  
  35.             if(ip_in_cfg_file(saddr,&ip_denied_in_node_head))  
  36.                 return NF_DROP;  
  37.             else return NF_ACCEPT;  
  38.             break;  
  39.         case MODE_IP_PORT_ALLOWED_IN:  
  40.             if(ip_in_cfg_file(saddr,&ip_allowed_in_node_head) && \  
  41.                 port_in_cfg_file(sport,&port_allowed_in_node_head))  
  42.                 return NF_ACCEPT;  
  43.             else return NF_DROP;  
  44.             break;  
  45.         case MODE_IP_PORT_DENIED_IN:  
  46.             if(ip_in_cfg_file(saddr,&ip_allowed_in_node_head) && \  
  47.                 !port_in_cfg_file(sport,&port_denied_in_node_head))  
  48.                 return NF_ACCEPT;  
  49.             else return NF_DROP;  
  50.             break;  
  51.         default:  
  52.             return NF_DROP;  
  53.             break;  
  54.     }  
  55. #endif  
  56.   
  57. }  
  58.   
  59. static unsigned int hook_func_out(unsigned int hook,  
  60.                 struct sk_buff *skb,  
  61.                 const struct net_device *in,  
  62.                 const struct net_device *out,  
  63.                 int (*okfn)(struct sk_buff *))  
  64. {  
  65. #ifdef NET_DOWN  
  66.     return NF_DROP;  
  67. #else  
  68.     struct iphdr *iph = ip_hdr(skb);  
  69.       
  70.     __be32 daddr = ntohl(iph->daddr);  
  71.       
  72.     __be16 sport = 0,dport = 0;  
  73.   
  74.     if(trans_port(iph,skb,&sport,&dport) == NO && \  
  75.         work_mode == MODE_IP_PORT_ALLOWED_OUT && \  
  76.             work_mode == MODE_IP_PORT_DENIED_OUT)  
  77.         return NF_ACCEPT;  
  78.   
  79. #ifdef MY_FIREWALL_DEBUG  
  80.     __be32 saddr = ntohl(iph->saddr);  
  81.     printk("saddr= %u : %u  daddr= %u : %d\n",saddr,sport,daddr,dport);  
  82. #endif  
  83.   
  84.     switch(work_mode)  
  85.     {  
  86.         case MODE_FREE:  
  87.             return NF_ACCEPT;  
  88.         case MODE_IP_ONLY_ALLOWED_OUT:  
  89.             if(ip_in_cfg_file(daddr,&ip_allowed_out_node_head))  
  90.                 return NF_ACCEPT;  
  91.             else return NF_DROP;  
  92.             break;  
  93.         case MODE_IP_ONLY_DENIED_OUT:  
  94.             if(ip_in_cfg_file(daddr,&ip_denied_out_node_head))  
  95.                 return NF_DROP;  
  96.             else return NF_ACCEPT;  
  97.             break;  
  98.         case MODE_IP_PORT_ALLOWED_OUT:  
  99.             if(ip_in_cfg_file(daddr,&ip_allowed_out_node_head) && \  
  100.                 port_in_cfg_file(dport,&port_allowed_out_node_head))  
  101.                 return NF_ACCEPT;  
  102.             else return NF_DROP;  
  103.             break;  
  104.         case MODE_IP_PORT_DENIED_OUT:  
  105.             if(ip_in_cfg_file(daddr,&ip_allowed_out_node_head) && \  
  106.                 !port_in_cfg_file(dport,&port_denied_out_node_head))  
  107.                 return NF_ACCEPT;  
  108.             else return NF_DROP;  
  109.             break;  
  110.         default:  
  111.             return NF_DROP;  
  112.             break;  
  113.     }  
  114. #endif  
  115.   
  116. }  

下面是打开文件并读取信息的函数,这里以打开IP地址的配置文件为例

  1. static int open_ip_cfg_file(char * file_dir,int flag)  
  2. {  
  3.     struct file * filp = NULL;  
  4.       
  5.     char str[IP_MAX_LEN];  
  6.     int i = 0;  
  7.   
  8.     if((filp = filp_open(file_dir,O_RDONLY,0)) < 0)   
  9.         return NO;  
  10.   
  11.     mm_segment_t fs;  
  12.     fs = get_fs();  
  13.     set_fs(KERNEL_DS);  
  14.   
  15.   
  16.     struct ip_node * work = NULL;  
  17.   
  18.     while((filp->f_op->read(filp,&str[i],1,&filp->f_pos)) == 1)  
  19.     {  
  20.         if(str[i] == '\n')//next line  
  21.         {  
  22.             str[i] = '\0';//the end of a string  
  23.             i = 0;  
  24. #ifdef MY_FIREWALL_DEBUG  
  25.             printk("%s\n",str);  
  26. #endif  
  27.             work = (struct ip_node *)kmalloc(sizeof(ip_allowed_in_node_head),GFP_ATOMIC);  
  28.           
  29.               
  30.             if( ip_to_unsigned(str,&(work->ip_start),&(work->ip_end)) == 0 )  
  31.                 return NO;  
  32.   
  33.             switch(flag)  
  34.             {  
  35.                 case ALLOWED_IP_IN:  
  36.                     work->next = (&ip_allowed_in_node_head)->next;  
  37.                     (&ip_allowed_in_node_head)->next = work;//head insert  
  38.                     break;  
  39.                 case DENIED_IP_IN:  
  40.                     work->next = (&ip_denied_in_node_head)->next;  
  41.                     (&ip_denied_in_node_head)->next = work;//head insert  
  42.                     break;  
  43.                 case ALLOWED_IP_OUT:  
  44.                     work->next = (&ip_allowed_out_node_head)->next;  
  45.                     (&ip_allowed_out_node_head)->next = work;//head insert  
  46.                     break;  
  47.                 case DENIED_IP_OUT:  
  48.                     work->next = (&ip_denied_out_node_head)->next;  
  49.                     (&ip_denied_out_node_head)->next = work;//head insert  
  50.                     break;  
  51.                 default:break;  
  52.             }  
  53.                   
  54.             filp->f_op->read(filp,&str[0],1,&filp->f_pos);//eat the '\r'  
  55.         }  
  56.           
  57.   
  58.         if(i > IP_MAX_LEN) return NO;  
  59.         i++;  
  60.           
  61.     }  
  62.     return YES;  
  63. }  

这里配置文件中不仅支持具体的IP地址,还支持IP地址网段,例如

192.168.1.1

192.168.1.*

192.168.*.*

192.*.*.*

下面是处理函数

  1. /************************************************ 
  2. str:The IP Address like 192.168.1.1 
  3. start:The pointer to the start IP 
  4. end:The pointer to the end IP 
  5. ***********************************************/  
  6. static int ip_to_unsigned(const char * str,unsigned int * start,unsigned int * end)  
  7. {  
  8.     char cache[4][4];  
  9.     /*split the IP address*/  
  10.     int i;  
  11.     int k = 0;  
  12.     int j = 0;  
  13.     for(i = 0;str[i] != '\0';i++)  
  14.     {  
  15.         cache[j][k] = str[i];  
  16.         if(str[i] == '.')  
  17.         {  
  18.             cache[j][k] = '\0';  
  19.             k = 0;  
  20.             j++;  
  21.               
  22.         }  
  23.         else k++;  
  24.         if(j > 3) return NO;  
  25.     }  
  26.     cache[3][k] = '\0';  
  27.   
  28.     short int a[4];  
  29.     for(i = 0;i < 4;i++)  
  30.     {  
  31.         if(cache[i][0] != '*')  
  32.         {  
  33.             a[i] = (short)simple_strtol(cache[i],NULL,0);  
  34.             if(a[i] < 0 || a[i] > 255) return NO;  
  35.         }  
  36.         else  
  37.         {  
  38.             break;  
  39.         }  
  40.     }  
  41.   
  42.     switch(i)  
  43.     {  
  44.         case 4:/*Specific IP Address eg.  192.168.1.1   */  
  45.             *start = *end = (a[0]<<24) + (a[1]<<16) + (a[2]<<8 )+a[3];  
  46.             break;  
  47.         case 3:/*  eg. 192.168.1.*   */  
  48.             *start = (a[0]<<24) + (a[1]<<16) + (a[2]<<8);  
  49.             *end = *start + (1<<8) - 1;  
  50.             break;  
  51.         case 2:/*  eg. 192.168.*.*   */  
  52.             *start = (a[0]<<24) + (a[1]<<16);  
  53.             *end = *start + (1<<16) - 1;  
  54.             break;  
  55.         case 1:/*  eg. 192.*.*.*    */  
  56.             *start = (a[0]<<24);  
  57.             *end = *start + (1<<24) - 1;  
  58.             break;  
  59.         default:  
  60.             *start = 0;  
  61.             *end = (1<<32) - 1;  
  62.             break;  
  63.     }  
  64.   
  65.     return  YES;  
  66. }  

模块的移除函数

  1. void remove_firewall()  
  2. {  
  3.     free_ip_list(&ip_allowed_in_node_head);  
  4.     free_ip_list(&ip_denied_in_node_head);  
  5.   
  6.     free_ip_list(&ip_allowed_out_node_head);  
  7.     free_ip_list(&ip_denied_out_node_head);  
  8.   
  9.     free_port_list(&port_allowed_in_node_head);  
  10.     free_port_list(&port_denied_in_node_head);  
  11.   
  12.     free_port_list(&port_allowed_out_node_head);  
  13.     free_port_list(&port_denied_out_node_head);  
  14.   
  15.     nf_unregister_hook(&my_netfilter[0]);  
  16.     nf_unregister_hook(&my_netfilter[1]);  
  17.     printk("CLEAN up my firewall OK!\n");  
  18. }  

  1. MODULE_LICENSE("Dual BSD/GPL");  
  2. MODULE_AUTHOR("yming0221@gmail.com");  
该防火墙支持命令参数设置,根据参数设置防火墙的工作模式,只需定义和声明

  1. static int work_mode = 0;//default mode  
  2. module_param(work_mode,int,S_IRUGO);  

目前测试,防火墙正常工作。

可以看到数据包能到达网络层,但由于防火墙启用的相应检查规则,浏览器等应用层软件无法联网,数据包被丢弃。



原文地址:https://www.cnblogs.com/wangfengju/p/6173195.html