Chinaunix首页 | 论坛 | 博客
  • 博客访问: 66816
  • 博文数量: 36
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 0
  • 用 户 组: 普通用户
  • 注册时间: 2015-09-14 16:56
个人简介

一个迷途小小程序员

文章分类

全部博文(36)

文章存档

2016年(1)

2015年(35)

我的朋友

分类:

2015-09-15 14:58:09

LINUX内核中Netfilter Hook的使用 作者:JuKevin
 
HookLinux Netfilter中重要技术,使用hook可以轻松开发内核下的多种网络处理程序。下面简单介绍一下hook及其使用。
1.      hook相关数据结构
 
struct nf_hook_ops
{
       struct list_head list;
 
       /* User fills in from here down. */
       nf_hookfn *hook;
       struct module *owner;
       int pf;
       int hooknum;
      
       /* Hooks are ordered in ascending priority. */
       int priority;
};
 
主要成员介绍
int pf; 协议家族类型
int hooknum hook执行点,它表示在报文处理的具体什么阶段执行hook函数。
Linux有以下几种执行点:
NF_IP_PRE_ROUTING          在报文作路由以前执行;
NF_IP_FORWARD                  在报文转向另一个NIC以前执行;
NF_IP_POST_ROUTING       在报文流出以前执行;
NF_IP_LOCAL_IN                 在流入本地的报文作路由以后执行;
NF_IP_LOCAL_OUT             在本地报文做流出路由前执行。
 
nf_hookfn *hook; hook处理回调函数。其定义为:
typedef unsigned int nf_hookfn(
unsigned int hooknum,        //hook执行点
struct sk_buff **skb, //sk buffer数据
const struct net_device *in, //输入设备
const struct net_device *out, //输出设备
int (*okfn)(struct sk_buff *) //
)
 
nf_hookfn执行后需要返回以下返回值:
NF_ACCEPT  继续正常的报文处理;
NF_DROP      将报文丢弃;
NF_STOLEN  由钩子函数处理了该报文,不要再继续传送;
NF_QUEUE    将报文入队,通常交由用户程序处理;
NF_REPEAT   再次调用该钩子函数。
 
最后一个参数为hook优先级,内核定义了以下多种优先级:
enum nf_ip_hook_priorities       //include/linux/netfilter_ipv4.h
{
  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,
};
 
2.      hook注册/注销
 
注册和注销函数使用起来非常简单,我们来看一下它们的函数原型:
 
单个hook注册和注销函数
int nf_register_hook(struct nf_hook_ops *reg);         //net/netfilter/core.c
void nf_unregister_hook(struct nf_hook_ops *reg);
 
多个hook注册和注销函数
int nf_register_hooks(struct nf_hook_ops *reg, unsigned int n);
void nf_unregister_hooks(struct nf_hook_ops *reg, unsigned int n);
 
3.      一个使用hook来监听主机ICMP报文的简单内核模块程序
 
#include
#include
#include
#include
#include
#include
#include
#include
 
static unsigned int icmp_srv(unsigned int hook,
                                               struct sk_buff **pskb,
                                               const struct net_device *in,
                                               const struct net_device *out,
                                               int (*okfn)(struct sk_buff *)
                                               )
{
       //printk(KERN_INFO"hook_icmp::icmp_srv()\n");
       struct iphdr *iph = (*pskb)->nh.iph;
      
       if(iph->protocol == IPPROTO_ICMP)
       {
              printk(KERN_INFO"hook_icmp::icmp_srv: receive ICMP packet\n");
              printk(KERN_INFO"src: ");
       }
      
       return NF_ACCEPT;
}
 
static struct nf_hook_ops icmpsrv_ops =
{
       .hook = icmp_srv,
       .pf = PF_INET,
       .hooknum = NF_IP_PRE_ROUTING,
       .priority = NF_IP_PRI_FILTER -1,
};
 
static int __init init_hook_icmp(void)
{
       return nf_register_hook(&icmpsrv_ops);
}
 
static void __exit fini_hook_icmp(void)
{
       nf_unregister_hook(&icmpsrv_ops);
}
 
MODULE_LICENSE("GPL");
 
module_init(init_hook_icmp);
module_exit(fini_hook_icmp);
 
