Netfilter源码分析

来源:互联网 发布:网络嘿嘿嘿是什么意思 编辑:程序博客网 时间:2024/04/29 19:04

一、基本介绍

1.1、常用名词解释

1target//规则匹配后的处理方法

一般将target分为两类,一类为标准的target,即下面的宏定义

NF_DROP                丢弃该数据包

NF_ACCEPT            保留该数据包

NF_STOLEN            忘掉该数据包

NF_QUEUE             将该数据包插入到用户空间

NF_REPEAT            再次调用该hook函数

另一类为由模块扩展的target,

REJECT,LOG,ULOG,TOS,DSCP,MARK,REDIRECT,MASQUERADE,NETMAP

2hook

//这个成员用于指定安装的这个函数对应的具体的hook类型:

NF_IP_PRE_ROUTING    在完整性校验之后,选路确定之前

NF_IP_LOCAL_IN        在选路确定之后,且数据包的目的是本地主机

NF_IP_FORWARD        目的地是其它主机地数据包

NF_IP_LOCAL_OUT        来自本机进程的数据包在其离开本地主机的过程中

NF_IP_POST_ROUTING    在数据包离开本地主机“上线”之前

3chain

相同类型的hook的所有操作以优先级升序排列所组成的链表。

4match

//匹配方式

4.1、标准匹配

interface,ip address,protocol

4.2、由模块延伸出来的匹配

tcp协议高级匹配,udp协议高级匹配,MACaddress 匹配,Multiport匹配,匹配包的MARK值,Owner匹配,IP范围匹配,包的状态匹配,AHESP协议的SPI值匹配,pkttype匹配,(MTU)匹配,limit特定包的重复率匹配,recent,特定包重复率匹配,

ip报头中的TOS值匹配,匹配包中的数据内容。

5table

//netfilter中规则都存放在此结构中

1.2、简介

NetfilterLinux的第三代防火墙,在此之前有IpfwadmIpchains两种防火墙。Netfilter 是通过将一系列调用入口,嵌入到内核IP协议栈的报文处理的路径上来完成对数据包的过滤,修改,跟踪等功能。网络数据包按照来源和去向,可以分为三类:流入的(IN)、流经的(FORWARD)和流出的(OUT),其中流入和流经的报文需要经过路由才能区分,而流经和流出的报文则需要经过投递,此外,流经的报文还有一个FORWARD的过程。

具体来说,Netfilter 框架为多种协议提供了一套类似的钩子(HOOK),用一个struct list_headnf_hooks[NPROTO][NF_MAX_HOOKS]二维数组结构存储,一维为协议族,二维为上面提到的各个调用入口。每个希望嵌入 Netfilter中的模块都可以为多个协议族的多个调用点注册多个钩子函数(HOOK),这些钩子函数将形成一条函数指针链,每次协议栈代码执行到 NF_HOOK()函数时(有多个时机),都会依次启动所有这些函数,处理参数所指定的协议栈内容。每个注册的钩子函数经过处理后都将返回下列值之一,告知Netfilter核心代码处理结果,以便对报文采取相应的动作。

在现有(kernel 2.6.30.3)中已内建了四个tablesfilternatmangleraw,和一个辅助功能,连接跟踪(ConnTrack)。绝大部分报文处理功能都可以通过在这些内建(built-in)的表格中填入规则完成:

FILTER,该模块的功能是过滤报文,不作任何修改,或者接受,或者拒绝。它在NF_IP_LOCAL_INNF_IP_FORWARDNF_IP_LOCAL_OUT三处注册了钩子函数,也就是说,所有报文都将经过filter模块的处理。

NAT,网络地址转换(Network AddressTranslation),该模块以ConnectionTracking模块为基础,仅对每个连接的第一个报文进行匹配和处理,然后交由ConnectionTracking模块将处理结果应用到该连接之后的所有报文。natNF_IP_PRE_ROUTINGNF_IP_POST_ROUTING注册了钩子函数;如果需要,还可以在NF_IP_LOCAL_INNF_IP_LOCAL_OUT两处注册钩子,提供对本地报文(出/入)的地址转换。nat 仅对报文头的地址信息进行修改,而不修改报文内容,按所修改的部分,nat可分为源NATSNAT)和目的NATDNAT)两类,前者修改第一个报文的源地址部分,而后者则修改第一个报文的目的地址部分。SNAT可用来实现IP伪装,而DNAT则是透明代理的实现基础。

MANGLE,属于可以进行报文内容修改的table,可供修改的报文内容包括MARKTOSTTL等,mangle表的操作函数嵌入在NetfilterNF_IP_PRE_ROUTING,NF_IP_LOCAL_IN,NF_IP_FORWARD,NF_IP_LOCAL_OUT,NF_IP_POST_ROUTING五处。内核编程人员还可以通过注入模块,调用Netfilter的接口函数创建新的iptables

ConnTrack, 连接跟踪用来跟踪和记录连接状态,是netfilter的一部分,也通过在hook点设定操作函数来完成。

RAW,netfilter提供了连接跟踪功能,在为了不让某一连接被跟踪时,可以使用rawtable,同时它也可以使数据包绕过 nat,因此rawtable 的主要作用就是减少不必要的包处理。Rawtable只注册在NF_IP_PRE_ROUTINGNF_IP_LOCAL_OUT两个hook点。

1.3数据包处理流程

Netfilter源码分析--1、基本介绍

   综上所述,Netfilter的处理结构如上图所示,我们用不同的颜色来代表不同的表,不同的表在不同的hook点有不同的优先级顺序,在前面的表有较高的优先级,Netfilter中不同的表在不同的hook点的优先级如下所示:

NF_IP_PRI_FIRST =INT_MIN,

       NF_IP_PRI_CONNTRACK_DEFRAG= -400,//连接跟踪

       NF_IP_PRI_RAW= -300,//RAW表的优先级

       NF_IP_PRI_SELINUX_FIRST= -225,

       NF_IP_PRI_CONNTRACK= -200,//

       NF_IP_PRI_MANGLE= -150,//

       NF_IP_PRI_NAT_DST= -100,//目标NATDNAT)优先级

       NF_IP_PRI_FILTER= 0,//FILTER表优先级

       NF_IP_PRI_SECURITY= 50,

       NF_IP_PRI_NAT_SRC= 100,//NAT优先级

       NF_IP_PRI_SELINUX_LAST= 225,

       NF_IP_PRI_CONNTRACK_CONFIRM= INT_MAX,//确认CONNTRACK优先级

       NF_IP_PRI_LAST= INT_MAX,

