close

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: yfydz_no1@hotmail.com
來源: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」去掉還要等待。
 
arrow
arrow
    文章標籤
    Netfilter nf_hook_ops
    全站熱搜
    創作者介紹
    創作者 lyt0112 的頭像
    lyt0112

    小廷的部落格

    lyt0112 發表在 痞客邦 留言(0) 人氣()