编译改模块之后,加载该模块,之后可以在DOS下用ping命令来测试。
linux中用dmesg查看,可以看到收到的icmp报文
hook_icmp::icmp_srv: receive ICMP packet
hook_icmp::icmp_srv: receive ICMP packet
hook_icmp::icmp_srv: receive ICMP packet
hook_icmp::icmp_srv: receive ICMP packet


------------------------------------------------------------------------------------
2.6内核中netfilter hook点一览

本文档的Copyleft归yfydz所有,使用GPL发布,可以自由拷贝,转载,转载时请保持文档的完整性,严禁用于任何商业用途。
msn:
来源:http://yfydz.cublog.cn

1. 5个挂接点
以下内核代码版本2.6.17.11。
1.1 PREROTING
/* net/ipv4/ip_input.c */
int ip_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev)
{
......
 return NF_HOOK(PF_INET, NF_IP_PRE_ROUTING, skb, dev, NULL,
         ip_rcv_finish);
......
}

1.2 INPUT
/* net/ipv4/ip_input.c */
int ip_local_deliver(struct sk_buff *skb)
{
......
 return NF_HOOK(PF_INET, NF_IP_LOCAL_IN, skb, skb->dev, NULL,
         ip_local_deliver_finish);
}

1.3 FORWARD
/* net/ipv4/ip_forward.c */
int ip_forward(struct sk_buff *skb)
{
......
 return NF_HOOK(PF_INET, NF_IP_FORWARD, skb, skb->dev, rt->u.dst.dev,
         ip_forward_finish);
......
}

1.4 OUTPUT
/* net/ipv4/ip_output.c */
int ip_build_and_send_pkt(struct sk_buff *skb, struct sock *sk,
     u32 saddr, u32 daddr, struct ip_options *opt)
{
......
 return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
         dst_output);
}
int ip_queue_xmit(struct sk_buff *skb, int ipfragok)
{
......
 return NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL, rt->u.dst.dev,
         dst_output);
......
}
int ip_push_pending_frames(struct sock *sk)
{
......
 /* Netfilter gets whole the not fragmented skb. */
 err = NF_HOOK(PF_INET, NF_IP_LOCAL_OUT, skb, NULL,
        skb->dst->dev, dst_output);
......
}
1.5 POSTROUTING

/* net/ipv4/ip_output.c */
int ip_output(struct sk_buff *skb)
{
 struct net_device *dev = skb->dst->dev;
 IP_INC_STATS(IPSTATS_MIB_OUTREQUESTS);
 skb->dev = dev;
 skb->protocol = htons(ETH_P_IP);
 return NF_HOOK_COND(PF_INET, NF_IP_POST_ROUTING, skb, NULL, dev,
              ip_finish_output,
       !(IPCB(skb)->flags & IPSKB_REROUTED));
}

2. 每个挂接点所挂接的hook操作

只考虑是AF_INET协议族的挂接点,以下各点的hook操作按执行顺序排序,优先级数值越小,级别越高,执行顺序越靠前。
如果用户可以通过iptables规则进行控制的处理点称为用户可控,否则为不可控。

2.1 PREREOUTING

/* net/bridge/br_netfilter.c */
// 这个hook点只丢弃skb结构中设置桥参数但没有相关桥标志的包
// 用户不可控
 { .hook = ip_sabotage_in,
   .owner = THIS_MODULE,
   .pf = PF_INET,
   .hooknum = NF_IP_PRE_ROUTING,
// 优先级最高
   .priority = NF_IP_PRI_FIRST,
        },

/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 这个hook点完成分片重组,以后处理过程中的包都是非分片包
// 直到发送出去重新分片。注意2.6重组后的分片包并不进行线性
// 化,所以逻辑上应该连在一起的两字节数据可能分属不同的页,
// 存储是不连续的
// 该点操作用户不可控
 {
  .hook  = ip_conntrack_defrag,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_PRE_ROUTING,
// 优先级为-400
  .priority = NF_IP_PRI_CONNTRACK_DEFRAG,
 },