简单说来,进入内核的数据包在经过不同的hook点时会遇到不同的处理机制,Netfilter各个不同的表中规则对数据包进行处理。在目前的内核版本中,netfilter提供四种处理机制,这四种处理机制有一定的先后顺序,在制定防火墙规则时要多加注意。

二、重要数据结构及分析

本章所涉及的数据结构均为Netfilter框架中的核心数据结构,不与具体的规则相联系,同时为了保证分析的完整性,本章以ipv4的相关规则定义为基础,对框架的核心数据结构的关系进行完整的分析,ipv6,arp,netbriget与此类似。
2.1、xt_table及xt_table_info
xt_table是Netfilter中的核心数据结构,它包含了每个表的所有规则信息,数据包进入Netfilter后通过查表,匹配相应的规则来决定对数据包的处理结果。其成员布局如下图所示:

Netfilter源码分析--2、重要数据结构及分析

图2.1 xt_table结构布局
每个成员的含义如下:
unsigned intvalid_hooks//所有支持的hook点操作,例如filter表有三处设置了hook操作,则对应的//valid_hooks为:
struct xt_table_info *private //xt_table的数据区,下面详细介绍
struct module *me;//如果是个模型设置成THIS_MODULE,否则为NULL
u_int8_t af //地址/协议族
const char name[XT_TABLE_MAXNAMELEN];//表的名字
xt_table的所有数据都存在private的成员变量中,private是结构体structxt_table_info,它的布局如下图所示,每个规则后面会跟随一个target,及对匹配的数据包的处理方式:
Netfilter源码分析--2、重要数据结构及分析
图2.2 xt_table_info结构体布局
每个成员的含义如下:
unsigned int size //每个表的尺寸
unsigned int number //表中存了多少规则
unsigned int initial_entries //表中存放的初始规则数,用于模型计数
unsigned int hook_entry[NF_INET_NUMHOOKS]//不同hook点规则的偏移
void *entries[1];//具体的规则存储在这里,这个成员包含了具体的规则信息
entries本质上是一个ipt_standard结构体,ipt_standard中包含了具体的规则及处理方法,ipt_standard的结构定义为:
struct ipt_standard
{
 

   structipt_entry entry;
    structipt_standard_target target;
};

2.2、entry相关数据结构
ipt_entry的结构体的布局如下:    
Netfilter源码分析--2、重要数据结构及分析
图2.3 ipt_match及xt_match布局
每个成员的含义如下:
struct ipt_ip ip //源和目的IP地址
u_int16_t target_offset //target的偏移量,其值为ipt_entry + match的尺寸值
u_int16_t next_offset //下一个match的偏移量,其值为ipt_entry + matches +target的尺寸
struct xt_counters counters //记录数据包(packet)和字节(byte)的数目
unsigned char elems[0];可变数据区域,存放具体的match
在ipt_entry中,每个elmes本质上是一个xt_entry_match,它包含了一个xt_match和xt_match的执行函数所需要的参数。
一个具体的match的布局如上图所示的xt_match,它的各个成员的含义如下:
const char name[XT_FUNCTION_MAXNAMELEN-1]//每个match的名字
u_int8_t revision; //修订的版本号
bool (*match)(const struct sk_buff *skb, const structxt_match_param *);

const char *table //这个match所属的表的名字
unsigned int hooks;//match在哪个hook注册
unsigned short proto;//协议号
unsigned short family//地址族
struct module *me//模型,同xt_table
2.3、target数据结构
Netfilter源码分析--2、重要数据结构及分析
图2.4 xt_target及相关结构布局
ipt_standard的另一个成员是xt_standard_target,这个结构体包含了两个类别的target即:标准的target和扩展的target,xt_standard_target的结构定义如下:
struct xt_standard_target
{
    structxt_entry_target target;
    intverdict;
};
其中xt_entry_target包含了一个具体可扩展的target,它包含了两个主要的数据成员,u存储了一个具体的结构体xt_target,xt_target的布局如上图所示,data存放了,xt_target的执行函数所需要的参数信息。xt_target每个成员的具体含义如下:
const char name[XT_FUNCTION_MAXNAMELEN-1]//target的名称
unsigned int (*target)(struct sk_buff *skb, const structxt_target_param *);
其他的数据成员与xt_match的含义相同。
2.4、所有相关数据结构的完成定义
struct xt_entry_match
{
    union{
      struct {
         u_int16_t match_size;//match尺寸

         
         charname[XT_FUNCTION_MAXNAMELEN-1];//match名称
         u_int8_t revision;
      } user;
      struct {
         u_int16_t match_size;//match尺寸

         
         struct xt_match *match;//具体的match
      } kernel;

      
      u_int16_t match_size;//match尺寸
    } u;

    unsignedchar data[0];//match信息,可变数据区
};

struct xt_entry_target
{
    union{
      struct {
         u_int16_t target_size;//target尺寸

         
         charname[XT_FUNCTION_MAXNAMELEN-1];//target名称

         u_int8_t revision;
      } user;//用户空间使用的target信息
      struct {
         u_int16_t target_size;//target尺寸

         
         struct xt_target *target;//具体的target
      } kernel;//内核空间使用的targe信息

      
      u_int16_t target_size;
    } u;

    unsignedchar data[0];//target信息,可变数据区域
};


struct xt_match
{
    structlist_head list;
    const charname[XT_FUNCTION_MAXNAMELEN-1];//match的名字
   
    bool(*match)(const struct sk_buff *skb,//一个match的真正执行者
           const struct net_device *in,
           const struct net_device *out,
           const struct xt_match *match,
           const void *matchinfo,
           int offset,
           unsigned int protoff,
           bool *hotdrop);   
    
