ebtables hook


1 概述

netfliter框架不僅僅在ipv4中有應用,bridge,ipv4,ipv6,decnet 這四種協議中都有應用,其中ipv4中又分開了arp和ip的兩種

其實netfliter是個大的框架,在ipv4中對應的應用層工具是iptables,在bridge中對應的應用層工具是ebtables,在arp中對應的應用層工具是arptables

iptables 中有raw,filter,nat,mangle,security,5個table,

ebtables 中有broute,filter,nat,3個table,

arptables 中有filter,1個table

 

具體的可以查看源碼目錄linux/net/目錄下的ipv4,ipv6,decnet,bridge目錄下的netfilter

 

2 一些概念

2.1 三層hook函數的優先級

enum nf_ip_hook_priorities {

        NF_IP_PRI_FIRST = INT_MIN,

        NF_IP_PRI_CONNTRACK_DEFRAG = -400,

        NF_IP_PRI_RAW = -300,

        NF_IP_PRI_SELINUX_FIRST = -225,

        NF_IP_PRI_CONNTRACK = -200,

        NF_IP_PRI_MANGLE = -150,

        NF_IP_PRI_NAT_DST = -100,

        NF_IP_PRI_FILTER = 0,

        NF_IP_PRI_SECURITY = 50,

        NF_IP_PRI_NAT_SRC = 100,

        NF_IP_PRI_SELINUX_LAST = 225,

        NF_IP_PRI_CONNTRACK_HELPER = 300,

        NF_IP_PRI_CONNTRACK_CONFIRM = INT_MAX,

        NF_IP_PRI_LAST = INT_MAX,

};

 

2.2 二層hook 函數的優先級

enum nf_br_hook_priorities {

        NF_BR_PRI_FIRST = INT_MIN,

        NF_BR_PRI_NAT_DST_BRIDGED = -300,

        NF_BR_PRI_FILTER_BRIDGED = -200,

        NF_BR_PRI_BRNF = 0,        

        NF_BR_PRI_NAT_DST_OTHER = 100,

        NF_BR_PRI_FILTER_OTHER = 200,

        NF_BR_PRI_NAT_SRC = 300,

        NF_BR_PRI_LAST = INT_MAX,

}

 

2.3 hook點,hooknum,hook函數

三層(ip)有5個hooknum,分別是pre_routing,local_in,forward,local_out,post_routing

二層(bridge)有6個hooknum,分別是

pre_routing,local_in,forward,local_out,post_routing,brouting,

在頭文件./uapi/linux/netfilter_bridge.h ./uapi/linux/netfilter_ipv4.h 可看到

linux/net/netfilter 是整個netfilter框架的代碼,不同的協議下面的netfilter是調用的代碼

hook函數,就是我們自定義的那些函數,函數優先級,數值越大的,優先級越小

 

一個hook點是由協議和hooknum兩者決定的,nf_hooks[pf][hooknum],因此,協議不一樣,hooknum一樣也是不一樣的hook點的,ipv4的協議是NFPROTO_INET,bridge的協議是NFPROTO_BRIDGE,而只有同一個hook點的函數才會有優先級的問題。因此,在正常情況下,同一個數據包在某一層中只會遍歷某一種協議的hook點,是一個水平分層的問題,雖然都注冊在netfilter框架下,可是協議決定了這是一個水平的流程。當數據包上到另外一層那就是另外一層的水平。

但是有一些地方在三層的改變會影響二層的結構的,比如像ip-DNAT的,改變了三層的daddr,那么對應的二層的dmac地址也是會跟着改變的,那么這個應該在routing之前還是應該在brigding之前做呢?按道理雖然改的是三層的內容,但是這個應該在brigding之前做的,這樣在二層選擇出口的時候,才不會錯。所以其實二層中有些地方是有穿插三層的hook點的調用的,所以整個結構看起來才會不那么清晰(后面的函數分析會證實這個想法)

 

hooknum 和pf 決定了hook點,hook點上面有hook函數,根據優先級來進行hook函數的調用。

NF_HOOK 這個宏就是遍歷給定的hook點(nf_hooks[pf][hooknum])上面的所有hook函數

在整個網絡協議棧(包括二層的)上面的不同位置的NF_HOOK的作用就是遍歷不同的hook點上hook函數,這就是netfilter做的事情

 