/* net/ipv4/netfilter/iptable_raw.c */
// 这个hook点为raw表,提供对收到的数据包在连接跟踪前进行处理的手段
// 该点用户可加载iptables规则进行控制
 {
  .hook = ipt_hook,
  .pf = PF_INET,
  .hooknum = NF_IP_PRE_ROUTING,
// 优先级为-300
  .priority = NF_IP_PRI_RAW,
  .owner = THIS_MODULE,
 },

/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 这个hook点完成连接跟踪,为每个skb找到所属连接(ESTABLISHED, REPLY)
// 或新建连接(NEW, RELATED)
// 该点操作用户不可控
 {
  .hook  = ip_conntrack_in,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_PRE_ROUTING,
// 优先级为-200
  .priority = NF_IP_PRI_CONNTRACK,
 },

/* net/ipv4/netfilter/iptable_mangle.c */
// 这个hook点为mangle表,提供对收到的数据包进行修改的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_route_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_PRE_ROUTING,
// 优先级为-150
  .priority = NF_IP_PRI_MANGLE,
 },
 
/* net/ipv4/netfilter/ip_nat_standalone.c */
// 该hook点对刚收到本机的skb包进行目的NAT操作
// 用户规则可控,nat表,但规则只对NEW包进行处理,后续包自动处理
 {
  .hook  = ip_nat_in,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_PRE_ROUTING,
// 优先级为-100
  .priority = NF_IP_PRI_NAT_DST,
 },

/* net/sched/sch_ingress.c */
// 该hook点对j进入本机的skb包进行排队处理,QoS操作
// 用户不可控
static struct nf_hook_ops ing_ops = {
 .hook           = ing_hook,
 .owner  = THIS_MODULE,
 .pf             = PF_INET,
 .hooknum        = NF_IP_PRE_ROUTING,
// 优先级为1
 .priority       = NF_IP_PRI_FILTER + 1,
};
 
2.2 INPUT

/* net/ipv4/netfilter/iptable_mangle.c */
// 这个hook点为mangle表,提供对收到的数据包进行修改的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_route_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_IN,
// 优先级为-150
  .priority = NF_IP_PRI_MANGLE,
 },

/* net/ipv4/netfilter/iptable_filter.c */
// 这个hook点为filter表,提供对进入本机的数据包进行过滤的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_IN,
// 优先级为0
  .priority = NF_IP_PRI_FILTER,
 },

/* net/ipv4/netfilter/ip_nat_standalone.c */
// 对进入本机的skb包进行源NAT操作
// 用户规则可控,nat表,但规则只对NEW包进行处理,后续包自动处理
 {
  .hook  = ip_nat_fn,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_IN,
// 优先级为100
  .priority = NF_IP_PRI_NAT_SRC,
 },

/* net/ipv4/ipvs/ip_vs_core.c */
// 该hook点对进入本机的skb包均衡分配
// 用户不可控
static struct nf_hook_ops ip_vs_in_ops = {
 .hook  = ip_vs_in,
 .owner  = THIS_MODULE,
 .pf  = PF_INET,
 .hooknum        = NF_IP_LOCAL_IN,
 .priority       = 100,
};

/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 该hook点对进入本机的skb包完成对连接跟踪的help,也就是
// 多连接协议中对子连接的处理
// 用户不可控
 {
  .hook  = ip_conntrack_help,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_IN,
// 优先级为INT_MAX-2,相当低
  .priority = NF_IP_PRI_CONNTRACK_HELPER,
 },

/* net/ipv4/netfilter/ip_nat_standalone.c */
// 对进入本机的skb包进行TCP序列号调整操作,主要是因为跟踪多连接协议时
// 修改了数据包内容可能导致数据包长度发生变化,相应序列号和确认号需要
// 自动调整
// 用户规则不可控
 {
  .hook  = ip_nat_adjust,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_IN,
// 优先级为INR_MAX-1,相当低
  .priority = NF_IP_PRI_NAT_SEQ_ADJUST,
 },
 
/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 该hook点对进入本机的skb包完成最后的确认,只对NEW包处理
// 确认NEW的连接信息在当前的连接表中是不存在的
// 用户不可控
 {
  .hook  = ip_confirm,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_IN,
// 优先级为INT_MAX,最低
  .priority = NF_IP_PRI_CONNTRACK_CONFIRM,
 },

