Netfilter

内核版本:2.6.34

NetFilter在2.4.x内核中引入,成为linux平台下进行网络应用的主要扩展,不仅包括防火墙的实现,还包括报文的处理(如报文加密、报文分类统计等)等。

NetFilter数据结构        勾子struct nf_hook_ops[net\filter\core.c]
  1. struct nf_hook_ops {  
  2.     struct list_head list;  
  3.     /* User fills in from here down. */  
  4.     nf_hookfn *hook;  
  5.     struct module *owner;  
  6.     u_int8_t pf;  
  7.     unsigned int hooknum;  
  8.     /* Hooks are ordered in ascending priority. */  
  9.     int priority;  
  10. };  
struct nf_hook_ops { struct list_head list; /* User fills in from here down. */
		     nf_hookfn *hook; 
		    struct module *owner; 
		      u_int8_t pf;
		      unsigned int hooknum; /* Hooks are ordered in ascending priority. */ 
		     int priority; };  

      成员list用于链入全局勾子数组nf_hooks中,它一定在第一位,保证&nf_hook_ops->list的值与&nf_hook_ops相同,稍后在使用时会用到这一技巧;
成员hook即用户定义的勾子函数;owner表示注册这个勾子函数的模块,因为netfilter是内核空间的,所以一般为模块来完成勾子函数注册;pf与hooknum一起索引到特定协议特定编号的勾子函数队列,用于索引nf_hooks; priority决定在同一队列(pf与hooknum相同)的顺序,priority越小则排列越靠前。
        struct nf_hook_ops只是存储勾子的数据结构,而真正存储这些勾子供协议栈调用的是nf_hooks,从定义可以看出,它其实就是二维数组的链表。
        struct list_head nf_hooks[NFPROTO_NUMPROTO][NF_MAX_HOOKS]; [net\filter\core.c]
        其中NFPROTO_NUMPROTO表示勾子关联的协议,可取值:
  1. enum {  
  2.     NFPROTO_UNSPEC =  0,  
  3.     NFPROTO_IPV4   =  2,  
  4.     NFPROTO_ARP    =  3,  
  5.     NFPROTO_BRIDGE =  7,  
  6.     NFPROTO_IPV6   = 10,  
  7.     NFPROTO_DECNET = 12,  
  8.     NFPROTO_NUMPROTO,  
  9. };  
enum { NFPROTO_UNSPEC = 0, 
NFPROTO_IPV4 = 2,
 NFPROTO_ARP = 3,
 NFPROTO_BRIDGE = 7,
 NFPROTO_IPV6 = 10,
 NFPROTO_DECNET = 12,
 NFPROTO_NUMPROTO, };        
NF_MAX_HOOKS表示勾子应用的位置,可选值在每个协议模块内部定义,这些值代表了勾子函数在协议流程中应用的位置(稍后会以bridge为例详细说明),大致上都有以下值:
  1. NF_XXX_PRE_ROUTING,  
  2. NF_XXX_LOCAL_IN,  
  3. NF_XXX_FORWARD,  
  4. NF_XXX_LOCAL_OUT,  
  5. NF_XXX_POST_ROUTING,  
  6. NF_XXX_NUMHOOKS  
NF_XXX_PRE_ROUTING, NF_XXX_LOCAL_IN, NF_XXX_FORWARD, NF_XXX_LOCAL_OUT, NF_XXX_POST_ROUTING, NF_XXX_NUMHOOKS
NetFilter注册
        在了解了nf_hook_ops和nf_hooks后,来看下如何操作nf_hooks中的元素。
        nf_register_hook()将nf_hook_ops注册到nf_hooks中:
  1. int nf_register_hook(struct nf_hook_ops *reg)  
  2. {  
  3.     struct nf_hook_ops *elem;  
  4.     int err;  
  5.     err = mutex_lock_interruptible(&nf_hook_mutex);  
  6.     if (err < 0)  
  7.         return err;  
  8.     list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {  
  9.         if (reg->priority < elem->priority)  
  10.             break;  
  11.     }  
  12.     list_add_rcu(®->list, elem->list.prev);  
  13.     mutex_unlock(&nf_hook_mutex);  
  14.     return 0;  
  15. }  
