IP包的处理模式

来源:百度文库 编辑:神马文学网 时间:2024/04/29 12:08:26
 2.1 LVS对 IP包的处理模式

  IP包处理用Linux 2.4内核的Netfilter框架完成。一个数据包通过Netfilter框架的过程如图所示:

  通俗的说,netfilter的架构就是在整个网络流程的若干位置放置了一些检测点(HOOK),而在每个检测点上上登记了一些处理函数进行处理(如包过滤,NAT等,甚至可以是用户自定义的功能)。

  1. NF_IP_PRE_ROUTING:刚刚进入网络层的数据包通过此点(刚刚进行完版本号,校验和等检测),源地址转换在此点进行;
  2. NF_IP_LOCAL_IN:经路由查找后,送往本机的通过此检查点,INPUT包过滤在此点进行;
  3. NF_IP_FORWARD:要转发的包通过此检测点,FORWORD包过滤在此点进行;
  4. NF_IP_LOCAL_OUT:本机进程发出的包通过此检测点,OUTPUT包过滤在此点进行;
  5. NF_IP_POST_ROUTING:所有马上便要通过网络设备出去的包通过此检测点,内置的目的地址转换功能(包括地址伪装)在此点进行。

 

  在IP层代码中,有一些带有NF_HOOK宏的语句,如IP的转发函数中有:

<-ipforward.c ip_forward()->            NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, dev2,ip_forward_finish);            //其中NF_HOOK宏的定义基本如下:            <-/include/linux/netfilter.h->            #ifdef CONFIG_NETFILTER            #define NF_HOOK(pf, hook, skb, indev, outdev, okfn)            (list_empty(&nf_hooks[(pf)][(hook)])            ? (okfn)(skb)            : nf_hook_slow((pf), (hook), (skb), (indev), (outdev), (okfn)))            #else /* !CONFIG_NETFILTER */            #define NF_HOOK(pf, hook, skb, indev, outdev, okfn) (okfn)(skb)            #endif /*CONFIG_NETFILTER*/ 

  如果在编译内核时没有配置netfilter时,就相当于调用最后一个参数,此例中即执行ip_forward_finish函数;否则进入HOOK点,执行通过nf_register_hook()登记的功能(这句话表达的可能比较含糊,实际是进入nf_hook_slow()函数,再由它执行登记的函数)。

  NF_HOOK宏的参数分别为:

  1. pf:协议族名,netfilter架构同样可以用于IP层之外,因此这个变量还可以有诸如PF_INET6,PF_DECnet等名字。
  2. hook:HOOK点的名字,对于IP层,就是取上面的五个值;
  3. skb:顾名思义
  4. indev:进来的设备,以struct net_device结构表示;
  5. outdev:出去的设备,以struct net_device结构表示;
  6. okfn:是个函数指针,当所有的该HOOK点的所有登记函数调用完后,转而走此流程。

 

  这些点是已经在内核中定义好的,除非你是这部分内核代码的维护者,否则无权增加或修改,而在此检测点进行的处理,则可由用户指定。像packet filter,NAT,connection track这些功能,也是以这种方式提供的。正如netfilter的当初的设计目标--提供一个完善灵活的框架,为扩展功能提供方便。

  如果我们想加入自己的代码,便要用nf_register_hook函数,其函数原型为:

int nf_register_hook(struct nf_hook_ops *reg)            struct nf_hook_ops://结构            struct nf_hook_ops            {            struct list_head list;            /* User fills in from here down. */            nf_hookfn *hook;            int pf;            int hooknum;            /* Hooks are ordered in ascending priority. */            int priority;            };

  其实,类似LVS的做法就是生成一个struct nf_hook_ops结构的实例,并用nf_register_hook将其HOOK上。其中list项要初始化为{NULL,NULL};由于一般在IP层工作,pf总是PF_INET;hooknum就是HOOK点;一个HOOK点可能挂多个处理函数,谁先谁后,便要看优先级,即priority的指定了。netfilter_ipv4.h中用一个枚举类型指定了内置的处理函数的优先级:

enum nf_ip_hook_priorities {            NF_IP_PRI_FIRST = INT_MIN,            NF_IP_PRI_CONNTRACK = -200,            NF_IP_PRI_MANGLE = -150,            NF_IP_PRI_NAT_DST = -100,            NF_IP_PRI_FILTER = 0,            NF_IP_PRI_NAT_SRC = 100,            NF_IP_PRI_LAST = INT_MAX,            };

  hook是提供的处理函数,也就是我们的主要工作,其原型为:

unsigned int nf_hookfn(unsigned int hooknum,            struct sk_buff **skb,            const struct net_device *in,            const struct net_device *out,            int (*okfn)(struct sk_buff *));

  它的五个参数将由NFHOOK宏传进去。

  以上是NetFillter编写自己模块时的一些基本用法,接下来,我们来看一下LVS中是如何实现的。  3、LVS中Netfiler的实现

 

  利用Netfilter,LVS处理数据报从左边进入系统,进行IP校验以后,数据报经过第一个钩子函数NF_IP_PRE_ROUTING[HOOK1]进行处理;然后进行路由选择,决定该数据报是需要转发还是发给本机;若该数据报是发被本机的,则该数据经过钩子函数NF_IP_LOCAL_IN[HOOK2]处理后传递给上层协议;若该数据报应该被转发,则它被NF_IP_FORWARD[HOOK3]处理;经过转发的数据报经过最后一个钩子函数NF_IP_POST_ROUTING[HOOK4]处理以后,再传输到网络上。本地产生的数据经过钩子函数NF_IP_LOCAL_OUT[HOOK5]处理后,进行路由选择处理,然后经过NF_IP_POST_ROUTING[HOOK4]处理后发送到网络上。

  当启动IPVS加载ip_vs模块时,模块的初始化函数ip_vs_init( )注册了NF_IP_LOCAL_IN[HOOK2]、NF_IP_FORWARD[HOOK3]、NF_IP_POST_ROUTING[HOOK4] 钩子函数用于处理进出的数据报。

  3.1 NF_IP_LOCAL_IN处理过程

  用户向虚拟服务器发起请求,数据报经过NF_IP_LOCAL_IN[HOOK2],进入ip_vs_in( )进行处理。如果传入的是icmp数据报,则调用ip_vs_in_icmp( );否则继续判断是否为tcp/udp数据报,如果不是tcp/udp数据报,则函数返回NF_ACCEPT(让内核继续处理该数据报);余下情况便是处理tcp/udp数据报。首先,调用ip_vs_header_check( )检查报头,如果异常,则函数返回NF_DROP(丢弃该数据报)。接着,调用ip_vs_conn_in_get( )去ip_vs_conn_tab表中查找是否存在这样的连接:它的客户机和虚拟服务器的ip地址和端口号以及协议类型均与数据报中的相应信息一致。如果不存在相应连接,则意味着连接尚未建立,此时如果数据报为tcp的sync报文或udp数据报则查找相应的虚拟服务器;如果相应虚拟服务器存在但是已经满负荷,则返回NF_DROP;如果相应虚拟服务器存在并且未满负荷,那么调用ip_vs_schedule( )调度一个RS并创建一个新的连接,如果调度失败则调用ip_vs_leave( )继续传递或者丢弃数据报。如果存在相应连接,首先判断连接上的RS是否可用,如果不可用则处理相关信息后返回NF_DROP。找到已存在的连接或建立新的连接后,修改系统记录的相关信息如传入的数据报的个数等。如果这个连接在创建时绑定了特定的数据报传输函数,调用这个函数传输数据报,否则返回NF_ACCEPT。

  ip_vs_in()调用的ip_vs_in_icmp( )处理icmp报文。函数开始时检查数据报的长度,如果异常则返回NF_DROP。函数只处理由tcp/udp报文传送错误引起的目的不可达、源端被关闭或超时的icmp报文,其他情况则让内核处理。针对上述三类报文,首先检查检验和。如果检验和错误,直接返回NF_DROP;否则,分析返回的icmp差错信息,查找相应的连接是否存在。如果连接不存在,返回NF_ACCEPT;如果连接存在,根据连接信息,依次修改差错信息包头的ip地址与端口号及ICMP数据报包头的ip地址,并重新计算和修改各个包头中的检验和,之后查找路由调用ip_send( )发送修改过的数据报,并返回NF_STOLEN(退出数据报的处理过程)。

  ip_vs_in()调用的函数ip_vs_schedule( )为虚拟服务器调度可用的RS并建立相应连接。它将根据虚拟服务器绑定的调度算法分配一个RS,如果成功,则调用ip_vs_conn_new( )建立连接。ip_vs_conn_new( )将进行一系列初始化操作:设置连接的协议、ip地址、端口号、协议超时信息,绑定application helper、RS和数据报传输函数,最后调用ip_vs_conn_hash( )将这个连接插入哈希表ip_vs_conn_tab中。一个连接绑定的数据报传输函数,依据IPVS工作方式可分为ip_vs_nat_xmit( )、ip_vs_tunnel_xmit( )、ip_vs_dr_xmit( )。例如ip_vs_nat_xmit( )的主要操作是:修改报文的目的地址和目的端口为RS信息,重新计算并设置检验和,调用ip_send( )发送修改后的数据报。

  3.2 NF_IP_FORWARD处理过程

  数据报进入NF_IP_FORWARD后,将进入ip_vs_out( )进行处理。这个函数只在NAT方式下被调用。它首先判断数据报类型,如果为icmp数据报则直接调用ip_vs_out_icmp( );其次判断是否为tcp/udp数据报,如果不是这二者则返回NF_ACCEPT。余下就是tcp/udp数据报的处理。首先,调用ip_vs_header_check( )检查报头,如果异常则返回NF_DROP。其次,调用ip_vs_conn_out_get( )判断是否存在相应的连接。若不存在相应连接:调用ip_vs_lookup_real_service( )去哈希表中查找发送数据报的RS是否仍然存在,如果RS存在且报文是tcp非复位报文或udp 报文,则调用icmp_send( )给RS发送目的不可达icmp报文并返回NF_STOLEN;其余情况下均返回NF_ACCEPT。若存在相应连接:检查数据报的检验和,如果错误则返回NF_DROP,如果正确,修改数据报,将源地址修改为虚拟服务器ip地址,源端口修改为虚拟服务器端口号,重新计算并设置检验和,并返回NF_ACCEPT。

  ip_vs_out_icmp( )的流程与ip_vs_in_icmp( )类似,只是修改数据报时有所区别:ip报头的源地址和差错信息中udp或tcp报头的目的地址均修改为虚拟服务器地址,差错信息中udp或tcp报头的目的端口号修改为虚拟服务器的端口号。

  3.3 NF_IP_POST_ROUTING处理过程

  NF_IP_POST_ROUTING钩子函数只在NAT方式下使用。数据报进入NF_IP_POST_ROUTING后,由ip_vs_post_routing( )进行处理。它首先判断数据报是否经过IPVS,如果未经过则返回NF_ACCEPT;否则立刻传输数据报,函数返回NF_STOLEN,防止数据报被iptable的规则修改。