2.3 FORWARD

/* net/bridge/br_netfilter.c */
// 这个hook点对由桥网卡转发的skb包设置桥信息和物理网卡等信息
// 该函数可能会返回NF_STOP不进行后续hook点的处理
// 用户不可控
 { .hook = ip_sabotage_out,
   .owner = THIS_MODULE,
   .pf = PF_INET,
   .hooknum = NF_IP_FORWARD,
// 优先级为-175
   .priority = NF_IP_PRI_BRIDGE_SABOTAGE_FORWARD,
        },
/* net/ipv4/netfilter/iptable_mangle.c */
// 这个hook点为mangle表,提供对收到的数据包进行修改的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_route_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_FORWARD,
// 优先级为-150
  .priority = NF_IP_PRI_MANGLE,
 },

/* net/ipv4/netfilter/iptable_filter.c */
// 这个hook点为filter表,提供对转发的数据包进行过滤的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_FORWARD,
// 优先级为0
  .priority = NF_IP_PRI_FILTER,
 },

/* net/ipv4/ipvs/ip_vs_core.c */
// 该hook点对转发的skb包均衡分配前处理ICMP异常
// 用户不可控
static struct nf_hook_ops ip_vs_forward_icmp_ops = {
 .hook  = ip_vs_forward_icmp,
 .owner  = THIS_MODULE,
 .pf  = PF_INET,
 .hooknum        = NF_IP_FORWARD,
 .priority       = 99,
};
/* net/ipv4/ipvs/ip_vs_core.c */
// 该hook点对转发的skb包均衡分配
// 用户不可控
static struct nf_hook_ops ip_vs_out_ops = {
 .hook  = ip_vs_out,
 .owner  = THIS_MODULE,
 .pf  = PF_INET,
 .hooknum        = NF_IP_FORWARD,
 .priority       = 100,
};
 
3.4 OUTPUT

/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 这个hook点对自身发出的包完成分片重组,以后处理过程中的包都是非分片包
// 直到最后发送出去重新分片。注意2.6重组后的分片包并不进行线性
// 化,所以逻辑上应该连在一起的两字节数据可能分属不同的页,
// 存储是不连续的
// 该点操作用户不可控
 {
  .hook  = ip_conntrack_defrag,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_OUT,
// 优先级为-400
  .priority = NF_IP_PRI_CONNTRACK_DEFRAG,
 },

/* net/ipv4/netfilter/iptable_raw.c */
// 这个hook点为raw表,提供对本机发出数据包在连接跟踪前进行处理的手段
// 该点用户可加载iptables规则进行控制
 {
  .hook = ipt_hook,
  .pf = PF_INET,
  .hooknum = NF_IP_LOCAL_OUT,
// 优先级为-300
  .priority = NF_IP_PRI_RAW,
  .owner = THIS_MODULE,
 },

/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 这个hook点对自身发出的包完成连接跟踪,为每个skb找到所属连接
// (ESTABLISHED, REPLY)或新建连接(NEW, RELATED)
// 该点操作用户不可控
 {
  .hook  = ip_conntrack_local,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_OUT,
// 优先级为-200
  .priority = NF_IP_PRI_CONNTRACK,
 },
/* net/ipv4/netfilter/iptable_mangle.c */
// 这个hook点为mangle表,提供对收到的数据包进行修改的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_local_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_OUT,
// 优先级为-150
  .priority = NF_IP_PRI_MANGLE,
 },
 
/* net/ipv4/netfilter/ip_nat_standalone.c */
// 对本机发出的skb包进行目的NAT操作
// 用户规则可控,nat表,但规则只对NEW包进行处理,后续包自动处理
 {
  .hook  = ip_nat_local_fn,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_OUT,
// 优先级为-100
  .priority = NF_IP_PRI_NAT_DST,
 },

/* net/bridge/br_netfilter.c */
// 这个hook点对由桥网卡发出的skb包设置桥信息和物理网卡等信息
// 该函数会返回NF_STOP,提前终止检查而返回
// 用户不可控
 { .hook = ip_sabotage_out,
   .owner = THIS_MODULE,
   .pf = PF_INET,
   .hooknum = NF_IP_LOCAL_OUT,
// 优先级为-50
   .priority = NF_IP_PRI_BRIDGE_SABOTAGE_LOCAL_OUT,
         },

