Netfiler框架简单使用

Netfilter是什么?

简单来说,它是内核提供的一个子系统,作为一个通用的、抽象的框架,提供一整套的hooks函数的管理机制,可以跟踪所有经过本机网络数据包。基于此,我们可以做网络数据包的过滤、网络地址转换(NAT)和基于协议类型的连接等事情。Linux下应用主要应用在iptables,nftables, VPN的实现,它们就是对Netfilter的一个强大的封装。
  netfilter的框架中有5个钩子,允许内核模块在这些钩子的地方注册回调函数,这样经过钩子的所有数据包都会被注册在相应钩子上的函数所处理,包括修改数据包内容、给数据包打标记或者丢掉数据包等。我们可以基于Netfilter实现自定义的网络安全相关的模块和特殊包过滤需求。 Netfilter是基于网络层的,因此可以截获所有IP,ICMP, RIP, OSPF, BGP, IGMP网络数据包。
 
 上图是对Netfilter框架一个简单视图,A,B,C,D,E对应五个钩子点位置.
A: NF_IP_PRE_ROUTING: 接收的数据包刚进来,还没有经过路由选择,即还不知道数据包是要发给本机还是其它机器。
B: NF_IP_LOCAL_IN: 已经经过路由选择,并且该数据包的目的IP是本机,进入本地数据包处理流程。
C: NF_IP_FORWARD: 已经经过路由选择,但该数据包的目的IP不是本机,而是其它机器,进入forward流程。
D: NF_IP_LOCAL_OUT: 本地程序要发出去的数据包刚到IP层,还没进行路由选择。
E: NF_IP_POST_ROUTING: 本地程序发出去的数据包,或者转发(forward)的数据包已经经过了路由选择,即将交由下层发送出去。

数据报经过各个HOOK的流程

数据报从进入系统,进行IP校验以后,首先经过第一个HOOK函数NF_IP_PRE_ROUTING进行处理;然后就进入路由代码,其决定该数据报是需要转发还是发给本机的;若该数据报是发被本机的,则该数据经过HOOK函数NF_IP_LOCAL_IN处理以后然后传递给上层协议;若该数据报应该被转发则它被NF_IP_FORWARD处理;经过转发的数据报经过最后一个HOOK函数NF_IP_POST_ROUTING处理以后,再传输到网络上。本地产生的数据经过HOOK函数NF_IP_LOCAL_OUT 处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING处理后发送出去。
下面用一个简单试列来注册hooks函数程序, 打印出所有本机接收到的TCP包的IP地址.

#include <linux/kernel.h>                                                       
#include <linux/module.h>                                                       
#include <asm/atomic.h>                                                         
#include <linux/ip.h>                                                           
#include <linux/version.h>                                                      
#include <linux/skbuff.h>                                                       
#include <linux/netfilter_ipv4.h>                                                                                                                                                                                    
#include <linux/moduleparam.h>                                                  
#include <linux/netfilter_ipv4/ip_tables.h>                                     

#define NIPQUAD(addr) \                                                         
((unsigned char *)&addr)[0], ((unsigned char *)&addr)[1], ((unsigned char *)&addr)[2], ((unsigned char *)&addr)[3]

/* IP Hooks */                                                                  
#define NF_IP_PRE_ROUTING   0                                                   
#define NF_IP_LOCAL_IN      1                                                   
#define NF_IP_FORWARD       2                                                   
#define NF_IP_LOCAL_OUT     3                                                   
#define NF_IP_POST_ROUTING  4                                                   
#define NF_IP_NUMHOOKS      5                                                   

static unsigned int myhook_func(const struct nf_hook_ops *ops, struct sk_buff* skb, const struct net_device* in, const struct net_device* out, int(*okfn)(struct sk_buff*))
{                                                                               
    const struct iphdr* iph = ip_hdr(skb);                                      

    if(iph->protocol == 6)                                                      
    {                                                                           
            printk(KERN_INFO "IP: %u.%u.%u.%u!\n", NIPQUAD(iph->daddr));        
    }                                                                           

    return NF_ACCEPT;                                                           
}                                                                               