3 數據包在網橋的流轉

3.1 接收入口函數

netif_rx

netif_receive_skb(skb)-->netif_receive_skb_internal()->__netif_receive_skb()-> __netif_receive_skb_core()

netif_rx 是上層處理函數中最接近驅動層的函數,往queue里面放skb

netif_receive_skb 是最接近上層處理函數的入口函數,在軟中斷中執行,在queue中取完skb后的處理函數

 

netif_rx 和netif_receive_skb的關系還沒有搞的很明白,兩者沒有明顯的調用關系,在驅動中兩者都有調用,

 

 __netif_receive_skb_core 是真正處理skb的函數,到底接着數據包是怎么走的,在這里判斷的

 

對於網橋的數據包,就是rx_handler = br_handle_frame,在調用這個函數之前已經調用了skb_vlan_untag把二層頭包含vlan信息的部分去掉,

並且把vlan信息記錄在skb->vlan_proto(協議),和skb->vlan_tci(優先級和id)

 

即bridge的入口函數是br_handle_frame,在br_input.c

 

br_handle_frame 主要有兩個分支有NF_HOOK的調用的,如下: 

|---link-local----      NF_HOOK(NFPROTO_BRIDGE,NF_BR_LOCAL_IN,..,br_handle_local_finish) 
|---forward--          NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, ...,br_handle_frame_finish)

 

link-local :dmac是本地鏈路地址。至於什么是本地鏈路地址,可以google,只知道在ipv6中(fe80)用得比較多,其他沒什么了解

                         

br_handle_frame_finish 這個函數對數據包的dmac進行判斷,然后走不同的處理函數.

 

dmac 的不同的,處理方式不同:

A.bridge it,如果dmac是在網橋的別的端口,復制一份幀到dmac所在的端口                    ---->br_forward 

B.flood it over all the forwarding bridge ports,如果dmac地址是網橋不知道的,就泛洪     ---->br_flood_forward

C.pass it to the higher protocol code,如果dmac是網橋的,或者網橋其中一個端口的        ---->br_pass_frame_up

D.ignore it,dmac在進來的端口的這一邊的,即dmac能在進來端口的mac地址表中找到             ---->br_forward

 

3.2 轉發

br_forward,通過should_deliver()來進行判斷,是否真的需要__br_forward 還是 ignore it,

__br_forward->NF_HOOK(NFPROTO_BRIDGE, NF_BR_FORWARD, ... skb->dev,br_forward_finish) ,

__br_forward 函數改變了skb->dev

br_forward_finish->NF_HOOK(NFPROTO_BRIDGE,NF_BR_POST_ROUTING,skb,NULL,skb->dev,br_dev_queue_push_xmit);

br_dev_queue_push_xmit->dev_queue_xmit

 

br_flood_forward->br_flood(br, skb, skb2, __br_forward, unicast)->__br_forward 

same as __br_forward

 

3.3 local_in

br_pass_frame_up->NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_IN, skb, indev,NULL,netif_receive_skb)

 

 

3.4 發送入口函數

對於二層以上的層,只有網橋這個接口,沒有其綁定的ethx了(通過路由表可知),網橋的發送函數是br_dev_xmit

在br_dev_xmit 也會根據dmac判斷是進行br_multicast_deliver,br_deliver,

還是br_flood_delver,但是最后調用都是__br_deliver

__br_deliver-> NF_HOOK(NFPROTO_BRIDGE, NF_BR_LOCAL_OUT, skb, NULL,skb->dev,br_forward_finish)

br_forward_finish->NF_HOOK(NFPROTO_BRIDGE,NF_BR_POST_ROUTING,skb,NULL,skb->dev,br_dev_queue_push_xmit);

br_dev_queue_push_xmit->dev_queue_xmit

 

 

3.5 結論

根據上面的分析,通過網橋進來的數據包會經過的hook點跟在三層的是一樣的

本地的會經過pre_routing 和local_in, 轉發的會經過pre_routing,forward,post_routing ,
而本地出去的會經過local_out,post_routing

 

 

4 二層調用三層的hook函數的實現

4.1 NF_HOOK 和NF_HOOK_THRESH的區別