int nf_register_hook(struct nf_hook_ops *reg) { 
struct nf_hook_ops *elem;
 int err;
 err = mutex_lock_interruptible(&nf_hook_mutex);
if (err < 0) return err;
 list_for_each_entry(elem, &nf_hooks[reg->pf][reg->hooknum], list) {
 if (reg->priority < elem->priority) 
break; 
}
 list_add_rcu(®->list, elem->list.prev);
 mutex_unlock(&nf_hook_mutex);
 return 0;
 }        
这个函数很简单,从指定pf&hooknum的nf_hooks队列遍历,按priority从小到大顺序,将reg插入相应位置,完成勾子函数的注册。

        nf_unregister_hook()将nf_hook_ops从nf_hooks中注销掉:
  1. void nf_unregister_hook(struct nf_hook_ops *reg)  
  2. {  
  3.     mutex_lock(&nf_hook_mutex);  
  4.     list_del_rcu(®->list);  
  5.     mutex_unlock(&nf_hook_mutex);  
  6.     synchronize_net();  
  7. }  
void nf_unregister_hook(struct nf_hook_ops *reg) { 
mutex_lock(&nf_hook_mutex);
 list_del_rcu(®->list);
 mutex_unlock(&nf_hook_mutex);
 synchronize_net();
 }        
这个函数更简单,从nf_hooks中删除reg。
        内核同时还提供了nf_register_hooks()和nf_unregister_hooks(),将reg重复注册n次或将reg从nf_hooks中注销n次。当勾子函数注册完成后,nf_hooks的结构如图所示:
 

NetFilter调用
        在报文在内核协议栈传递时,会调用NetFilter模块对报文进行特定的进滤,这样的过滤在代码中随处可见。
        以上一篇讲过的网桥为例,对于要进行网桥处理的报文,handle_bridge()->br_handle_frame(),如果端口处理于LEARNING或FORWARDING状态,且报文目的地址正确,则会调用br_handle_frame()进行后续处理,而这个函数调用就是:
NF_HOOK(PF_BRIDGE, NF_BR_PRE_ROUTING, skb, skb->dev, NULL,
br_handle_frame_finish);
NF_HOOK()->NF_HOOK_THRESH()->nf_hook_thresh()->nf_hook_slow():
  1. int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb,  
  2.          struct net_device *indev,  
  3.          struct net_device *outdev,  
  4.          int (*okfn)(struct sk_buff *),  
  5.          int hook_thresh)  
  6. {  
  7.     struct list_head *elem;  
  8.     unsigned int verdict;  
  9.     int ret = 0;    
  10.     /* We may already have this, but read-locks nest anyway */  
  11.     rcu_read_lock();  
  12.     elem = &nf_hooks[pf][hook];  
  13. next_hook:  
  14.     verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev,  
  15.                  outdev, &elem, okfn, hook_thresh);  
  16.     if (verdict == NF_ACCEPT || verdict == NF_STOP) {  
  17.         ret = 1;  
  18.     } else if (verdict == NF_DROP) {  
  19.         kfree_skb(skb);  
  20.         ret = -EPERM;  
  21.     } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {  
  22.         if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,  
  23.                   verdict >> NF_VERDICT_BITS))  
  24.             goto next_hook;  
  25.     }  
  26.     rcu_read_unlock();  
  27.     return ret;  
  28. }  
int nf_hook_slow(u_int8_t pf, unsigned int hook, struct sk_buff *skb, struct net_device *indev, struct net_device *outdev, int (*okfn)(struct sk_buff *), int hook_thresh) 
{ 
	struct list_head *elem;
	 unsigned int verdict;
	  int ret = 0; /* We may already have this, but read-locks nest anyway */ 
	 rcu_read_lock();
	 elem = &nf_hooks[pf][hook]; 
next_hook: 
	verdict = nf_iterate(&nf_hooks[pf][hook], skb, hook, indev, outdev, &elem, okfn, hook_thresh);
	if (verdict == NF_ACCEPT || verdict == NF_STOP) {
		 ret = 1; 
		} else if (verdict == NF_DROP) {
			 kfree_skb(skb); ret = -EPERM; 
			} else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) { 
				if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS)) 
					goto next_hook; 
			} 
			rcu_read_unlock(); 
			return ret; 
		} 
	}
}

       nf_hook_slow()从nf_hooks中找出到执行的勾子队列,依次执行,然后根据返回值决定是否继续(由nf_iterate()完成)。参数中的pf和hook代表了注册勾子函数时给的参数PF和HOOKNUM,它们共同决定勾子函数要插入的nf_hook的哪个队列中。 
        作为过滤报文的勾子函数的返回值是值得注意的地方,可取值如下:
  1. #define NF_DROP 0   
  2. #define NF_ACCEPT 1   
  3. #define NF_STOLEN 2   
  4. #define NF_QUEUE 3   
  5. #define NF_REPEAT 4   
  6. #define NF_STOP 5  