    bool(*checkentry)(const char *tablename,
            const void*ip,
            const structxt_match *match,
            void*matchinfo,
            unsigned inthook_mask);//检验match是否在指定的表中   
    void(*destroy)(const struct xt_match *match, void *matchinfo);   
    void(*compat_from_user)(void *dst, void *src);
    int(*compat_to_user)(void __user *dst, void *src);    
    structmodule *me;    
    unsignedlong data;
    char*table;//表名
    unsigned intmatchsize;
    unsigned intcompatsize;
    unsigned inthooks;//hook类型
    unsignedshort proto;
    unsignedshort family;//协议族
    u_int8_trevision;
};

struct xt_target
{
    structlist_head list;
    const charname[XT_FUNCTION_MAXNAMELEN-1];    
    unsigned int(*target)(struct sk_buff *skb,
               const struct net_device *in,
               const struct net_device *out,
               unsigned int hooknum,
               const struct xt_target *target,
               const void *targinfo);   
    
    bool(*checkentry)(const char *tablename,
            const void*entry,
            const structxt_target *target,
            void*targinfo,
            unsigned inthook_mask);   
    void(*destroy)(const struct xt_target *target, void *targinfo);   
    void(*compat_from_user)(void *dst, void *src);
    int(*compat_to_user)(void __user *dst, void *src);    
    structmodule *me;
    char*table;
    unsigned inttargetsize;
    unsigned intcompatsize;
    unsigned inthooks;
    unsignedshort proto;

    unsignedshort family;
    u_int8_trevision;
};

struct xt_table
{
    structlist_head list;
   
    charname[XT_TABLE_MAXNAMELEN];//表名    
    unsigned intvalid_hooks;//表所支持的hook操作    
    rwlock_tlock;    
    //structip6t_table_info *private;
    void*private;//table的数据区,为ipt_table_info或者ipt6_table_info    
    structmodule *me;
    intaf;      
};

struct xt_table_info//table真正的数据区
{
              
    unsigned intsize;    
    unsigned intnumber;    
    unsigned intinitial_entries;
    
    unsigned inthook_entry[NF_IP_NUMHOOKS];//每类hook对应的规则的入口
    unsigned intunderflow[NF_IP_NUMHOOKS];
    
    char*entries[NR_CPUS];//每个cpu上都对应相同的一套规则

三、Netfilter内部处理流程

分类:iptables

3、1 IP数据包处理流程

要想理解Netfilter的工作原理,必须从对Linux IP报文处理流程的分析开始,Netfilter正是将自己紧密地构建在这一流程之中的。IP 协议栈是Linux操作系统的主要组成部分,也是Linux的特色之一,素以高效稳定著称。NetfilterIP协议栈是密切结合在一起的,我们以接收报文为例,简要介绍一下IPv4协议栈(IP层)的报文处理过程。

报文接收从网卡驱动程序开始,当网卡收到一个报文时,会产生一个中断,其驱动程序中的中断服务程序将调用确定的接收函数来处理。流程分成两个阶段:驱动程序中断服务程序阶段和IP协议栈处理阶段,驱动程序的处理流程与本章的联系不是十分紧密,故不做详细的介绍,我们以下面的流程图简要介绍以下数据包的接收过程。

驱动程序处理报文时,会生成一个skb_buff,同时将其放入一个全局的存储结构当中,同时设置软中断NET_RX_SOFTIRQ等待内核处理,内核收到软中断后,报文便开始了协议栈之旅。我们用以下的流程图来表示接收报文时整个处理的流程:

从图3.1的流程可以看出,NetfilterNF_HOOK形式挂载到ip协议栈对报文的处理过程中,然后将相应的数据包转入到Netfilter中来处理,我们可以将IP协议栈中调用NF_HOOK的地方称之为挂载点。除了流程图中指出的几处挂载点,Netfitler还在ip协议栈的多处进行了挂载,可以具体参考内核网络部分的源码。

Netfilter源码分析--3、Netfilter内部处理流程

3.3.2、Netfilter对报文的处理流程

 


3.2.1、扩展机制

Netfilter的扩展通过对全局变量的注册来完成。所谓的注册其本质上就是将数据存储到全局变量中,为后续的调用做好准备,Netfilter的注册机制可以分为表注册,target注册,match注册,hook操作注册。

表注册就是将定义好的表存放到一个全局变量xttables成员变量中,这个成员变量为一个xt_af的结构体,定义如下:

*

*这是一个存放所有规则信息及表信息的数据结构

*/

struct xt_af {

       structmutex mutex;

       structlist_head match;//所有在内核中注册的match信息

       structlist_head target;//所有在内核中注册的target信息

       structlist_head tables;//所有表的信息

       structmutex compat_mutex;

};

static struct xt_af*xt;//一个全局变量,存储了所有内核和用户空间需要的规则信息。    

与表注册类似,target注册和match注册也是通过向全局变量xt的成员变量写入信息来完成的。Netfilter通过这种注册的方式来实现对其扩展机制,用户可以根据自己的需求来实现matchtarget,甚至是自己实现一个表,然后注册到相应的全局变量,当数据包进入Netfilter后,hook操作会查找对应的表,以实现对数据包的匹配和处理。

Hook操作的注册的地点与上面三个注册不同。先来看下什么是hook操作。Netfilter在不同的挂载点注册不同的操作函数,以达到为不同协议的不同挂载点的报文进行不同处理的目的,为实现这个目的Netfilter定义了一个叫做nf_hook_ops的结构体,具体定义如下:

structnf_hook_ops

{

       structlist_head list;      

       nf_hookfn*hook;//函数指针,操作的具体执行者

       structmodule *owner;

       intpf;//协议族

       inthooknum;//hook的类型      

       intpriority;//该操作的优先级,插入时使用

};

其中hook成员变量的原型为:

typedef unsigned intnf_hookfn(unsigned int hooknum,

                            structsk_buff *skb,

                            conststruct net_device *in,

                            conststruct net_device *out,

                            int(*okfn)(struct sk_buff *));//hook函数原型

Netfilter将不同协议的不同挂载点的操作函数都存放在一个全无变量nf_hooks中,数据包进入Netfilter后,会查找相应的hook操作,nf_hook对应的定义如下:

struct list_head nf_hooks[NPROTO][NF_MAX_HOOKS]__read_mostly;//存放所有协议的hooks

综上所述,我们可以用如下的表来表示,netfilter的内部的整个数据和扩展流程:

Netfilter源码分析--3、Netfilter内部处理流程

 

 

3.2 内部扩展机制

3.2.2Netfilter包处理

数据包在IP协议栈处理的过程中在经过挂载点时,就会进入Netfilter对报文做处理,NF_HOOK的整个调用流程可用如下的流程表来表示:

1、  整个hook的调用流程如下:

Netfilter源码分析--3、Netfilter内部处理流程

 

3.3Netfilter中包处理

其中NF_HOOK的定义如下:

#define NF_HOOK(pf, hook, skb,indev, outdev, okfn) \