NF_HOOK 封裝了NF_HOOK_THRESH ,是特殊的NF_HOOK_THRESH, 是從優先級最高的hook函數開始的

NF_HOOK_THRESH,

static inline int NF_HOOK{

         return NF_HOOK_THRESH(pf, hook, skb, in, out, okfn, INT_MIN)

 

4.2 br_netfilter.c分析

二層hook點中調用三層的hook的實現主要在linux/net/bridge/br_netfilter.c ,這個函數注冊了7個hook函數,其中5個是NFPROTO_BRIDGE協議的,2個分別是NFPROTO_IPV4,NFPROTO_IPV6的

 

NFPROTO_BRIDGE的5個函數分別是br_nf_pre_routing,br_nf_local_in,br_nf_forward_ip,

br_nf_forward_arp,br_nf_post_routing的,br_nf_forward_ip 優先級是 -1,其他優先級都是0,

NFPROTO_IPV4/6 的兩個都是在pre_routing hook點,優先級是first,hook函數都是ip_sabotage_in,這個函數的作用就是防止多次調用三層pre_routing hook點的hook函數

 

 

因此目前看到的在NFPROTO_BRIDGE協議下系統注冊了的鈎子函數的順序如下:

pre_routing  ebt_nat_in(dnat)->br_nf_pre_routing

local_in     ebt_in_hook(filter)->br_nf_local_in

forward      ebt_in_hook(filter)->br_nf_forward_ip->br_nf_forward_arp

local_out     ebt_nat_out(dnat_other)->ebt_out_hook(filter_other)

post_routing  ebt_nat_out(snat)->br_nf_post_routing(last)

 

(1). br_nf_pre_routing->NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, skb, skb->dev,NULL,br_nf_pre_routing_finish)

br_nf_pre_routing_finish->NF_HOOK_THRESH(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, skb,skb->dev, NULL,br_handle_frame_finish, 1);

到br_handle_frame_finish 就走完了pre_routing的鈎子了,其實NF_HOOK_THRESH 就是為了走完pre_routing 優先級大於1的鈎子函數

 

正常的數據包走br_hadnle_frame 進來調用了一次NF_HOOK ,執行NFPROTO_BRIDGE的pre_routing的hook點中的hook函數,當執行到      br_nf_pre_routing這個鈎子函數的時候,會先去調用一次三層的pre_routing的所有hook函數,然后再回到br_nf_pre_routing_finish

 

因為在br_nf_pre_routing 中返回值是NF_STOLEN,所以在br_handle_frame調用的

NF_HOOK(NFPROTO_BRIDGE, NF_BR_PRE_ROUTING, ...,br_handle_frame_finish),

到br_nf_pre_routing 就結束了,所以會有在br_nf_pre_routing_finish->NF_HOOK_THRESH()的過程,是為了重新接上pre_routing 后面的hook函數

 

有了這個函數br_nf_pre_routing,就可以對只經過二層的數據包做三層的dnat,

 

(2).br_nf_local_in->nothing ,

 

(3).br_nf_forward_ip->NF_HOOK(pf, NF_INET_FORWARD, skb, brnf_get_logical_dev(skb, in),parent,br_nf_forward_finish),pf=INET/INET6

bf_nf_forward_finish->NF_HOOK_THRESH(NFPROTO_BRIDGE,NF_BR_FORWARD,skb, in,skb->dev, br_forward_finish, 1);

這里主要是經過了3層的forward hook點,就是經過二層走的數據包可以在三層的forward鏈做過濾,主要是結合physdev模塊做indev和outdev的過濾。繼續NF_HOOK_THRESH的時候,會走到優先級是1的hook函數那里,跳過了br_nf_forward_arp,因為一個skb->protocol,只能是一種,不可能既是ip,也是arp,既然在br_nf_forward_ip中能走到br_nf_forward_finish就證明這是個ip包了,如果不是ip包,在一開始就會返回NF_ACCEPT,讓其繼續走原來的遍歷順序

 

br_nf_forward_arp->NF_HOOK(NFPROTO_ARP,NF_ARP_FORWARD,skb, (struct net_device*)in,(struct net_device *)out,br_nf_forward_finish);

這個就在ARP的forward鏈上做過濾

 

注意兩個NF_HOOK中傳進去的indev和outdev 的區別,不一樣的

 