#define NF_DROP 0 #define NF_ACCEPT 1 #define NF_STOLEN 2 #define NF_QUEUE 3 #define NF_REPEAT 4 #define NF_STOP 5        先以nf_iterate()函数为例,elem->hook()表示执行勾子函数,执行结构为verdict;
  1. unsigned int nf_iterate(……)  
  2. {  
  3.     unsigned int verdict;  
  4.   
  5.   
  6.     list_for_each_continue_rcu(*i, head) {  
  7.         struct nf_hook_ops *elem = (struct nf_hook_ops *)*i;  
  8.         if (hook_thresh > elem->priority)  
  9.             continue;  
  10.         verdict = elem->hook(hook, skb, indev, outdev, okfn);  
  11.         if (verdict != NF_ACCEPT) {  
  12.             if (verdict != NF_REPEAT)  
  13.                 return verdict;  
  14.             *i = (*i)->prev;  
  15.         }  
  16.     }  
  17.     return NF_ACCEPT;  
  18. }  
unsigned int nf_iterate(……) { unsigned int verdict; list_for_each_continue_rcu(*i, head) { struct nf_hook_ops *elem = (struct nf_hook_ops *)*i; if (hook_thresh > elem->priority) continue; verdict = elem->hook(hook, skb, indev, outdev, okfn); if (verdict != NF_ACCEPT) { if (verdict != NF_REPEAT) return verdict; *i = (*i)->prev; } } return NF_ACCEPT; }        根据nf_iterate()返回,会有以下情况:
              1.如果结果为NF_ACCEPT,表示勾子函数允许报文继续向下处理,此时应该继续执行队列上的下一个勾子函数,因为这些勾子函数都是对同一类报文在相同位置的过滤,前一个通后,并不能返回,而要所有函数都执行完,结果仍为NF_ACCEPT时,则可返回它;
              2.如果结果为NF_REPEAT,表示要重复执行勾子函数一次;所以勾子函数要编写得当,否则报文会一直执行一个返回NF_REPEAET的勾子函数,当返回值为NF_REPEAT时,不会返回;
              3.如果为其它结果,则不必再执行队列上的其它函数,直接返回它;如NF_STOP表示停止执行队列上的勾子函数,直接返回;NF_DROP表示丢弃掉报文;NF_STOLEN表示报文不再往上传递,与NF_DROP不同的是,它没有调用kfree_skb()释放掉skb;NF_QUEUE检查给定协议(pf)是否有队列处理函数,有则进行处理,否则丢掉。
        了解了这些值再来看nf_hook_slow()中对于nf_iterate()返回值的处理就明了了:
  1. if (verdict == NF_ACCEPT || verdict == NF_STOP) {  
  2.     ret = 1;  
  3. else if (verdict == NF_DROP) {  
  4.     kfree_skb(skb);  
  5.     ret = -EPERM;  
  6. else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) {  
  7.     if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn,  
  8.              verdict >> NF_VERDICT_BITS))  
  9.         goto next_hook;  
  10. }  
if (verdict == NF_ACCEPT || verdict == NF_STOP) { ret = 1; } else if (verdict == NF_DROP) { kfree_skb(skb); ret = -EPERM; } else if ((verdict & NF_VERDICT_MASK) == NF_QUEUE) { if (!nf_queue(skb, elem, pf, hook, indev, outdev, okfn, verdict >> NF_VERDICT_BITS)) goto next_hook; }        最后还是以bridge来说明下hooks参数的意义,上面已经讲过,它决定了在协议流程的何处调用勾子函数;因为使用NetFilter的目的是在内核态处理报文,而哪些地方可以处理报文只能是内核已经定义好的。一般来说,内核会在报文发送和接收的关键位置添加勾子函数处理,查找代码中NF_HOOK即可知。下面以bridge,为例,来看下在哪些地方用到了,以及这些值的含义:
  
        NetFilter的存在使得在内核空间对报文进行用户定义的要求处理变得可能、简单。一般来说,编写好struct nf_hook_ops,其中hook/pf/ hook是必给的参数,然后使用nf_register_hook进行注册就可以了。整个过滤文件可以写了一个内核模块,用insmod进行动态加载。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值