       NF_HOOK_THRESH(pf,hook, skb, indev, outdev, okfn, INT_MIN)

四、FILTR四、FILTER


FILTER表顾名思义,用来对数据包进行做过滤,它是Netfilter中的核心表,提供了Netfilter防火墙的主要功能。

本章将以以ipv4filter表为例,从FILTER表的建立,注册以及hook操作对表的匹配过程对FILTER进行详细的分析。

34、1初始化与注册

首先是iptable_filter模块初始化时注册一个packet_filter表,及相应的hook操作

static struct xt_tablepacket_filter = {

       .name             ="filter",//表名

       .valid_hooks   =FILTER_VALID_HOOKS,//有效的hooks#define FILTER_VALID_HOOKS ((1<<                             

                                      //NF_IP_LOCAL_IN)| (1 << NF_IP_FORWARD)|

                                      //(1 <<NF_IP_LOCAL_OUT))

       .lock              =RW_LOCK_UNLOCKED,  //读写锁

       .me         =THIS_MODULE,           //模块

       .af          =AF_INET,                //所属协议族

};

然后初始化相应的hook操作,由于FILTER表只在三个挂载点注册了函数,故在ipt_ops中我们设置了三个hook操作。

static struct nf_hook_opsipt_ops[] = {

       {

              .hook             =ipt_hook,//具体的操作

              .owner           =THIS_MODULE,//模块

              .pf          =PF_INET,       //协议族

              .hooknum       =NF_IP_LOCAL_IN,  //hook的标号

              .priority   =NF_IP_PRI_FILTER,//操作的优先级

       },

       {

              .hook             =ipt_hook,

              .owner           =THIS_MODULE,

              .pf          =PF_INET,

              .hooknum       =NF_IP_FORWARD,

              .priority   =NF_IP_PRI_FILTER,

       },

