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