/* net/ipv4/netfilter/iptable_filter.c */
// 这个hook点为filter表,提供对本机发出的数据包进行过滤的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_local_out_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_LOCAL_OUT,
// 优先级为0
  .priority = NF_IP_PRI_FILTER,
 },
 

2.5 POSTROUTING

/* net/bridge/br_netfilter.c */
// 这个hook点对由桥网卡发出的skb包设置桥信息和物理网卡等信息
// 该函数会返回NF_STOP,提前终止检查而返回
// 用户不可控
 { .hook = ip_sabotage_out,
   .owner = THIS_MODULE,
   .pf = PF_INET,
   .hooknum = NF_IP_POST_ROUTING,
// 优先级最高
   .priority = NF_IP_PRI_FIRST, },

/* net/ipv4/netfilter/iptable_mangle.c */
// 这个hook点为mangle表,提供对收到的数据包进行修改的处理
// 该点用户可加载iptables规则进行控制
 {
  .hook  = ipt_route_hook,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_POST_ROUTING,
// 优先级为-150
  .priority = NF_IP_PRI_MANGLE,
 },

/* net/ipv4/ipvs/ip_vs_core.c */
// 该hook点对IPVS本身的控制包直接返回NF_STOP不进行后续hook点处理
// 用户不可控
static struct nf_hook_ops ip_vs_post_routing_ops = {
 .hook  = ip_vs_post_routing,
 .owner  = THIS_MODULE,
 .pf  = PF_INET,
 .hooknum        = NF_IP_POST_ROUTING,
// 优先级为99
 .priority       = NF_IP_PRI_NAT_SRC-1,
};

/* net/ipv4/netfilter/ip_nat_standalone.c */
// 对本机发出的skb包进行源NAT操作
// 用户规则可控,nat表,但规则只对NEW包进行处理,后续包自动处理
 {
  .hook  = ip_nat_out,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_POST_ROUTING,
// 优先级为100
  .priority = NF_IP_PRI_NAT_SRC,
 },

/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 该hook点对转发的skb包完成对连接跟踪的help,也就是
// 多连接协议中对子连接的处理
// 用户不可控
 {
  .hook  = ip_conntrack_help,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
// 优先级为INT_MAX-2,相当低
  .hooknum = NF_IP_POST_ROUTING,
  .priority = NF_IP_PRI_CONNTRACK_HELPER,
 },

/* net/ipv4/netfilter/ip_nat_standalone.c */
// 对发出本机的skb包进行TCP序列号调整操作,主要是因为跟踪多连接协议时
// 修改了数据包内容可能导致数据包长度发生变化,相应序列号和确认号需要
// 自动调整
// 用户规则不可控
 {
  .hook  = ip_nat_adjust,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_POST_ROUTING,
// 优先级为INR_MAX-1,相当低
  .priority = NF_IP_PRI_NAT_SEQ_ADJUST,
 },

/* net/ipv4/netfilter/ip_conntrack_standalone.c */
// 该hook点对进入本机的skb包完成最后的确认,只对NEW包处理
// 确认NEW的新连接信息在当前的连接表中是不存在的
// 用户不可控
 {
  .hook  = ip_confirm,
  .owner  = THIS_MODULE,
  .pf  = PF_INET,
  .hooknum = NF_IP_POST_ROUTING,
// 优先级为INT_MAX,最低
  .priority = NF_IP_PRI_CONNTRACK_CONFIRM,
 },

3. 结论

由此可见,即使内核不支持bridge, ipvs和sched,一个转发包通过netfilter时也会经过12个处理点的处理,任何一点的拒绝都会使该包丢弃,在各点的控制处理功能可以高度集 中,象流水线的各个环节一样。如果能用多核处理器能让比较费资源的点单独跑一个核,各个核的处理结果进行流水线,系统效率的提升肯定会很高,可惜这种 AMP处理还是"Mission impossible",当前的SMP处理方式只会使netfilter架构效率很低,什么时候可以把“im”去掉还要等待。

阅读(2770) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~