       {

              .hook             =ipt_local_out_hook,

              .owner           =THIS_MODULE,

              .pf          =PF_INET,

              .hooknum       =NF_IP_LOCAL_OUT,

              .priority   =NF_IP_PRI_FILTER,

       },

};

表和hook操作初始化完成后,调用相应的注册函数注册到相应的全局标量当中去,这个注册函数分别为:

ipt_register_tablenf_register_hooks,其中ipt_register_table会调用xt_register_table,完成对表的注册。

表注册完成后是还不够的,这相当于我们有了仓库,还没工具,各种各样的targetmatch就是我们需要的工具,各种各样的targetmatch注册后,一个防火墙的过滤报文功能就可以实现了。

4.24、2、hook操作流程

相应的注册和初始化工作完成后,当有报文进入Netfilter时,我们就可以对报文进行处理了。图3.3中,没有详细的列出hook操作如何对报文进行处理,在这里将对图3.3中的最后一步,以IPV4filter表为例进行扩展。挂载点被触发,调用hook操作,hook操作会根据对应的规则来对报文进行处理。从hook操作开始后的函数调用流程如下:

Netfilter源码分析--4、FILTER

 

4.1FILTER表对数据包处理流程

4.4、3、重要函数分析

unsigned int

ipt_do_table(struct sk_buff*skb,

            unsignedint hook,

            conststruct net_device *in,

            conststruct net_device *out,

            structxt_table *table)

{

       staticconst char nulldevname[IFNAMSIZ]__attribute__((aligned(sizeof(long))));

       u_int16_toffset;

       structiphdr *ip;

       u_int16_tdatalen;

       boolhotdrop = false;

       

       unsignedint verdict = NF_DROP;

       constchar *indev, *outdev;

       void*table_base;

       structipt_entry *e, *back;

       structxt_table_info *private;

 

       

       ip= ip_hdr(skb);//获取ip

       datalen= skb->len - ip->ihl *4;

       indev= in ? in->name :nulldevname;//获得输入设备

       outdev= out ? out->name :nulldevname;//获得输出设备

       

       offset= ntohs(ip->frag_off) &IP_OFFSET;

 

       read_lock_bh(&table->lock);

       IP_NF_ASSERT(table->valid_hooks& (1 <<hook));//检验hook是否有效

       private= table->private;//获取table的数据区

       table_base= (void*)private->entries[smp_processor_id()];//获取相应cputable的所有规则,                              //match,target

       e= get_entry(table_base,private->hook_entry[hook]);//获取特定hook相对应的所有规则

 

       

       back= get_entry(table_base,private->underflow[hook]);

       do{

              IP_NF_ASSERT(e);

              IP_NF_ASSERT(back);

// 标准的匹配,详细分析见下面

              if(ip_packet_match(ip, indev, outdev,&e->ip, offset)){

       //  代码分析

                     structipt_entry_target *t;

                     if(IPT_MATCH_ITERATE(e, do_match,//匹配所有的match

                                         skb,in,out,      

                                         offset,&hotdrop) != 0)

                            gotono_match;

 

                     ADD_COUNTER(e->counters,ntohs(ip->tot_len), 1);

 

                     t= ipt_get_target(e);//获得match对应的target

                     IP_NF_ASSERT(t->u.kernel.target);

 

#ifdefined(CONFIG_NETFILTER_XT_TARGET_TRACE) || \

    defined(CONFIG_NETFILTER_XT_TARGET_TRACE_MODULE)

                     

                     if(unlikely(skb->nf_trace))

                            trace_packet(skb,hook, in, out,

                                        table->name,private, e);

#endif

                     

                     if(!t->u.kernel.target->target){//判断是否是标准的target

                            intv;

 

                     v= ((struct ipt_standard_target*)t)->verdict;//获得相应的target

                            if(v < 0) {

                                   

                                   if(v != IPT_RETURN) {

                                          verdict= (unsigned)(-v) - 1;

                                          break;

                                   }

                                   e= back;

                                   back= get_entry(table_base,

                                                  back->comefrom);

                                   continue;

                            }

                            if(table_base + v != (void *)e +e->next_offset

                                &&!(e->ip.flags & IPT_F_GOTO)){

                                   

                                   structipt_entry *next

                                          =(void *)e + e->next_offset;

                                   next->comefrom

                                          =(void *)back - table_base;

                                   

                                   back= next;

                            }

 

                            e= get_entry(table_base, v);

                     }else {//不是标准的target,也就是说match定义了自己的target

                            

#ifdefCONFIG_NETFILTER_DEBUG

                            ((structipt_entry*)table_base)->comefrom

                                   =0xeeeeeeec;

#endif

                            verdict=t->u.kernel.target->target(skb,//获得target标准的target

                                                             in,out,

                                                             hook,

                                                             t->u.kernel.target,

                                                             t->data);

 

#ifdefCONFIG_NETFILTER_DEBUG

                            if(((struct ipt_entry*)table_base)->comefrom

                                !=0xeeeeeeec

                                &&verdict == IPT_CONTINUE) {

                                   printk("Target%s reentered!\n",

                                          t->u.kernel.target->name);

                                   verdict= NF_DROP;

                            }

                            ((structipt_entry*)table_base)->comefrom

                                   =0x57acc001;

#endif

                            

                            ip= ip_hdr(skb);

                            datalen= skb->len - ip->ihl *4;

 

                            if(verdict == IPT_CONTINUE)

                                   e= (void *)e +e->next_offset;

                            else

                                   

                                   break;

                     }

              }else {

 

              no_match:

                     e= (void *)e +e->next_offset;//继续下个匹配

              }

       }while (!hotdrop);

 

       read_unlock_bh(&table->lock);

 

#ifdefDEBUG_ALLOW_ALL

       returnNF_ACCEPT;

#else

       if(hotdrop)

              returnNF_DROP;

       elsereturn verdict;

#endif

}

 

static inlineint

ip_packet_match(const structiphdr *ip,

              constchar *indev,

              constchar *outdev,

              conststruct ipt_ip *ipinfo,

              intisfrag)

{

       size_ti;

       unsignedlong ret;

 

#define FWINV(bool,invflg) ((bool) ^!!(ipinfo->invflags &invflg))//boolinvflg不同时为真//两个!为了使后面的表达式只能为1或0

       //判断源地址和目的地址是否相等

  if(FWINV((ip->saddr&ipinfo->smsk.s_addr)!= ipinfo->src.s_addr,

                IPT_INV_SRCIP)

           ||FWINV((ip->daddr&ipinfo->dmsk.s_addr)!= ipinfo->dst.s_addr,

                   IPT_INV_DSTIP)){

              dprintf("Sourceor dest mismatch.\n");

              dprintf("SRC:%u.%u.%u.%u. Mask: %u.%u.%u.%u. Target:%u.%u.%u.%u.%s\n",

                     NIPQUAD(ip->saddr),

                     NIPQUAD(ipinfo->smsk.s_addr),

                     NIPQUAD(ipinfo->src.s_addr),

                     ipinfo->invflags& IPT_INV_SRCIP ? " (INV)" :"");

              dprintf("DST:%u.%u.%u.%u Mask: %u.%u.%u.%u Target:%u.%u.%u.%u.%s\n",

                     NIPQUAD(ip->daddr),

                     NIPQUAD(ipinfo->dmsk.s_addr),

                     NIPQUAD(ipinfo->dst.s_addr),

                     ipinfo->invflags& IPT_INV_DSTIP ? " (INV)" :"");

              return0;

       }

//接口是否匹配

       

       for(i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long);i++) {

              ret|= (((const unsigned long *)indev)[i]

                     ^((const unsigned long*)ipinfo->iniface)[i])

                     &((const unsigned long*)ipinfo->iniface_mask)[i];

       }

       if(FWINV(ret != 0, IPT_INV_VIA_IN)) {

              dprintf("VIAin mismatch (%s vs %s).%s\n",

                     indev,ipinfo->iniface,

                     ipinfo->invflags&IPT_INV_VIA_IN?" (INV)":"");

              return0;

       }

 

       for(i = 0, ret = 0; i < IFNAMSIZ/sizeof(unsigned long);i++) {

              ret|= (((const unsigned long *)outdev)[i]

                     ^((const unsigned long*)ipinfo->outiface)[i])

                     &((const unsigned long*)ipinfo->outiface_mask)[i];

       }

       if(FWINV(ret != 0, IPT_INV_VIA_OUT)) {

              dprintf("VIAout mismatch (%s vs %s).%s\n",

                     outdev,ipinfo->outiface,

                     ipinfo->invflags&IPT_INV_VIA_OUT?" (INV)":"");

              return0;

       }

       

//协议是否匹配

       if(ipinfo->proto

           &&FWINV(ip->protocol != ipinfo->proto,IPT_INV_PROTO)) {

              dprintf("Packetprotocol %hi does not match %hi.%s\n",

                     ip->protocol,ipinfo->proto,

                     ipinfo->invflags&IPT_INV_PROTO? " (INV)":"");

              return0;

       }

       

//分片是否匹配

       if(FWINV((ipinfo->flags&IPT_F_FRAG)&& !isfrag, IPT_INV_FRAG)){

              dprintf("Fragmentrule but not fragment.%s\n",

                     ipinfo->invflags& IPT_INV_FRAG ? " (INV)" :"");

              return0;

       }

   return1;

}

五、连接跟踪(nf_conntrack)

 .15、1、主体结构

连接跟踪用来跟踪和记录连接状态,是netfilter的一部分,它是NAT等重要功能的基础,也是通过在挂载点注册来完成相应工作的。本章对连接跟踪的分析仍然以IPV4为例。