(4).br_nf_post_routing->NF_HOOK(pf, NF_INET_POST_ROUTING, skb, NULL,realoutdev,br_nf_dev_queue_xmit)

注意br_nf_post_routing 的優先級是last,

在post_routing中也先判斷,數據包是否是經過bridge的了,如果是從

ip/local_out->bridge/local_out,或者直接bridge/local_out的數據包都沒有必要再經過一次ip/post_routing,即只有經過bridge轉發的包,
才需要經過ip/post_routing

 

4.3 防止多次調用三層hook點的hook函數

ip_sabotage_in 在NFPROTO_IPV4/6的pre_routing 的first,如果是從網橋上來到三層的數據包,其實三層的pre_routing已經做過了,這個函數

就是控制如果是從網橋上來的數據包就返回NF_STOP ,停止這個hook點的后續hook函數的檢查,並且接受數據包(防止兩次走過三層的pre_routing),如果不是從網橋上來的包,就返回NF_ACCEP ,繼續做這個hook點的hook函數的檢查的

 

 

根據4.2 可知,在二層只有pre_routing,forward,post_routing 三個hook點會調用到三層對應hook點的hook函數,而只有經過
bridge/pre_routing->bridge/local_in->ip/pre_routing這樣路徑進來的數據包才需要在ip/pre_routing的位置判斷是否是網橋上來的包,如果是網橋上來的就不再需要遍歷這個hook點剩下的hook函數了.其他的路徑,都不可能同時經過二層和三層的同一個hook點,

所以只需要在ip/pre_routing的first的位置注冊ip_sabotage_in,就可以了,ip/forward,ip/post_routing 都不需要

 

5 brouting hook點

brouting的調用不是通過NF_HOOK 這種傳統的方式來進行的,而且系統沒有通過nf_register_hooks 這種方式注冊對應的hook函數,

只是把一個函數賦值給了一個在br_input.c 中定義的br_should_route_hook_t

 *br_should_route_hook 這個變量

然后通過這個變量來進行函數的調用,真正的函數是net/bridge/netfilter/ebtable_broute.c 中的ebt_broute

 

 

ebtable 有三個表,分別是

broute:系統沒有注冊有hook函數,允許注冊的hook點只有一個就是brouting

nat:pre_routing(dnat),post_routing(snat),local_out(dnat_other)

filter:local_in,forward,local_out(other)

 

STP 最小生成樹協議的5中狀態

#define BR_STATE_DISABLED 0           

#define BR_STATE_LISTENING 1          

#define BR_STATE_LEARNING 2          

#define BR_STATE_FORWARDING 3        

#define BR_STATE_BLOCKING 4

 

 

DISABLE:   什么功能都沒有,只有一個邏輯設備。

LISTENING: 可以接收和發送網絡傳輸的BPDU,包括Configureation BPDU和TCN BPDU,但不能進行數據幀的轉發、不能學習。

LEARNING:  可以接收和發送BPDU,可以學習,但是不能進行數據幀的轉發。

FORWARDING:可以接收和發送BPDU、可以學習、可以進行數據幀的轉發。

BLOCKING:  只能接收BPDU,不能發送BPDU,不能學習,不能轉發數據幀。

 

至於什么是BPDU 這個可以去看看linux-bridge的最小生成樹的相關知識

 

在br_handle_frame函數的forward 標簽下,

如果p->state 是FORWARDING的才會調用到brouting的hook點的唯一的hook函數ebt_broute,這個是在pre_routing 的調用之前的,
這里是以調用函數的方式來做ebtable的規則的,而不是遍歷hook點上面的hook函數來做ebtables上面的規則的,因此,如果想自定義
hook函數,估計要改源碼,即brouting這個hook點,只提供了用戶接口,沒有提供開發接口

 

至於p->state (端口狀態)是什么時候進行狀態轉換的?還不清楚

網卡新建為一個網橋的端口的時候狀態是BR_STATE_DISABLED,

 

6 結論

經過二層的數據包會經過的hook點如下:

不知道怎樣把大圖弄上CU,只能用viso畫了,然后截圖上去了,有點模糊

還有一篇從ebtables的使用角度分析的文章,個人感覺不錯的,也貼在這里了
http://ebtables.netfilter.org/br_fw_ia/br_fw_ia.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM