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内核分析和过滤特定协议数据包处理框架,为其他模块动态参与网络层数据包处理提供了方便的途径。

该防火墙的总体结构如下:

linux 内核netfilter实现_第1张图片

本防火墙的简单功能就是检查数据包是否符合过滤的条件,如果不符合就舍弃(Drop),否则就接受(Accept),这里定义八个链表头结点

[cpp] view plain copy print ?
  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:

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



 

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

[cpp] view plain copy print ?
  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 



 

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

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


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

下图是Netfilter的IPV4下的结构

linux 内核netfilter实现_第2张图片

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

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

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


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

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


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

192.168.1.1

192.168.1.*

192.168.*.*

192.*.*.*

下面是处理函数

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


模块的移除函数

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


 

[cpp] view plain copy print ?
  1. MODULE_LICENSE("Dual BSD/GPL"); 
  2. MODULE_AUTHOR("[email protected]"); 

该防火墙支持命令参数设置,根据参数设置防火墙的工作模式,只需定义和声明

[cpp] view plain copy print ?
  1. static int work_mode = 0;//default mode 
  2. module_param(work_mode,int,S_IRUGO); 


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

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

linux 内核netfilter实现_第3张图片

 

你可能感兴趣的:(linux 内核netfilter实现)