连接跟踪用一个tuple来标识唯一的连接,用“来源地址/来源端口+目标地址/目标端口“来表示一个tuple,同时将一个tuple分为两个方向:ORIGINALREPLYORIGINAL指的是连接发起端为”源地址/源端口“为tuple的“来源地址/来源端口”,REPLY指的是连接的”目标地址/目标端口“端的回应报文,这时”目标地址/目标端口“为tuple的源地址/源端口。

连接跟踪对报文的处理主要分为两个过程,第一个过程对于新接受到的报文,通过函数nf_conntrack_in来完成,连接跟踪根据sk_buff的信息提取出tuple的信息,判断此连接是否已记录,如果没有记录创建相应的连接;第二个过程是确认阶段,通过函数nf_conntrack_confirm来完成,这个阶段将已经确认的连接放到一个表中。下面以ipv4为例用两个流程图来表示这两个函数的处理过程。

连接跟踪根据TCP/IP协议的模型,将一个连接所要使用到的协议栈的协议分为三层协议,四层协议,和应用层协议,三层协议用数据结构nf_conntrack_l3proto来表示,四层协议用数据结构nf_conntrack_l4proto来表示,不同的应用层协议实现了不同的结构这里不做赘叙。每个协议结构体内都有对应的操作函数,在下节做详细的说明。

连接跟踪的相关信息建立以后,通过扩展网络协议栈的核心数据结构sk_buff来将相关信息存储到sk_buff中,在sk_buff中扩展了两个成员变量:structnf_conntrack   *nfctnfctinfo:3来分别表示连接及相关信息。

与前面提到的表的创建类似,连接跟踪也是创建自己的hook操作,通过hook操作来完成对连接的记录,连接状态的改变等等。IPV4在四个挂载点注册了hook操作,主要通过两个函数来完成:ipv4_conntrack_in,ipv4_confirm,这两个函数以上面提到的两个函数为基础,实际上这个函数是与具体协议相关的,而上面两个函数是从所有的协议中提炼出来的共同的处理流程。

当数据包接过挂载点时,钩子函数会调用对应的链,但调用到连接跟踪处理函数时,连接就被记录下来,下面是具体的流程:

 

  

 Netfilter源码分析--5、连接跟踪

5.2ipv4_confirm

5.2、重要数据结构

 

5.2.1nf_conn

nf_conn是连接跟踪的核心数据结构,表示一个连接,整个连接跟踪的操作过程都围绕它展开,这个结构的定义及相关的解释如下:

structnf_conn{

 

       structnf_conntrack ct_general;

 

       structnf_conntrack_tuple_hashtuplehash[IP_CT_DIR_MAX];

 

       unsignedlong status;

       

       structnf_conn *master;

       

       structtimer_list timeout;

       

       unionnf_conntrack_proto proto;

       

       structnf_ct_ext *ext;

};

nf_conn成员的status的取值及含义如下:

 

       IPS_EXPECTED

       

       IPS_SEEN_REPLY

       

       IPS_ASSURED

       

       IPS_CONFIRMED

       

       IPS_SRC_NAT

       

       IPS_DST_NAT

       

       IPS_NAT_MASK

       

       IPS_SEQ_ADJUST

       

       IPS_SRC_NAT_DONE

       IPS_DST_NAT_DONE

       

       IPS_NAT_DONE_MASK= (IPS_DST_NAT_DONE | IPS_SRC_NAT_DONE),

       

       IPS_DYING

       

       IPS_FIXED_TIMEOUT

5.2.2nf_conntrack_tuple

nf_conntrack_tuple是用来标识每个连接的数据结构,其核心就是用源和目的的IP和端口来标识这个唯一的连接,不同的传输层协议可能还需要其他的标识,因此在nf_conntrack_tuple的定义中用一个联合来表示不同协议。

structnf_conntrack_tuple

{

       structnf_conntrack_man src;

 

       

       struct{

              unionnf_inet_addr u3;

              union{

                     

                     __be16all;

                     struct{

                            __be16port;

                     }tcp;

                     struct{

                            __be16port;

                     }udp;

                     struct{

                            u_int8_ttype, code;

                     }icmp;

                     struct{

                            __be16port;

                     }dccp;

                     struct{

                            __be16port;

                     }sctp;

                     struct{

                            __be16key;

                     }gre;

              }u;

              

              u_int8_tprotonum;

 

              

              u_int8_tdir;

       }dst;

};

连接跟踪中并不是直接的存储一个tuple而是将其做hash,存储它的hash值。

5.2.3、协议相关数据结构

连接跟踪将TCP/IP协议的三,四层协议抽象为两个数据结构,不同的结构有不同函数把协议的相关的信息存储到nf_conn结构当中去,其中三层协议的抽象为:

structnf_conntrack_l3proto

{

       

       u_int16_tl3proto;

       

       constchar *name;

       

       bool(*pkt_to_tuple)(const struct sk_buff *skb, unsigned intnhoff,

                          structnf_conntrack_tuple *tuple);

       

       bool(*invert_tuple)(struct nf_conntrack_tuple*inverse,

                          conststruct nf_conntrack_tuple *orig);

       

       int(*print_tuple)(struct seq_file *s, const struct nf_conntrack_tuple*);

       

       int(*get_l4proto)(const struct sk_buff *skb, unsigned intnhoff,

                        unsignedint *dataoff, u_int8_t *protonum);

 

       int(*tuple_to_nlattr)(struct sk_buff *skb, const structnf_conntrack_tuple *t);

       

       int(*nlattr_tuple_size)(void);

       int(*nlattr_to_tuple)(struct nlattr *tb[],struct nf_conntrack_tuple*t);

       conststruct nla_policy *nla_policy;

       size_tnla_size;

       

       structmodule *me;

};

四层协议的抽象为:

structnf_conntrack_l4proto

{

       

       u_int16_tl3proto;

       

       u_int8_tl4proto;

       

       bool(*pkt_to_tuple)(const struct sk_buff *skb, unsigned intdataoff,

                          structnf_conntrack_tuple *tuple);

 

       

       bool(*invert_tuple)(struct nf_conntrack_tuple*inverse,

                          conststruct nf_conntrack_tuple *orig);

       

       int(*packet)(struct nf_conn *ct,

                    conststruct sk_buff *skb,

                    unsignedint dataoff,

                    enumip_conntrack_info ctinfo,

                    u_int8_tpf,

                    unsignedint hooknum);

 

       

       bool(*new)(struct nf_conn *ct, const struct sk_buff*skb,

                  unsignedint dataoff);

       

       void(*destroy)(struct nf_conn *ct);

       int(*error)(struct net *net, struct sk_buff *skb, unsigned intdataoff,

                   enumip_conntrack_info *ctinfo,

                   u_int8_tpf, unsigned int hooknum);

 

       

       int(*print_tuple)(struct seq_file *s,

                        conststruct nf_conntrack_tuple *);

       

       int(*print_conntrack)(struct seq_file *s, const struct nf_conn*);

              int(*to_nlattr)(struct sk_buff *skb, struct nlattr*nla,

                      conststruct nf_conn *ct);

       

       int(*nlattr_size)(void);

       

       int(*from_nlattr)(struct nlattr *tb[], struct nf_conn*ct);

       int(*tuple_to_nlattr)(struct sk_buff *skb,const structnf_conntrack_tuple *t);

       

       int(*nlattr_tuple_size)(void);

       int(*nlattr_to_tuple)(struct nlattr *tb[],struct nf_conntrack_tuple*t);

       conststruct nla_policy *nla_policy;

       size_tnla_size;

       

       constchar *name;

       

       structmodule *me;

};

连接跟踪同时也提供了对协议的注册函数,对于不同类型的协议,设置不同的成员函数,同时将对应的数据结构注册到全局的数据结构当中去,连接跟踪在进行hook操作时,会根据sk_buff提供的协议信息找到对应的结构。

5.2.4、其他数据结构

连接跟踪为每个应用成的协议通过一个helper结构,同过这个结构的成员函数应用层的协议可以做一些与自己协议相关的工作,定义如下:

structnf_conntrack_helper

{

       structhlist_node hnode; 

       constchar*name;         

       structmodule*me;        

       conststruct nf_conntrack_expect_policy*expect_policy;

       

       structnf_conntrack_tuple tuple;

       

       int(*help)(struct sk_buff *skb,unsigned int protoff,struct nf_conn*ct,

                  enumip_conntrack_info conntrackinfo);

       void(*destroy)(struct nf_conn *ct);

       int(*to_nlattr)(struct sk_buff *skb, const struct nf_conn*ct);

       unsignedint expect_class_max;

};

连接跟踪的nf_conn结构通过ext成员存储helper信息,其定义如下:

struct nf_ct_ext{

       structrcu_head rcu;

       u8offset[NF_CT_EXT_NUM];//offset数组

       u8len;//整个结构长度

       chardata[0];//可变数据区

};

nf_ct_ext可以存储三种类别的扩展,分别为:

NF_CT_EXT_HELPER,//nf_conntrack_helper

       NF_CT_EXT_NAT,//NAT相关信息

       NF_CT_EXT_ACCT,//统计信息

5.2.5、期望连接

期望连接是指对于那些活动协议而言,例如对FTP协议,FTP协议的连接分为控制连接和数据连接,对于这种类型的协议,连接跟踪认为这两次连接应该归结到一个跟踪里,这样,当FTP的控制连接建立起来后,它的数据连接就是控制连接的期望连接,而数据连接就是控制连接的master。期望连接可用如下的数据结构表示:

structnf_conntrack_expect

{

       

       structhlist_node lnode;

       

       structhlist_node hnode;

       

       structnf_conntrack_tuple tuple;

       structnf_conntrack_tuple_mask mask;

       

       void(*expectfn)(struct nf_conn *new, struct nf_conntrack_expect*this);

       

       structnf_conntrack_helper *helper;

       

       structnf_conn *master;

       

       structtimer_list timeout;

       

       atomic_tuse;

       

       unsignedint flags;

       

       unsignedint class;

       structrcu_head rcu;

};

5.3、应用举例

本节将以应用层协议FTP为例,对连接跟踪整个过程做详细的介绍,同时假定网络层的通信协议为IPV4

我们假设ftpclient端的ip为:193.168.18.133,ftp服务器端的ip地址为193.168.18.135

Client端通过命令 ftp193.168.18.135 与服务器建立连接,这时服务器的连接跟踪建立如下的跟踪:

ORIGINAL:193.168.18.133.1059->193.168.18.135.21

REPLY:193.168.18.135.21->193.168.18.133.1059

一次简单的连接跟踪建立起来。当用户在client端输入dir命令后,clientserver端发送一个PORTftp命令,服务器端的连接跟踪机制接收到这个连接后,调用ftp协议相关的help函数,这个help函数将初始化一个期望连接,并与主连接相关联,期望连接的相关tuple为:

193.168.133.XXXX->193.168.18.135.20

XXXX表示端口任意,可以用一个mask完成,当客户端发起一个数据连接时:

193.168.133.1059->1933.168.18.135.20

Server端发现此连接为期望连接,不会在重新初始化一个新的跟踪。

六、NAT

 

6.1、简介

网络地址转换源(NAT)分为(Source NATSNAT)和目的NAT(Destination NAT,DNAT)2种不同的类型。SNAT是指修改数据包的源地址(改变连接的源IP)SNAT会在数据包送出之前的最后一刻做好转工作。地址伪装 (Masquerading)SNAT的一种特殊形式。DNAT 是指修改数据包的目标地址(改变连接的目的IP)DNAT 总是在数据包进入以后立即转发,端口转发,负载平衡和透明代理都属于DNAT。下面介绍下NAT的流程:

6.1 NAT机制流程

这个流程需要说明的是,如果这是一个新的连接跟踪,那么hook操作会操作NAT表,寻找对应的规则进行处理,如果这个连接不是新的连接,即连接跟踪已经记录并且NAT已经更改过连接的源地址,那么hook操作会直接调用nf_nat_packet来修改报文的目的地址,这个不同于DNAT,它都是在SNAT中完成。也就是说netfilter中所有的NAT操作都可以通过一次NAT设置完成,也就是说NAT会自动帮我们判断另一个方向的报文。

进一步分析nf_nat_rule_find的工作流程如下:

Netfilter源码分析--6、NAT

 

6.2、重要数据结构

1、两种不同的对IP操作类型