//<linux/netfilter.h>                                                           
static struct nf_hook_ops nfhook =                                                
{                                                                               
    .hook = myhook_func,          //回调函数
    .owner = THIS_MODULE,                                                       
    .pf = PF_INET,                                                              
    .hooknum = NF_IP_LOCAL_OUT,   //挂载在本地出口处    
    .priority = NF_IP_PRI_FIRST,  //优先级最高                                  
};                                                                              

static int __init myhook_init(void)                                             
{                                                                               
    return nf_register_net_hook(&nfhook);                                         
}                                                                               

static void __exit myhook_fini(void)                                            
{                                                                               
    nf_unregister_net_hook(&nfhook);                                              
}

module_init(myhook_init);                                                       
module_exit(myhook_fini);                                                       

MODULE_LICENSE("GPL");                                                                                                                
MODULE_DESCRIPTION("Myhook"); 

我们在hoosk函数里可以干很多事情,回调函数里会有一个skb_buff结构体,存放着网络数据包数据,从中可以提取出ip包的所有信息和数据,我们也可以修改IP包,比如在NF_IP_LOCAL_OUT的hooks中,对出去的IP包中写入Deepin字符,从新计算IP头的checksum后封装成新的IP包,并在NF_IP_LOCAL_IN的hooks中去校验Deepin这个标识,没发现就DROP这个IP包。那么网络层之后的OSI层将无法接收到任何非Deepin标识的网络包,并且只有安装了这个模块的终端才能进行网络传输。我们还需要注意的是,在回调函数最后,我们需要返回网络包处理状态,这些回调函数最后必须向Netfilter报告一下该数据包的死活情况,因为毕竟每个数据包都是Netfilter从人家协议栈那儿借调过来用的,别个再怎么滴也总得“活要见人,死要见尸”。每个钩子函数最后必须向Netfilter框架返回下列几个值其中之一:
1. NF_ACCEPT继续正常传输数据报。这个返回值告诉Netfilter:到目前为止,该数据包还是被接受的并且该数据包应当被递交到网络协议栈的下一个阶段。
2. NF_DROP丢弃该数据报,不再传输
3. NF_STOLEN模块接管该数据报,告诉Netfilter“忘掉”该数据报。该回调函数将从此开始对数据包的处理,并且Netfilter应当放弃对该数据包做任何的处理。但是,这并不意味着该数据包的资源已经被释放。这个数据包以及它独自的sk_buff数据结构仍然有效,只是回调函数从Netfilter获取了该数据包的所有权。
4. NF_QUEUE对该数据报进行排队(通常用于将数据报给用户空间的进程进行处理)
5. NF_REPEAT 再次调用该回调函数,应当谨慎使用这个值,以免造成死循环。

通过上述方式注册Netfilter的hooks函数,我们就能够随意操作本机所有网络包,当然,网络包的过滤机制iptables已经实现了很强大的功能,但我们依旧可以利用Netfilter定制出自己的防火墙机制。 

1 条思考于 “Netfiler框架简单使用

  1. iamhyc65

    这内容有点少啊,都没讲怎么构造skb;

    补充分享计算checksum的函数:

    * IP checksum:
    “`
    struct iphdr *iph = NULL;
    iph = ip_hdr(skb);
    iph->check = 0;
    iph->check = ip_fast_csum(iph, iph->ihl);
    “`

    * TCP checksum:
    “`
    struct iphdr *iph = NULL;
    struct tcphdr *tcph = NULL;
    tcph = (struct tcphdr *)((u8 *)iph + iph->ihl*4);
    tcph->check = 0;
    iph->check = 0; //set to ZERO to force re-calculation
    tcph->check = ~csum_tcpudp_magic(
    iph->saddr, iph->daddr,
    skb->len – iph->ihl*4,
    iph->protocol, 0);
    “`

发表评论

电子邮件地址不会被公开。 必填项已用*标注