enumnf_nat_manip_type

{

       IP_NAT_MANIP_SRC,//修改报文的源地址

       IP_NAT_MANIP_DST//修改报文的目的地址

};

1、  NAT的范围

主要是IP地址的范围和端口的范围。

structnf_nat_range

{

       

       unsignedint flags;

 

       

       __be32min_ip, max_ip;//IP地址范围

 

       

       unionnf_conntrack_man_proto min, max;//端口范围

};

2、  内嵌在nf_conn的结构

此结构体内嵌在成员变量struct nf_ct_ext *ext中。

structnf_conn_nat

{

       structhlist_node bysource;

       structnf_nat_seq seq[IP_CT_DIR_MAX];

       structnf_conn *ct;

       unionnf_conntrack_nat_help help;

#ifdefined(CONFIG_IP_NF_TARGET_MASQUERADE) || \

    defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)

       intmasq_index;

#endif

};

 

3、  协议相关结构

与连接跟踪机制类似,NAT也定义了一些与协议相关的操作。

structnf_nat_protocol

{

       

       unsignedint protonum;

       structmodule *me;

       

       bool(*manip_pkt)(struct sk_buff *skb,

                       unsignedint iphdroff,

                       conststruct nf_conntrack_tuple *tuple,

                       enumnf_nat_manip_type maniptype);

       

       bool(*in_range)(const struct nf_conntrack_tuple*tuple,

                      enumnf_nat_manip_type maniptype,

                      constunion nf_conntrack_man_proto *min,

                      constunion nf_conntrack_man_proto *max);

       

       bool(*unique_tuple)(struct nf_conntrack_tuple*tuple,

                          conststruct nf_nat_range *range,

                          enumnf_nat_manip_type maniptype,

                          conststruct nf_conn *ct);

 

       int(*range_to_nlattr)(struct sk_buff *skb,

                            conststruct nf_nat_range *range);

       int(*nlattr_to_range)(struct nlattr *tb[],

                            structnf_nat_range *range);

};

6.3、重要函数分析

 

static void

get_unique_tuple(structnf_conntrack_tuple *tuple,

               conststruct nf_conntrack_tuple *orig_tuple,

               conststruct nf_nat_range *range,

               structnf_conn *ct,

               enumnf_nat_manip_type maniptype)

{

       structnet *net = nf_ct_net(ct);

       conststruct nf_nat_protocol *proto;

       

       if(maniptype == IP_NAT_MANIP_SRC&&

           !(range->flags& IP_NAT_RANGE_PROTO_RANDOM)){

              if(find_appropriate_src(net, orig_tuple, tuple, range)){

                     pr_debug("get_unique_tuple:Found current src map\n");

                     if(!nf_nat_used_tuple(tuple, ct))

                            return;

              }

       }

 

       

       *tuple= *orig_tuple;

       find_best_ips_proto(tuple,range, ct, maniptype);

 

       

 

       rcu_read_lock();

 

       proto=__nf_nat_proto_find(orig_tuple->dst.protonum);

 

       

       if(range->flags &IP_NAT_RANGE_PROTO_RANDOM) {

              proto->unique_tuple(tuple,range, maniptype, ct);

              gotoout;

       }

 

       

       if((!(range->flags &IP_NAT_RANGE_PROTO_SPECIFIED) ||

            proto->in_range(tuple,maniptype, &range->min,&range->max))&&

           !nf_nat_used_tuple(tuple,ct))

              gotoout;

 

       

       proto->unique_tuple(tuple,range, maniptype, ct);

out:

       rcu_read_unlock();

}

6.4、应用实例

我们假设应用层的iptables有如下的NAT设置:

       iptables–t nat –A POSTROUTING –o eth0 –s192.168.0.0/24 –j SNAT–to 11.11.11.11

这条命令的意义在于将私用ip段:192.168.0.0/24,封包的源地址都改为公用ip11.11.11.11,这个IPNAT主机的ip

现在我们假设有一台私用ip为:192.168.18.135的机器用临时端口45678来访问218.247.215.23880端口。则连接跟踪建立如下的连接

ORIGINAL:192.168.18.135.45678->218.247.215.238.80

REPLY:218.247.215.238.80->192.168.18.135.45678

设定了SNAT后,我在POSTROUTING进入NAThook操作,并用SNATtarget做相应的处理,进入6.1流程的nf_nat_fn后,我们发现这个连接是一个新的连接,则遍历NAT表,发现设置了如上描述的规则,并且此私用IP符合我们的设定的私用ip范围,故对此报文做SNAT处理,在流程图6.2nf_nat_setup_info

中,我们调用get_unique_tuple获得一个唯一的tuple(符合上面iptables所设定的),我们通过find_best_ips_proto在指定的ip范围内寻找合适的ip,本例中只使用了唯一的公用IP:11.11.11.11,则合适的IP就为这个。在协议相关的成员函数unique_tuple中我们找到一个端口,同时判断新的tuple是否在连接跟中存在,如果存在,在换另一个端口,这样一个新的tuple被建立起来了他为:

REPLY:218.247.215.238.80-11.11.11.11.56789(这个端口为NAT主机上的端口)

同时我们更改连接跟踪的REPLY tuple为如上的tuple,这样连接跟踪建立的新连接如下:

ORIGINAL:192.168.18.135.45678->218.247.215.238.80

REPLY:218.247.215.238.80-11.11.11.11.56789

这个封包发送到主机218.247.215.238后,218.247.215.238后发送一个确认的数据包,连接为:

218.247.215.238.80-11.11.11.11.56789

NAT主机11.11.11.11收到这个封包后,连接跟踪发现这个连接已经被记录,不做新的记录,同时设定ctinfo为:IP_CT_RELATED+IP_CT_IS_REPLYNAT发现这个数据包的信息为这个时,直接调用nf_nat_packet,而不是去查NAT表所对应的规则,nf_nat_packet

中会有连接的相反方向,即本例中为REPLY封包,相反为ORINGAL,修改相应的报文。即将数据包中的11.11.11.11.56789再次替换成192.168.18.135.45678NAT主机将这个数据包转发给192.168.18.135,这就完成了一次SNAT操作。

0 0
原创粉丝点击