看到一篇講Netfilter框架的,若是有一點基礎了的話對於捋清整個框架很好幫助,轉下來細細閱讀。linux
轉自http://aichundi.blog.163.com/blog/static/7013846220084910397396/數組
#####緩存
1、概述網絡
1. Netfilter/IPTables框架簡介數據結構
Netfilter/IPTables是繼2.0.x的IPfwadm、2.2.x的IPchains以後,新一代的Linux防火牆機制。Netfilter採用模塊化設計,具備良好的可擴充性。其重要工具模塊IPTables鏈接到Netfilter的架構中,並容許使用者對數據報進行過濾、地址轉換、處理等操做。架構
Netfilter提供了一個框架,將對網絡代碼的直接干涉降到最低,並容許用規定的接口將其餘包處理代碼以模塊的形式添加到內核中,具備極強的靈活性。app
2. 主要源代碼文件框架
Linux內核版本:2.4.21異步
Netfilter主文件:net/core/netfilter.csocket
Netfilter主頭文件:include/linux/netfilter.h
IPv4相關:
c文件:net/ipv4/netfilter/*.c
頭文件:include/linux/netfilter_ipv4.h
include/linux/netfilter_ipv4/*.h
IPv4協議棧主體的部分c文件,特別是與數據報傳送過程有關的部分:
ip_input.c,ip_forward.c,ip_output.c,ip_fragment.c等
2、Netfilter/IPTables-IPv4整體架構
Netfilter主要經過表、鏈實現規則,能夠這麼說,Netfilter是表的容器,表是鏈的容器,鏈是規則的容器,最終造成對數據報處理規則的實現。
詳細地說,Netfilter/IPTables的體系結構能夠分爲三個大部分:
1. Netfilter的HOOK機制
Netfilter的通用框架不依賴於具體的協議,而是爲每種網絡協議定義一套HOOK函數。這些HOOK函數在數據報通過協議棧的幾個關鍵點時被調用,在這幾個點中,協議棧將數據報及HOOK函數標號做爲參數,傳遞給Netfilter框架。
對於它在網絡堆棧中增長的這些HOOK,內核的任何模塊能夠對每種協議的一個或多個HOOK進行註冊,實現掛接。這樣當某個數據報被傳遞給Netfilter框架時,內核能檢測到是否有任何模塊對該協議和HOOK函數進行了註冊。若註冊了,則調用該模塊的註冊時使用的回調函數,這樣這些模塊就有機會檢查、修改、丟棄該數據報及指示Netfilter將該數據報傳入用戶空間的隊列。
這樣,HOOK提供了一種方便的機制:在數據報經過Linux內核的不一樣位置上截獲和操做處理數據報。
2. IPTables基礎模塊
IPTables基礎模塊實現了三個表來篩選各類數據報,具體地講,Linux2.4內核提供的這三種數據報的處理功能是相互間獨立的模塊,都基於Netfilter的HOOK函數和各類表、鏈實現。這三個表包括:filter表,nat表以及mangle表。
3. 具體功能模塊
數據報過濾模塊
鏈接跟蹤模塊(Conntrack)
網絡地址轉換模塊(NAT)
數據報修改模塊(mangle)
其它高級功能模塊
因而,Netfilter/IPTables整體架構如圖http://blog.chinaunix.net/photo/24896_061206192251.jpg所示
3、HOOK的實現
1. Netfilter-IPv4中的HOOK
Netfilter模塊須要使用HOOK來啓用函數的動態鉤接,它在IPv4中定義了五個HOOK(位於文件include/linux/netfilter_ipv4.h,Line 39),分別對應0-4的hooknum
簡單地說,數據報通過各個HOOK的流程以下:
數據報從進入系統,進行IP校驗之後,首先通過第一個HOOK函數NF_IP_PRE_ROUTING進行處理;而後就進入路由代碼,其決定該數據報是須要轉發仍是發給本機的;若該數據報是發被本機的,則該數據通過HOOK函數NF_IP_LOCAL_IN處理之後而後傳遞給上層協議;若該數據報應該被轉發則它被NF_IP_FORWARD處理;通過轉發的數據報通過最後一個HOOK函數NF_IP_POST_ROUTING處理之後,再傳輸到網絡上。本地產生的數據通過HOOK函數NF_IP_LOCAL_OUT 處理後,進行路由選擇處理,而後通過NF_IP_POST_ROUTING處理後發送出去。
總之,這五個HOOK所組成的Netfilter-IPv4數據報篩選體系如圖http://blog.chinaunix.net/photo/24896_061206192311.jpg: (注:下面所說Netfilter/IPTables均基於IPv4,再也不贅述)
詳細地說,各個HOOK及其在IP數據報傳遞中的具體位置如圖http://blog.chinaunix.net/photo/24896_061206192340.jpg
NF_IP_PRE_ROUTING (0)
數據報在進入路由代碼被處理以前,數據報在IP數據報接收函數ip_rcv()(位於net/ipv4/ip_input.c,Line379)的最後,也就是在傳入的數據報被處理以前通過這個HOOK。在ip_rcv()中掛接這個HOOK以前,進行的是一些與類型、長度、版本有關的檢查。
通過這個HOOK處理以後,數據報進入ip_rcv_finish()(位於net/ipv4/ip_input.c,Line306),進行查路由表的工做,並判斷該數據報是發給本地機器仍是進行轉發。
在這個HOOK上主要是對數據報做報頭檢測處理,以捕獲異常狀況。
涉及功能(優先級順序):Conntrack(-200)、mangle(-150)、DNAT(-100)
NF_IP_LOCAL_IN (1)
目的地爲本地主機的數據報在IP數據報本地投遞函數ip_local_deliver()(位於net/ipv4/ip_input.c,Line290)的最後通過這個HOOK。
通過這個HOOK處理以後,數據報進入ip_local_deliver_finish()(位於net/ipv4/ip_input.c,Line219)
這樣,IPTables模塊就能夠利用這個HOOK對應的INPUT規則鏈表來對數據報進行規則匹配的篩選了。防火牆通常創建在這個HOOK上。
涉及功能:mangle(-150)、filter(0)、SNAT(100)、Conntrack(INT_MAX-1)
NF_IP_FORWARD (2)
目的地非本地主機的數據報,包括被NAT修改過地址的數據報,都要在IP數據報轉發函數ip_forward()(位於net/ipv4/ip_forward.c,Line73)的最後通過這個HOOK。
通過這個HOOK處理以後,數據報進入ip_forward_finish()(位於net/ipv4/ip_forward.c,Line44)
另外,在net/ipv4/ipmr.c中的ipmr_queue_xmit()函數(Line1119)最後也會通過這個HOOK。(ipmr爲多播相關,估計是在須要經過路由轉發多播數據時的處理)
這樣,IPTables模塊就能夠利用這個HOOK對應的FORWARD規則鏈表來對數據報進行規則匹配的篩選了。
涉及功能:mangle(-150)、filter(0)
NF_IP_LOCAL_OUT (3)
本地主機發出的數據報在IP數據報構建/發送函數ip_queue_xmit()(位於net/ipv4/ip_output.c,Line339)、以及ip_build_and_send_pkt()(位於net/ipv4/ip_output.c,Line122)的最後通過這個HOOK。(在數據報處理中,前者最爲經常使用,後者用於那些不傳輸有效數據的SYN/ACK包)
通過這個HOOK處理後,數據報進入ip_queue_xmit2()(位於net/ipv4/ip_output.c,Line281)
另外,在ip_build_xmit_slow()(位於net/ipv4/ip_output.c,Line429)和ip_build_xmit()(位於net/ipv4/ip_output.c,Line638)中用於進行錯誤檢測;在igmp_send_report()(位於net/ipv4/igmp.c,Line195)的最後也通過了這個HOOK,進行多播時相關的處理。
這樣,IPTables模塊就能夠利用這個HOOK對應的OUTPUT規則鏈表來對數據報進行規則匹配的篩選了。
涉及功能:Conntrack(-200)、mangle(-150)、DNAT(-100)、filter(0)
NF_IP_POST_ROUTING (4)
全部數據報,包括源地址爲本地主機和非本地主機的,在經過網絡設備離開本地主機以前,在IP數據報發送函數ip_finish_output()(位於net/ipv4/ip_output.c,Line184)的最後通過這個HOOK。
通過這個HOOK處理後,數據報進入ip_finish_output2()(位於net/ipv4/ip_output.c,Line160)另外,在函數ip_mc_output()(位於net/ipv4/ip_output.c,Line195)中在克隆新的網絡緩存skb時,也通過了這個HOOK進行處理。
涉及功能:mangle(-150)、SNAT(100)、Conntrack(INT_MAX)
其中,入口爲net_rx_action()(位於net/core/dev.c,Line1602),做用是將數據報一個個地從CPU的輸入隊列中拿出,而後傳遞給協議處理例程。
出口爲dev_queue_xmit()(位於net/core/dev.c,Line1035),這個函數被高層協議的實例使用,以數據結構struct sk_buff *skb的形式在網絡設備上發送數據報。
2. HOOK的調用
HOOK的調用是經過宏NF_HOOK實現的,其定義位於include/linux/Netfilter.h,Line122:
#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)))
這裏先調用list_empty函數檢查HOOK點存儲數組nf_hooks是否爲空,爲空則表示沒有HOOK註冊,則直接調用okfn繼續處理。若是不爲空,則轉入nf_hook_slow()函數。
nf_hook_slow()函數(位於net/core/netfilter.c,Line449)的工做主要是讀nf_hook數組遍歷全部的nf_hook_ops結構,並調用nf_hookfn()處理各個數據報。
即HOOK的調用過程如圖http://blog.chinaunix.net/photo/24896_061206192356.jpg所示
下面說明一下NF_HOOK的各個參數:
pf:協議族標識,相關的有效協議族列表位於include/linux/socket.h,Line 178。對於IPv4,應該使用協議族PF_INET;
hook:HOOK標識,即前面所說5個HOOK對應的hooknum;
skb:是含有須要被處理包的sk_buuff數據結構的指針。sk_buff是Linux網絡緩存,指那些linux內核處理IP分組報文的緩存,即套接字緩衝區。
網卡收到IP分組報文後,將它們放入sk_buff,而後再傳送給網絡堆棧,網絡堆棧幾乎一直要用到sk_buff。其定義在include/linux/skbuff.h,Line 129,下面列出我認爲對分析有意義的部分紅員:
`struct sock *sk;`:指向建立分組報文的socket;
`struct timeval stamp;`:分組報文到達系統的時間;
下面是三個union,存放的是各層中各類協議的報文頭指針:
h對應傳輸層的報頭
nh對應網絡層的報頭
mac對應MAC層的報頭
`unsigned int len;`:套接字緩存所表明的報文長度,即從`unsigned char *data;`的位置算起的當前有效報文長度。
`unsigned char pkt_type,`:表示報文的類型,具體類型定義在include/linux/if_packet.h,Line24:
#define PACKET_HOST 0 // 發送到本機的報文
#define PACKET_BROADCAST 1 // 廣播報文
#define PACKET_MULTICAST 2 // 多播報文
#define PACKET_OTHERHOST 3 // 表示目的地非本機但被本機 接收的報文
#define PACKET_OUTGOING 4 // 離開本機的報文
/* These ones are invisible by user level */
#define PACKET_LOOPBACK 5 // 本機發給本身的報文
#define PACKET_FASTROUTE 6 // 快速路由報文
indev:輸入設備,收到數據報的網絡設備的net_device數據結構指針,即數據報到達的接口。
用於NF_IP_PRE_ROUTING和NF_IP_LOCAL_IN兩個HOOK
outdev:輸出設備,數據報離開本地所要使用的網絡設備的net_device數據結構指針。
用於NF_IP_LOCAL_OUT和NF_IP_POST_ROUTING兩個HOOK
注意:在一般狀況下,在一次HOOK調用中,indev和outdev中只有一個參數會被使用
okfn:下一步要處理的函數。即若是有HOOK函數,則處理完全部的HOOK函數,且全部向該HOOK註冊過的篩選函數都返回NF_ACCEPT時,調用這個函數繼續處理;若是沒有註冊任何HOOK,則直接調用此函數。其5個參數將由宏NF_HOOK傳入。
3. HOOK點的實現
對應於各個不一樣協議的不一樣HOOK點是由一個二維數組nf_hooks存儲的(位於net/core/netfilter.c,Line 47),具體的HOOK點則由數據結構nf_hook_ops(位於include/linux/netfilter.h,Line 44)實現。如圖http://blog.chinaunix.net/photo/24896_061206192528.jpg所示:
其中,nf_hook_ops成員中:
`int priority;` priority值越小,優先級越高,相關優先級在include/linux/netfilter_ipv4.h,Line52中枚舉定義:
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,
};
`nf_hookfn *hook;` 爲處理函數的指針,其函數指針類型定義位於include/linux/netfilter.h,Line38,爲:
typedef 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 *));
這是nf_hook_ops中最關鍵的成員,其五個參數分別對應前面所解釋的NF_HOOK中弟2到6個參數
調用HOOK的包篩選函數必須返回特定的值,這些值以宏的形式定義於頭文件include/linux/netfilter.h中(Line15),分別爲:
NF_DROP(0):丟棄此數據報,禁止包繼續傳遞,不進入此後的處理流程;
NF_ACCEPT(1):接收此數據報,容許包繼續傳遞,直至傳遞到鏈表最後,而進入okfn函數;
以上兩個返回值最爲常見
NF_STOLEN(2):數據報被篩選函數截獲,禁止包繼續傳遞,但並不釋放數據報的資源,這個數據報及其佔有的sk_buff仍然有效(e.g. 將分片的數據報一一截獲,而後將其裝配起來再進行其餘處理);
NF_QUEQUE(3):將數據報加入用戶空間隊列,使用戶空間的程序能夠直接進行處理;
在nf_hook_slow()以及nf_reinject()函數(位於net/core/netfilter.c,Line449,Line505)中,當由調用nf_iterate()函數(位於net/core/netfilter.c,Line339,做用爲遍歷全部註冊的HOOK函數,並返回相應的NF_XX值)而返回的verdict值爲NF_QUEUE時(即當前正在執行的這個HOOK篩選函數要求將數據報加入用戶空間隊列),會調用nf_queue()函數(位於net/core/netfilter.c,Line407)
nf_queue()函數將這個數據報加入用戶空間隊列nf_info(位於include/linux/netfilter.h,Line77),並保存其設備信息以備用
NF_REPEAT(4):再次調用當前這個HOOK的篩選函數,進行重複處理。
4. HOOK的註冊和註銷
HOOK的註冊和註銷分別是經過nf_register_hook()函數和nf_unregister_hook()函數(分別位於net/core/netfilter.c,Line60,76)實現的,其參數均爲一個nf_hook_ops結構,兩者的實現也很是簡單。
nf_register_hook()的工做是首先遍歷nf_hools[][],由HOOK的優先級肯定在HOOK鏈表中的位置,而後根據優先級將該HOOK的nf_hook_ops加入鏈表;
nf_unregister_hook()的工做更加簡單,其實就是將該HOOK的nf_hook_ops從鏈表中刪除。
4、IPTables系統
1. 表-規則系統
IPTables是基於Netfilter基本架構實現的一個可擴展的數據報高級管理系統,利用table、chain、rule三級來存儲數據報的各類規則。系統預約義了三個table:
filter:數據報過濾表(文件net/ipv4/netfilter/iptable_filter.c)
監聽NF_IP_LOCAL_IN、NF_IP_FORWARD和NF_IP_LOCAL_OUT三個HOOK,做用是在全部數據報傳遞的關鍵點上對其進行過濾。
nat:網絡地址轉換表
監聽NF_IP_PRE_ROUTING、NF_IP_POST_ROUTING和NF_IP_LOCAL_OUT三個HOOK,做用是當新鏈接的第一個數據報通過時,在nat表中決定對其的轉換操做;然後面的其它數據報都將根據第一個數據報的結果進行相同的轉換處理。
mangle:數據報修改表(位於net/ipv4/netfilter/iptable_mangle.c)
監聽NF_IP_PRE_ROUTING和NF_IP_LOCAL_OUT兩個HOOK,做用是修改數據報報頭中的一些值。
2. 表的實現
表的基本數據結構是ipt_table(位於include/linux/netfilter_ipv4/ip_tables.h,Line413):
struct ipt_table
{
struct list_head list; // 一個雙向鏈表
char name[IPT_TABLE_MAXNAMELEN]; // 被用戶空間使用的表函數的名字
struct ipt_replace *table; // 表初始化的模板,定義了一個初始化用的該 // 表的所默認的HOOK所包含的規則等信息,
// 用戶經過系統調用進行表的替換時也要用
unsigned int valid_hooks; // 表所監聽的HOOK,實質是一個位圖
rwlock_t lock; // 整個表的讀/寫自旋鎖
struct ipt_table_info *private; // 表所存儲的數據信息,也就是實際的數據區,
// 僅在處理ipt_table的代碼內部使用
struct module *me; // 若是是模塊,那麼取THIS_MODULE,不然取NULL
};
其中:
`unsigned int valid_hooks;`這個位圖有兩個做用:一是檢查Netfilter中哪些HOOK對應着合法的entries;二是用來爲ipt_match以及ipt_target數據結構中的checkentry()函數覈算可能的HOOK。
`struct module *me;`當取值爲THIS_MODULE時,能夠阻止用戶rmmod一個仍然被某個規則指向的模塊的嘗試。
`struct ipt_replace *table;`的數據結構是被用戶空間用來替換一個表的,其定義位於include/linux/netfilter_ipv4/ip_tables.h,Line230:
struct ipt_replace
{
char name[IPT_TABLE_MAXNAMELEN];
unsigned int valid_hooks;
unsigned int num_entries; // 規則表入口的數量
unsigned int size; // 新的規則表的總大小
/* Hook entry points. */
unsigned int hook_entry[NF_IP_NUMHOOKS]; // 表所監聽HOOK的規則入口,
// 是對於entries[ ]的偏移
unsigned int underflow[NF_IP_NUMHOOKS]; // 規則表的最大下界
unsigned int num_counters; // 舊的計數器數目,即當前的舊entries的數目
struct ipt_counters *counters; // 舊的計數器
struct ipt_entry entries[0]; // 規則表入口
};
上文所提到的filter、nat和mangle表分別是ipt_table這個數據結構的三個實例:packet_filter(位於net/ipv4/netfilter/iptable_filter.c,Line84)、nat_table(位於net/ipv4/netfilter/ip_nat_rule.c,Line104)以及packet_mangler(位於net/ipv4/netfilter/iptable_mangle.c,Line117)
ipt_table_info(位於net/ipv4/netfilter/ip_tables.c,Line86)是實際描述規則表的數據結構:
struct ipt_table_info
{
unsigned int size;
unsigned int number; // 表項的數目
unsigned int initial_entries; // 初始表項數目
unsigned int hook_entry[NF_IP_NUMHOOKS]; // 所監聽HOOK的規則入口
unsigned int underflow[NF_IP_NUMHOOKS]; // 規則表的最大下界
char entries[0] ____cacheline_aligned; // 規則表入口,即真正的規則存儲結構 // ipt_entry組成塊的起始地址,對多CPU,每一個CPU對應一個
};
3. 規則的實現
IPTables中的規則表能夠在用戶空間中使用,但它所採用的數據結構與內核空間中的是同樣的,只不過有些成員不會在用戶空間中使用。
一個完整的規則由三個數據結構共同實現,分別是:
一個ipt_entry結構,存儲規則的總體信息;
0或多個ipt_entry_match結構,存放各類match,每一個結構均可以存聽任意的數據,這樣也就擁有了良好的可擴展性;
1個ipt_entry_target結構,存放規則的target,相似的,每一個結構也能夠存聽任意的數據。
下面將依次對這三個數據結構進行分析:
存儲規則總體的結構ipt_entry,其形式是一個鏈表(位於include/linux/netfilter_ipv4/ip_tables.h,Line122):
struct ipt_entry
{
struct ipt_ip ip;
unsigned int nfcache;
u_int16_t target_offset;
u_int16_t next_offset;
unsigned int comefrom;
struct ipt_counters counters;
unsigned char elems[0];
};
其成員以下:
`struct ipt_ip ip;`:這是對其將要進行匹配動做的IP數據報報頭的描述,其定義於include/linux/netfilter_ipv4/ip_tables.h,Line122,其成員包括源/目的IP及其掩碼,出入端口及其掩碼,協議族、標誌/取反flag等信息。
`unsigned int nfcache;`:HOOK函數返回的cache標識,用以說明通過這個規則後數據報的狀態,其可能值有三個,定義於include/linux/netfilter.h,Line23:
#define NFC_ALTERED 0x8000 //已改變
#define NFC_UNKNOWN 0x4000 //不肯定
另外一個可能值是0,即沒有改變。
`u_int16_t target_offset;`:指出了target的數據結構ipt_entry_target的起始位置,即從ipt_entry的起始地址到match存儲結束的位置
`u_int16_t next_offset;`:指出了整條規則的大小,也就是下一條規則的起始地址,即ipt_entry的起始地址到match偏移再到target存儲結束的位置
`unsigned int comefrom;`:所謂的「back pointer」,據引用此變量的代碼(主要是net/ipv4/netfilter/ip_tables.c中)來看,它應該是指向數據報所經歷的上一個規則地址,由此實現對數據報行爲的跟蹤
`struct ipt_counters counters;`:說明了匹配這個規則的數據報的計數以及字節計數(定義於include/linux/netfilter_ipv4/ip_tables.h,Line100)
`unsigned char elems[0];`:表示擴展的match開始的具體位置(由於它是大小不肯定的),固然,若是不存在擴展的match那麼就是target的開始位置
擴展match的存儲結構ipt_entry_match,位於include/linux/netfilter_ipv4/ip_tables.h,Line48:
struct ipt_entry_match
{
union {
struct {
u_int16_t match_size;
char name[IPT_FUNCTION_MAXNAMELEN];
} user;
struct {
u_int16_t match_size;
struct ipt_match *match;
} kernel;
u_int16_t match_size; //總長度
} u;
unsigned char data[0];
};
其中描述match大小的`u_int16_t match_size;`,從涉及這個變量的源碼看來,在使用的時候須要注意使用一個宏IPT_ALIGN(位於include/linux/netfilter_ipv4/ip_tables.h,Line445)來進行4的對齊處理(0x3 & 0xfffffffc),這應該是因爲match、target擴展後大小的不肯定性決定的。
在結構中,用戶空間與內核空間爲不一樣的實現,內核空間中的描述擁有更多的信息。在用戶空間中存放的僅僅是match的名稱,而在內核空間中存放的則是一個指向ipt_match結構的指針
結構ipt_match位於include/linux/netfilter_ipv4/ip_tables.h,Line342:
struct ipt_match
{
struct list_head list;
const char name[IPT_FUNCTION_MAXNAMELEN];
int (*match)(const struct sk_buff *skb,
const struct net_device *in,
const struct net_device *out,
const void *matchinfo, // 指向規則中match數據的指針,
// 具體是什麼數據結構依狀況而定
int offset, // IP數據報的偏移
const void *hdr, // 指向協議頭的指針
u_int16_t datalen, // 實際數據長度,即數據報長度-IP頭長度
int *hotdrop);
int (*checkentry)(const char *tablename, // 可用的表
const struct ipt_ip *ip,
void *matchinfo,
unsigned int matchinfosize,
unsigned int hook_mask); // 對應HOOK的位圖
void (*destroy)(void *matchinfo, unsigned int matchinfosize);
struct module *me;
};
其中幾個重要成員:
`int (*match)(……);`:指向用該match進行匹配時的匹配函數的指針,match相關的核心實現。返回0時hotdrop置1,當即丟棄數據報;返回非0表示匹配成功。
`int (*checkentry)(……);`:當試圖插入新的match表項時調用這個指針所指向的函數,對新的match表項進行有效性檢查,即檢查參數是否合法;若是返回false,規則就不會被接受(譬如,一個TCP的match只會TCP包,而不會接受其它)。
`void (*destroy)(……);`:當試圖刪除一個使用這個match的表項時,即模塊釋放時,調用這個指針所指向的函數。咱們能夠在checkentry中動態地分配資源,並在destroy中將其釋放。
擴展target的存儲結構ipt_entry_target,位於include/linux/netfilter_ipv4/ip_tables.h,Line71,這個結構與ipt_entry_match結構相似,同時其中描述內核空間target的結構ipt_target(位於include/linux/netfilter_ipv4/ip_tables.h,Line375)也與ipt_match相似,只不過其中的target()函數返回值不是0/1,而是verdict。
而target的實際使用中,是用一個結構ipt_standard_target專門來描述,這纔是實際的target描述數據結構(位於include/linux/netfilter_ipv4/ip_tables.h,Line94),它實際上就是一個ipt_entry_target加一個verdict。
其中成員verdict這個變量是一個很巧妙的設計,也是一個很是重要的東東,其值的正負有着不一樣的意義。我沒有找到這個變量的中文名稱,在內核開發者的新聞組中稱這個變量爲「a magic number」。 它的可能值包括IPT_CONTINUE、IPT_RETURN以及前文所述的NF_DROP等值,那麼它的做用是什麼呢?
因爲IPTables是在用戶空間中執行的,也就是說Netfilter/IPTables這個框架須要用戶態與內核態之間的數據交換以及識別。而在具體的程序中,verdict做爲`struct ipt_standard_target`的一個成員,也是對於`struct ipt_entry_target`中的target()函數的返回值。這個返回值標識的是target()所對應的執行動做,包括系統的默認動做以及外部提交的自定義動做。
可是,在用戶空間中提交的數據每每是相似於「ACCPET」之類的字符串,在程序處理時則是以`#define NF_ACCEPT 1`的形式來進行的;而實際上,以上那些執行動做是以鏈表的數據結構進行存儲的,在內核空間中表現爲偏移。
因而,verdict實際上描述了兩個本質相同但實現不一樣的值:一個是用戶空間中的執行動做,另外一個則是內核空間中在鏈表中的偏移——而這就出現了衝突。
解決這種衝突的方法就是:用正值表示內核中的偏移,而用負值來表示數據報的那些默認動做,而外部提交的自定義動做則也是用正值來表示。這樣,在實際使用這個verdict時,咱們就能夠經過判斷值的正負來進行相應的處理了。
位於net/ipv4/netfilter/ip_tables.h中的函數ipt_do_table()中有一個典型的verdict使用(Line335,其中v是一個verdict的實例):
if (v !=IPT_RETURN) {
verdict = (unsigned)(-v) - 1;
break;
}
其中的IPT_RETURN定義爲:
#define IPT_RETURN (-NF_MAX_VERDICT – 1)
而宏NF_MAX_VERDICT實際上就是:
#define NF_MAX_VERDICT NF_REPEAT
這樣,實際上IPT_RETURN的值就是-NF_REPEAT-1,也就是對應REPEAT,這就是對執行動做的實際描述;而咱們能夠看到,在下面對verdict進行賦值時,它所使用的值是`(unsigned)(-v) – 1`,這就是在內核中實際對偏移進行定位時所使用的值。
那麼總之呢,表和規則的實現如圖http://blog.chinaunix.net/photo/24896_061206192551.jpg所示:
從上圖中不難發現,match的定位以下:
起始地址爲:當前規則(起始)地址+sizeof(struct ipt_entry);
結束地址爲:當前規則(起始)地址+ipt_entry->target_offset;
每個match的大小爲:ipt_entry_match->u.match_size。
target的定位則爲:
起始地址爲match的結束地址,即:當前規則(起始)地址+ipt_entry-> target_offset;
結束地址爲下一條規則的起始地址,即:當前規則(起始)地址+ipt_entry-> next_offset;
每個target的大小爲:ipt_entry_target->u.target_size。
這些對於理解match以及target相關函數的實現是頗有必要明確的。
同時,include/linux/netfilter_ipv4/ip_tables.h中提供了三個「helper functions」,可用於使對於entry、tartget和match的操做變得方便,分別是:
函數ipt_get_target():Line274,做用是取得target的起始地址,也就是上面所說的當前規則(起始)地址+ipt_entry-> target_offset;
宏IPT_MATCH_ITERATE():Line281,做用是遍歷規則的全部match,並執行同一個(參數中)給定的函數。其參數爲一個ipt_entry_match結構和一個函數,以及函數須要的參數。當返回值爲0時,表示遍歷以及函數執行順利完成;返回非0值時則意味着出現問題已終止。
宏IPT_ENTRY_ITERATE():Line300,做用是遍歷一個表中的全部規則,並執行同一個給定的函數。其參數爲一個ipt_entry結構、整個規則表的大小,以及一個函數和其所需參數。其返回值的意義與宏IPT_MATCH_ITERATE()相似。
那麼如何保證傳入的ipt_entry結構是整個規則表的第一個結構呢?據源碼看來,實際調用這個宏的時候傳入的第一個參數都是某個ipt_table_info結構的實例所指向的entries成員,這樣就保證了對整個規則表的完整遍歷。
4. 規則的使用
當一個特定的HOOK被激活時,數據報就開始進入Netfilter/IPTables系統進行遍歷,首先檢查`struct ipt_ip ip`,而後數據報將依次遍歷各個match,也就是`struct ipt_entry_match`,並執行相應的match函數,即ipt_match結構中的*match所指向的函數。當match函數匹配不成功時返回0,或者hotdrop被置爲1時,遍歷將會中止。
對match的遍歷完成後,會開始檢查`struct ipt_entry_target`,其中若是是一個標準的target,那麼會檢查`struct ipt_standard_target`中的verdict,若是verdict值是正的而偏移卻指向不正確的位置,那麼ipt_entry中的comefrom成員就有了用武之地——數據報返回所經歷的上一個規則。對於非標準的target呢,就會調用target()函數,而後根據其返回值進行後面的處理。
5. 規則的擴展
Netfilter/IPTables提供了對規則進行擴展的機制:能夠寫一個LKM來擴展內核空間的功能,也能夠寫一個共享庫來擴展用戶空間中IPTables的功能。
內核的擴展
要對內核空間的功能進行擴展,實際上就是寫一個具備表、match以及target增長功能的模塊,相關的函數爲(位於net/ipv4/netfilter/ip_tables.c,Line1318 to 1444):
ipt_register_table()、ipt_unregister_table(),參數爲struct ipt_table *。
ipt_register_table()函數是這三對函數中最複雜的一個,涉及了內存、信號量等方方面面的東西,但總起來講就作了初始化表以及加入雙向鏈表兩件事。
其複雜一是由於涉及到多CPU的處理(每一個CPU擁有各自獨立的「規則空間」),須要首先將新的entries放入第一個CPU空間,在檢查完畢後再複製到其餘CPU中;二是就是上面所說對新table各個entry的檢查,包括邊界檢查以及完整性檢查等。
其中的重要函數有這麼幾個:
translate_table()(位於net/ipv4/netfilter/ip_tables.c,Line797):這個函數的主要做用是檢查並應用用戶空間傳來的規則 :
對新表進行邊界檢查(由宏IPT_ENTRY_ITERATE()調用函數check_entry_size_and_blocks(),位於net/ipv4/netfilter/ip_tables.c,Line732),包括對齊、過大太小等,特別是保證賦給hook_entries和underflows值的正確性。
調用函數make_source_chains()(位於net/ipv4/netfilter/ip_tables.c,Line499)檢查新的表中是否存在規則環,同時將HOOK的規則遍歷順序存入comefrom變量。(這個函數我沒有仔細看,只是大概略了一下)
對ipt_entry依次進行ipt_ip頭、match以及target的完整性檢查(由宏IPT_ENTRY_ITERATE()調用函數check_entry(),位於net/ipv4/netfilter/ip_tables.c,Line676),保證ipt_entry的正確性。
將正確的 ipt_tables複製給其餘的CPU。
這個函數另外還在do_replace()函數(僅在一個源碼中沒有被調用過的函數中被調用,不予分析)中被調用。
replace_table()(位於net/ipv4/netfilter/ip_tables.c,Line877):這個函數的主要做用是:將獲得模塊初始值的ipt_table_info結構(newinfo)中的值傳給ipt_table中的private,並返回ip_table中舊的private。
list_prepend()(位於include/linux/netfilter_ipv4/listhelp.h,Line75):在這個函數被調用以前,整個初始化的過程就已經結束了,這個函數的主要做用是:互斥地調用Linux源碼中的list_add()函數(位於include/linux/list.h,Line55),將新的table加入到雙向鏈表之中。
ipt_register_match()、ipt_unregister_match(),參數爲struct ipt_match *。
ipt_register_target()、ipt_unregister_target(),參數爲struct ipt_target *。
這三對函數除了ipt_register_table()外的5個函數主要就是互斥地將table/match/target加入到雙向鏈表中或者從雙向鏈表中刪除。
其中向雙向鏈表中加入新節點是經過調用list_named_insert()函數(位於include/linux/netfilter_ipv4/listhelp.h,Line101)實現的。這個函數的主要工做是首先肯定待插入的match名字是否已經存在,只有不存在時才進行插入的操做。
用戶空間的擴展
用戶空間中的擴展用的是共享庫配合libiptc庫的機制,但這種機制是在單獨的IPTbales程序中提供的,內核源碼中並無提供,這裏就不作分析了。
5、數據報過濾模塊——filter表
1. 概述
filter表的功能僅僅是對數據報進行過濾,並不對數據報進行任何的修改。
filter模塊在Netfilter中是基於下列HOOK點的:
NF_IP_LOCAL_IN
NF_IP_FORWARD
NF_IP_LOCAL_OUT
這幾個HOOK分別對應着filter表中的INPUT、FORWARD、OUTPUT三條規則鏈,對於任何一個數據報都會通過這3個HOOK之一。
filter模塊的接口位於文件net/ipv4/netfilter/iptables_filter.c中。
2. filter表的定義和初始化
filter表是前面所述數據結構ipt_table的一個實例,它的定義和初始化位於net/ipv4/netfilter/iptable_filter.c,Line84:
static struct ipt_table packet_filter
= { { NULL, NULL }, "filter", &initial_table.repl,
FILTER_VALID_HOOKS, RW_LOCK_UNLOCKED, NULL, THIS_MODULE };
對照結構ipt_table的定義,咱們能夠發現,filter表的初始化數據爲:
鏈表初始化爲空
表名爲filter
初始化的模板爲&initial_table.repl
初始化的模板表定義於net/ipv4/netfilter/iptable_filter.c,Line30,是一個很簡單的數據結構,只是賦值有些複雜,由於要對所涉及的各個HOOK進行不一樣的處理:
static struct
{
struct ipt_replace repl;
struct ipt_standard entries[3];
struct ipt_error term;
} initial_table __initdata
= { { "filter", FILTER_VALID_HOOKS, 4,
sizeof(struct ipt_standard) * 3 + sizeof(struct ipt_error),
{ [NF_IP_LOCAL_IN] 0,
[NF_IP_FORWARD] sizeof(struct ipt_standard),
[NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 },
{ [NF_IP_LOCAL_IN] 0,
[NF_IP_FORWARD] sizeof(struct ipt_standard),
[NF_IP_LOCAL_OUT] sizeof(struct ipt_standard) * 2 },
0, NULL, { } },
{
/* LOCAL_IN */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),
0, { 0, 0 }, { } },
{ { { { ipt_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* FORWARD */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),
0, { 0, 0 }, { } },
{ { { { ipt_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } },
/* LOCAL_OUT */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_standard),
0, { 0, 0 }, { } },
{ { { { ipt_ALIGN(sizeof(struct ipt_standard_target)), "" } }, { } },
-NF_ACCEPT - 1 } }
},
/* ERROR */
{ { { { 0 }, { 0 }, { 0 }, { 0 }, "", "", { 0 }, { 0 }, 0, 0, 0 },
0,
sizeof(struct ipt_entry),
sizeof(struct ipt_error),
0, { 0, 0 }, { } },
{ { { { ipt_ALIGN(sizeof(struct ipt_error_target)), ipt_ERROR_TARGET } },
{ } },
"ERROR"
}
}
};
咱們能夠看到,一個initial_table包含三個成員:
`struct ipt_replace repl;`:是對一個表進行初始化的最主要部分,這個ipt_replace結構在前面已經分析過了;
`struct ipt_standard entries[3];`:是對這個表所監聽的各個HOOK上對應的初始化信息,實際上就是一個ipt_entry結構加一個ipt_standard_target結構;
`struct ipt_error term;`:是這個表出錯時對應的信息,實際上就是一個ipt_entry結構、一個ipt_entry_target結構再加一個errorname。
當前表所監聽的HOOK位圖爲FILTER_VALID_HOOKS,位於net/ipv4/netfilter/iptable_filter.c,Line9:
#define FILTER_VALID_HOOKS ((1 << NF_IP_LOCAL_IN) | (1 << NF_IP_FORWARD) | (1 << NF_IP_LOCAL_OUT))
咱們能夠看到,實際上就是IN,FORWARD和OUT。
讀寫鎖爲RW_LOCK_UNLOCKED,即爲打開狀態
實際數據區ipt_table_info爲空
定義爲模塊
3. filter表的實現
filter表的實現函數實際上就是模塊iptable_filter.o的init函數,位於net/ipv4/netfilter/iptable_filter.c,Line128。其主要工做是首先經過ipt_register_table()函數進行表的註冊,而後用nf_register_hook()函數註冊表所監聽的各個HOOK。
其中,對HOOK進行註冊時,是經過對數據結構nf_hook_ops的一個實例ipt_ops進行操做來實現的,這個實例的定義及初始化位於net/ipv4/netfilter/iptable_filter.c,Line117:
static struct nf_hook_ops ipt_ops[]
= { { { NULL, NULL }, ipt_hook, PF_INET, NF_IP_LOCAL_IN, NF_IP_PRI_FILTER },
{ { NULL, NULL }, ipt_hook, PF_INET, NF_IP_FORWARD, NF_IP_PRI_FILTER },
{ { NULL, NULL }, ipt_local_out_hook, PF_INET, NF_IP_LOCAL_OUT,
NF_IP_PRI_FILTER }
};
對應前面所分析nf_hook_ops的各個成員,不難肯定這些初始化值的意義。
其中,對應IN和FORWARD的處理函數均爲ipt_hook,OUT的處理函數則爲ipt_local_out_hook,下面依次分析之:
ipt_hook,定義於net/ipv4/netfilter/iptable_filter.c,Line89:
static unsigned int
ipt_hook(unsigned int hook,
struct sk_buff **pskb,
const struct net_device *in,
const struct net_device *out,
int (*okfn)(struct sk_buff *))
{
return ipt_do_table(pskb, hook, in, out, &packet_filter, NULL);
}
實際上它就是調用了ipt_do_table()函數,也就是說,註冊時首先註冊一個ipt_hook()函數,而後ipt_hook()經過調用ipt_do_table()函數對傳入的數據進行真正的處理。下面咱們來看一下ipt_do_table()這個函數:
它位於net/ipv4/netfilter/ip_tables.c,Line254,是一個很長的函數,其主要功能是對數據報進行各類匹配、過濾(包括基本規則、matches以及target),具體些說,其工做大體爲:
初始化各類變量,如IP頭、數據區、輸入輸出設備、段偏移、規則入口及偏移量等等;
進行規則的匹配,首先調用ip_packet_match()函數(位於net/ipv4/netfilter/ip_tables.c,Line121)肯定IP數據報是否匹配規則,若不匹配則跳到下一條規則(這個函數的主要工做大體爲:依次處理源/目的IP地址、輸入輸出接口,而後對基本的規則進行匹配);
若是數據報匹配,則下面開始繼續匹配matches和target,首先利用宏IPT_MATCH_ITERATE調用do_match()函數(下面單獨分析)對擴展的match進行匹配,若不匹配則跳到下一條規則;
擴展match匹配後,首先調用ipt_get_target()得到target的偏移地址,而後對target進行匹配,這個匹配的過程要比match的匹配過程複雜一些,一樣在下面單獨分析。
下面首先來分析do_match()函數,它位於net/ipv4/netfilter/ip_tables.c,Line229,它的實現只有一個if語句:
if (!m->u.kernel.match->match(skb, in, out, m->data, offset, hdr, datalen, hotdrop))
return 1;
else
return 0;
其中的`m->u.kernel.match->match(skb, in, out, m->data, offset, hdr, datalen, hotdrop)`是用來定位match的。
由於若是僅僅是根據match的名字遍歷鏈表來進行查找的話,效率會很是低下。Netfilter源碼中採用的方法是在進行match的檢測以前,也就是在ipt_register_table()函數中經過translate_table()函數由宏IPT_ENTRY_ITERATE調用函數check_entry()時,在check_entry()中經過宏IPT_MATCH_ITERATE調用了check_match()函數(位於net/ipv4/netfilter/ip_tables.c,Line640),在這個函數中,有一個對m->u.kernel.match的賦值:
m->u.kernel.match = match;
這樣,每條規則的u.kernel.match 就與內核模塊中的struct ipt_match鏈表關聯了起來,也就是說,這樣一來,根據match的名字,其對應的match函數就與鏈表中對應的函數關聯了起來。因而,上面的那條定位match的語句的意義也就開始明瞭了:
利用宏IPT_MATCH_ITERATE來遍歷規則中的全部mach,而後直接調用`m->u.kernel.match->match`來進行對數據報的匹配工做——這樣的效率顯然要比簡單的遍歷要高許多。
而後咱們來看一下對target的匹配,從數據結構的實現上看,彷佛這個過程與match的匹配應該是類似的,但實際上target存在標準的和非標準的兩種,其中標準的target與非標準的target的處理是不同的。在這裏我遇到了問題,以下:
首先,在Netfilter的源碼中,存在兩個ipt_standard_target,其中一個是一個struct,位於include/linux/netfilter_ipv4/ip_tables.h,Line94;另外一個是`struct ipt_target`的一個實例,位於net/ipv4/netfilter/IPtables.c,Line1684,而在target的匹配過程當中,它是這樣處理的(ipt_do_tables(),net/ipv4/netfilter/ip_tables.c,Line329):
/* Standard target? */
if (!t->u.kernel.target->target) {……}
從這裏看來,它應該是當 t->u.kernel.target的target函數爲空時,代表其爲標準的target。那麼結合上述二者的定義,彷佛用的是後者,由於後者的定義及初始化以下:
/* The built-in targets: standard (NULL) and error. */
static struct ipt_target ipt_standard_target
= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };
可是問題出如今:初始化中的IPT_STANDARD_TARGET被定義爲」」!!而且在整個源碼中,用到實例化的ipt_standard_target的地方僅有兩處,即上面的這個定義以及ip_tables.c中將ipt_standard_target加入到target鏈表之中。也就是說這個實例的名字一直爲空,這一點如何理解?
ipt_local_out_hook,定義於net/ipv4/netfilter/iptable_filter.c,Line99其功能與ipt_hook()類似,只不過爲了防止DOS攻擊而增長了對ratelimit的檢查。
這樣,到這裏,filter表的實現已經分析完畢,至於具體的過濾功能如何實現,那就是每一個HOOK處理函數的問題了。
6、鏈接跟蹤模塊(Conntrack)
1. 概述
鏈接跟蹤模塊是NAT的基礎,但做爲一個單獨的模塊實現。它用於對包過濾功能的一個擴展,管理單個鏈接(特別是TCP鏈接),並負責爲現有的鏈接分配輸入、輸出和轉發IP數據報,從而基於鏈接跟蹤來實現一個「基於狀態」的防火牆。
當鏈接跟蹤模塊註冊一個鏈接創建包以後,將生成一個新的鏈接記錄。此後,全部屬於此鏈接的數據報都被惟一地分配給這個鏈接。若是一段時間內沒有流量而超時,鏈接將被刪除,而後其餘須要使用鏈接跟蹤的模塊就能夠從新使用這個鏈接所釋放的資源了。
若是須要用於比傳輸協議更上層的應用協議,鏈接跟蹤模塊還必須可以將創建的數據鏈接與現有的控制鏈接相關聯。
Conntrack在Netfilter中是基於下列HOOK的:
NF_IP_PRE_ROUTING
NF_IP_LOCAL_OUT
同時當使用NAT時,Conntrack也會有基於NF_IP_LOCAL_IN和NF_IP_POST_ROUTING的,只不過優先級很小。
在全部的HOOK上,NF_IP_PRI_CONNTRACK的優先級是最高的(-200),這意味着每一個數據報在進入和發出以前都首先要通過Conntrack模塊,而後纔會被傳到鉤在HOOK上的其它模塊。
Conntrack模塊的接口位於文件net/ipv4/netfilter/ip_conntrack_standalone.c中。
2. 鏈接狀態的管理
多元組
在鏈接跟蹤模塊中,使用所謂的「tuple」,也就是多元組,來小巧銳利地描述鏈接記錄的關鍵部分,主要是方便鏈接記錄的管理。其對應的數據結構是ip_conntrack_tuple(位於include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line38):
struct ip_conntrack_tuple
{
struct ip_conntrack_manip src;
struct {
u_int32_t ip;
union {
u_int16_t all;
struct { u_int16_t port; } tcp;
struct { u_int16_t port; } udp;
struct { u_int8_t type, code; } icmp;
} u;
u_int16_t protonum;
} dst;
};
從它的定義能夠看出,一個多元組實際上包括兩個部分:一是所謂的「unfixed」部分,也就是源地址以及端口;二是所謂的「fixed」部分,也就是目的地址、端口以及所用的協議。這樣,鏈接的兩端分別用地址+端口,再加上所使用的協議,一個tuple就能夠惟一地標識一個鏈接了(對於沒有端口的icmp協議,則用其它東東標識)。
鏈接記錄
那麼真正的完整鏈接記錄則是由數據結構ip_conntrack(位於include/linux/netfilter_ipv4/ip_conntrack.h,Line160)來描述的,其成員有:
`struct nf_conntrack ct_general;`:nf_conntrack結構定義於include/linux/skbuff.h,Line89,其中包括一個計數器use和一個destroy函數。計數器use對本鏈接記錄的公開引用次數進行計數
`struct ip_conntrack_tuple_hash tuplehash[IP_CT_DIR_MAX];`:其中的IP_CT_DIR_MAX是一個枚舉類型ip_conntrack_dir(位於include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line65)的第3個成員,從這個結構實例在源碼中的使用看來,實際上這是定義了兩個tuple多元組的hash表項tuplehash[IP_CT_DIR_ORIGINAL/0]和tuplehash[IP_CT_DIR_REPLY/1],利用兩個不一樣方向的tuple定位一個鏈接,同時也能夠方便地對ORIGINAL以及REPLY兩個方向進行追溯
`unsigned long status;`:這是一個位圖,是一個狀態域。在實際的使用中,它一般與一個枚舉類型ip_conntrack_status(位於include/linux/netfilter_ipv4/ip_conntrack.h,Line33)進行位運算來判斷鏈接的狀態。其中主要的狀態包括:
IPS_EXPECTED(_BIT),表示一個預期的鏈接
IPS_SEEN_REPLY(_BIT),表示一個雙向的鏈接
IPS_ASSURED(_BIT),表示這個鏈接即便發生超時也不能提前被刪除
IPS_CONFIRMED(_BIT),表示這個鏈接已經被確認(初始包已經發出)
`struct timer_list timeout;`:其類型timer_list位於include/linux/timer.h,Line11,其核心是一個處理函數。這個成員表示當發生鏈接超時時,將調用此處理函數
`struct list_head sibling_list;`:所謂「預期的鏈接」的鏈表,其中存放的是咱們所指望的其它相關鏈接
`unsigned int expecting;`:目前的預期鏈接數量
`struct ip_conntrack_expect *master;`:結構ip_conntrack_expect位於include/linux/netfilter_ipv4/ip_conntrack.h,Line119,這個結構用於將一個預期的鏈接分配給現有的鏈接,也就是說本鏈接是這個master的一個預期鏈接
`struct ip_conntrack_helper *helper;`:helper模塊。這個結構定義於include/linux/netfilter_ipv4/ip_conntrack_helper.h,Line11,這個模塊提供了一個能夠用於擴展Conntrack功能的接口。通過鏈接跟蹤HOOK的每一個數據報都將被髮給每一個已經註冊的helper模塊(註冊以及卸載函數分別爲ip_conntrack_helper_register()以及ip_conntrack_helper_unregister(),分別位於net/ipv4/netfilter/ip_conntrack_core.c,Line113六、1159)。這樣咱們就能夠進行一些動態的鏈接管理了
`struct nf_ct_info infos[IP_CT_NUMBER];`:一系列的nf_ct_info類型(定義於include/linux/skbuff.h ,Line92,實際上就是nf_conntrack結構)的結構,每一個結構對應於某種狀態的鏈接。這一系列的結構會被sk_buff結構的nfct指針所引用,描述了全部與此鏈接有關係的數據報。其狀態由枚舉類型ip_conntrack_info定義(位於include/linux/netfilter_ipv4/ip_conntrack.h,Line12),共有5個成員:
IP_CT_ESTABLISHED:數據報屬於已經徹底創建的鏈接
IP_CT_RELATED: 數據報屬於一個新的鏈接,但此鏈接與一個現有鏈接相關(預期鏈接);或者是ICMP錯誤
IP_CT_NEW:數據報屬於一個新的鏈接
IP_CT_IS_REPLY:數據報屬於一個鏈接的回覆
IP_CT_NUMBER:不一樣IP_CT類型的數量,這裏爲7,NEW僅存於一個方向上
爲NAT模塊設置的信息(在條件編譯中)
hash表
Netfilter使用一個hash表來對鏈接記錄進行管理,這個hash表的初始指針爲*ip_conntrack_hash,位於net/ipv4/netfilter/ip_conntrack_core.c,Line65,這樣咱們就可使用ip_conntrack_hash[num]的方式來直接定位某個鏈接記錄了。
而hash表的每一個表項則由數據結構ip_conntrack_tuple_hash(位於include/linux/netfilter_ipv4/ip_conntrack_tuple.h,Line86)描述 :
struct ip_conntrack_tuple_hash
{
struct list_head list;
struct ip_conntrack_tuple tuple;
struct ip_conntrack *ctrack;
};
可見,一個hash表項中實質性的內容就是一個多元組ip_conntrack_tuple;同時還有一個指向鏈接的ip_conntrack結構的指針;以及一個鏈表頭(這個鏈表頭不知是幹嗎的)。
3. 鏈接跟蹤的實現
有了以上的數據結構,鏈接跟蹤的具體實現其實就很是簡單而常規了,無非就是初始化、鏈接記錄的建立、在鏈接跟蹤hash表中搜索並定位數據報、將數據報轉換爲一個多元組、判斷鏈接的狀態以及方向、超時處理、協議的添加、查找和註銷、對不一樣協議的不一樣處理、以及在兩個鏈接跟蹤相關的HOOK上對數據報的處理等。
下面重點說明一下我在分析中遇到的幾個比較重要或者比較難理解的地方:
能夠將預期鏈接看做父子關係來理解,如圖 http://blog.chinaunix.net/photo/24896_061206192612.jpg
ip_conntrack的狀態轉換分兩種,一樣用圖來描述。首先是正常的狀態轉換,如圖http://blog.chinaunix.net/photo/24896_061206192631.jpg,
而後是ICMP error時的狀態轉換(由函數icmp_error_track()來判斷,位於net/ipv4/netfilter/ip_conntrack_core.c,Line495),
如圖http://blog.chinaunix.net/photo/24896_061206192648.jpg
在通過HOOK中的NF_IP_PRE_ROUTING時(函數ip_conntrack_in(),位於net/ipv4/netfilter/ip_conntrack_core.c,Line796),因爲外來的數據報有多是通過分片的,因此必須對分片的情形進行處理,將IP數據報組裝後才能分配給鏈接。
具體的操做是首先由函數ip_ct_gather_frags()對分片的數據報進行收集,而後調用ip_defrag()函數(位於net/ipv4/ip_fragment.c,Line632)組裝之
4. 協議的擴展
因爲咱們可能要添加新的協議,因此單獨對協議的擴展進行分析。
各類協議使用一個全局的協議列表存放,即protocol_list(位於include/linux/netfilter_ipv4/ip_conntrack_core.h,Line21),使用結構ip_conntrack_protocol(位於include/linux/netfilter_ipv4/ip_conntrack_protocol.h,Line6)來表示:
struct ip_conntrack_protocol
{
struct list_head list;
u_int8_t proto; //協議號
const char *name;
int (*pkt_to_tuple)(const void *datah, size_t datalen,
struct ip_conntrack_tuple *tuple);
int (*invert_tuple)(struct ip_conntrack_tuple *inverse,
const struct ip_conntrack_tuple *orig);
unsigned int (*print_tuple)(char *buffer,
const struct ip_conntrack_tuple *);
unsigned int (*print_conntrack)(char *buffer,
const struct ip_conntrack *);
int (*packet)(struct ip_conntrack *conntrack,
struct iphdr *iph, size_t len,
enum ip_conntrack_info ctinfo);
int (*new)(struct ip_conntrack *conntrack, struct iphdr *iph,
size_t len);
void (*destroy)(struct ip_conntrack *conntrack);
int (*exp_matches_pkt)(struct ip_conntrack_expect *exp,
struct sk_buff **pskb);
struct module *me;
};
其中重要成員:
`int (*pkt_to_tuple)(……)`:其指向函數的做用是將協議加入到ip_conntrack_tuple的dst子結構中
`int (*invert_tuple)(……)`:其指向函數的做用是將源和目的多元組中協議部分的值進行互換,包括IP地址、端口等
`unsigned int (*print_tuple)(……)`:其指向函數的做用是打印多元組中的協議信息
`unsigned int (*print_conntrack)(……)`:其指向函數的做用是打印整個鏈接記錄
`int (*packet)(……)`:其指向函數的做用是返回數據報的verdict值
`int (*new)(……)`:當此協議的一個新鏈接發生時,調用其指向的這個函數,調用返回true時再繼續調用packet()函數
`int (*exp_matches_pkt)(……)`:其指向函數的做用是判斷是否有數據報匹配預期的鏈接
添加/刪除協議使用的是函數ip_conntrack_protocol_register()以及ip_conntrack_protocol_unregister(),分別位於net/ipv4/netfilter/ip_conntrack_standalone.c,Line298 & 320,其工做就是將ip_conntrack_protocol添加到全局協議列表protocol_list。
7、網絡地址轉換模塊(Network Address Translation)
1. 概述
網絡地址轉換的機制通常用於處理IP地址轉換,在Netfilter中,能夠支持多種NAT類型,而其實現的基礎是鏈接跟蹤。
NAT能夠分爲SNAT和DNAT,即源NAT和目的NAT,在Netfilter中分別基於如下HOOK:
NF_IP_PRE_ROUTING:能夠在這裏定義DNAT的規則,由於路由器進行路由時只檢查數據報的目的IP地址,因此爲了使數據報得以正確路由,咱們必須在路由以前就進行DNAT
NF_IP_POST_ROUTING:能夠在這裏定義SNAT的規則,系統在決定了數據報的路由之後在執行該HOOK上的規則
NF_IP_LOCAL_OUT:定義對本地產生的數據報的DNAT規則
CONFIG_IP_NF_NAT_LOCAL定義後,NF_IP_LOCAL_IN上也能夠定義DNAT規則。
同時,MASQUERADE(假裝)是SNAT的一種特例,它與SNAT幾乎同樣,只有一點不一樣:若是鏈接斷開,全部的鏈接跟蹤信息將被丟棄,而去使用從新鏈接之後的IP地址進行IP假裝;而REDIRECT(重定向)是DNAT的一種特例,這時候就至關於將符合條件的數據報的目的IP地址改成數據報進入系統時的網絡接口的IP地址。
2. 基於鏈接跟蹤的相關數據結構
NAT是基於鏈接跟蹤實現的,NAT中全部的鏈接都由鏈接跟蹤模塊來管理,NAT模塊的主要任務是維護nat表和進行實際的地址轉換。這樣,咱們來回頭從新審視一下鏈接跟蹤模塊中由條件編譯決定的部分。
首先,是鏈接的描述ip_conntrack,在鏈接跟蹤模塊部分中提到,這個結構的最後有「爲NAT模塊設置的信息」,即:
#ifdef CONFIG_IP_NF_NAT_NEEDED
struct {
struct ip_nat_info info;
union ip_conntrack_nat_help help;
#if defined(CONFIG_IP_NF_TARGET_MASQUERADE) || \
defined(CONFIG_IP_NF_TARGET_MASQUERADE_MODULE)
int masq_index;
#endif
} nat;
#endif /* CONFIG_IP_NF_NAT_NEEDED */
這是一個叫作nat的子結構,其中有3個成員:
一個ip_nat_info結構,這個結構下面會具體分析
一個ip_conntrack_nat_help結構,是一個空結構,爲擴展功能而設
一個爲假裝功能而設的index,從源碼中對這個變量的使用看來,是對應所假裝網絡接口的ID,也就是net_device中的ifindex成員
好,下面咱們來看一下這個ip_nat_info結構,這個結構存儲了鏈接中的地址綁定信息,其定義位於include/linux/netfilter_ipv4/ip_nat.h,Line98:
struct ip_nat_info
{
int initialized;
unsigned int num_manips;
struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];
const struct ip_nat_mapping_type *mtype;
struct ip_nat_hash bysource, byipsproto;
struct ip_nat_helper *helper;
struct ip_nat_seq seq[IP_CT_DIR_MAX];
};
`int initialized;`:這是一個位圖,代表源地址以及目的地址的地址綁定是否已被初始化
`unsigned int num_manips;`:這個成員指定了存放在下面的manip數組中的可執行操做的編號,在不一樣的HOOK以及不一樣的方向上,可執行操做是分別進行計數的。
`struct ip_nat_info_manip manips[IP_NAT_MAX_MANIPS];`:ip_nat_info_manip結構定義於include/linux/netfilter_ipv4/ip_nat.h,Line66,一個ip_nat_info_manip結構對應着一個可執行操做或地址綁定,其成員包括方向(ORIGINAL以及REPLY)、HOOK號、操做類型(由一個枚舉類型ip_nat_manip_type定義,有IP_NAT_MANIP_SRC和IP_NAT_MANIP_DST兩種)以及一個ip_conntrack_manip結構
`const struct ip_nat_mapping_type *mtype;`:ip_nat_mapping_type這個結構在整個內核源碼中都沒有定義,根據註釋來看應該也是一個預留的擴展,通常就是NULL
`struct ip_nat_hash bysource, byipsproto;`:ip_nat_hash結構定義於include/linux/netfilter_ipv4/ip_nat.h,Line89,實際上就是一個帶表頭的ip_conntrack結構,跟鏈接跟蹤中hash表的實現相似。其中,
bysource表是用來管理現有鏈接的
byipsproto表則管理已經完成的轉換映射,以保證同一個IP不會同時有兩個映射,避免地址轉換衝突
`struct ip_nat_helper *helper;`:擴展用
`struct ip_nat_seq seq[IP_CT_DIR_MAX];`:這是爲每個方向(其實就兩個方向)記錄一個序列號。ip_nat_seq結構定義於include/linux/netfilter_ipv4/ip_nat.h,Line33,這個結構用得並很少,應該是用於TCP鏈接的計數和對涉及TCP的修改的定位
3. nat表的實現
nat表的初始化和實現與filter極爲類似。
在初始化上,其初始化位於net/ipv4/netfilter/ip_nat_rule.c,Line104,初始化所用模板則位於net/ipv4/netfilter/ip_nat_rule.c,Line50。
在實現上,其實現函數就是NAT模塊的初始化函數init_or_cleanup()(位於net/ipv4/netfilter/ip_nat_standalone.c,Line278)。其工做主要是依次調用ip_nat_rule_init()、ip_nat_init()以及nf_register_hook()。
首先ip_nat_rule_init()(位於net/ipv4/netfilter/ip_nat_rule.c,Line278)調用ipt_register_table()來初始化並註冊nat表,而後利用ipt_register_target()來初始化並註冊SNAT和DNAT,在這個註冊過程當中,關鍵的函數是ip_nat_setup_info(位於net/ipv4/netfilter/ip_nat_core.c,Line511),其工做是:
首先調用invert_tupler()(net/ipv4/netfilter/ip_conntrack_core.c,Line879),將記錄反轉
而後調用get_unique_tuple()(net/ipv4/netfilter/ip_nat_core.c,Line393,在指定的地址範圍(ip_nat_multi_range結構)中查找空閒的地址),若是沒有空閒地址可用則會返回NF_DROP
判斷源和目的是否改變,若是改變,則更新ip_nat_info。
而後ip_nat_init()(位於net/ipv4/netfilter/ip_nat_core.c,Line953)會給nat所用的兩個hash表(bysource、byipsproto)分配空間並初始化各類協議
最後會經過nf_register_hook()註冊相應HOOK的函數ip_nat_fn()、ip_nat_local_fn()和ip_nat_out(),並增長鏈接跟蹤的計數器。
在具體的HOOK函數實現上,後二者其實都是基於ip_nat_fn()的,而這其中最重要的處理函數,也就是實際的網絡地址轉換函數是do_bindings(),下面將對其進行分析:
do_bindings()位於net/ipv4/netfilter/ip_nat_core.c,Line747,其主要工做是將ip_nat_info中的地址綁定應用於數據報:
它首先在ip_nat_info->manip數組中查找可以匹配的綁定
而後調用manip_pkt()函數(位於net/ipv4/netfilter/ip_nat_core.c,Line701)進行相應的地址轉換:
manip_pkt()這個函數會根據不一樣的方向進行轉換,而且對校驗和進行處理
同時,它是遞歸調用本身以處理不一樣協議的狀況
最後調用helper模塊進行執行(固然,若是有的話),特別是避免在同一個數據報上執行屢次同一個helper模塊
nat表與filter表還有一個較大的不一樣:在一個鏈接中,只有第一個數據報纔會通過nat表,而其轉換的結果會做用於此鏈接中的其它全部數據報。
4. 協議的擴展
要想擴展NAT的協議,那麼必須寫入兩個模塊,一個用於鏈接跟蹤,一個用於NAT實現。
與鏈接跟蹤相似,nat中協議也由一個列表protos存放,位於include/linux/netfilter_ipv4/ip_nat_core.h,Line17。協議的註冊和註銷分別是由函數ip_nat_protocol_register()和ip_nat_protocol_unregister()實現的,位於net/ipv4/netfilter/ip_nat_standalone.c,Line242。
nat的協議是由結構ip_nat_protocol描述的,其定義位於include/linux/netfilter_ipv4/ip_nat_protocol.h,Line10:
struct ip_nat_protocol
{
struct list_head list;
const char *name;
unsigned int protonum;
void (*manip_pkt)(struct iphdr *iph, size_t len,
const struct ip_conntrack_manip *manip,
enum ip_nat_manip_type maniptype);
int (*in_range)(const struct ip_conntrack_tuple *tuple,
enum ip_nat_manip_type maniptype,
const union ip_conntrack_manip_proto *min,
const union ip_conntrack_manip_proto *max);
int (*unique_tuple)(struct ip_conntrack_tuple *tuple,
const struct ip_nat_range *range,
enum ip_nat_manip_type maniptype,
const struct ip_conntrack *conntrack);
unsigned int (*print)(char *buffer,
const struct ip_conntrack_tuple *match,
const struct ip_conntrack_tuple *mask);
unsigned int (*print_range)(char *buffer,
const struct ip_nat_range *range);
};
其中重要成員:
`void (*manip_pkt)(……)`:其指向的函數會根據ip_nat_info->manip參數進行數據報的轉換,即do_bindings()中調用的manip_pkt()函數
`int (*in_range)(……)`:其指向的函數檢查多元組的協議部分值是否在指定的區間以內
`int (*unique_tuple)(……)`:其指向函數的做用是根據manip的類型修改多元組中的協議部分,以得到一個惟一的地址,多元組的協議部分被初始化爲ORIGINAL方向
8、數據報修改模塊──mangle表
1. 概述
mangle這個詞的原意是撕裂、破壞,這裏所謂「packet mangling」是指對packet的一些傳輸特性進行修改。mangle表被用來真正地對數據報進行修改,它能夠在全部的5個HOOK上進行操做。
從源碼看來,在mangle表中所容許修改的傳輸特性目前有:
TOS(服務類型):修改IP數據報頭的TOS字段值
TTL(生存時間):修改IP數據報頭的TTL字段值
MARK:修改skb的nfmark域設置的nfmark字段值。
nfmark是數據報的元數據之一,是一個用戶定義的數據報的標記,能夠是unsigned long範圍內的任何值。該標記值用於基於策略的路由,通知ipqmpd(運行在用戶空間的隊列分揀器守護進程)將該數據報排隊給哪一個進程等信息。
TCP MSS(最大數據段長度):修改TCP數據報頭的MSS字段值
2. mangle表的實現
遍歷整個源碼,沒有發現mangle表的獨有數據結構。
mangle表的初始化和實現與filter極爲類似。在初始化上,其初始化位於net/ipv4/netfilter/iptable_mangle.c,Line117,初始化所用模板則位於net/ipv4/netfilter/iptable_mangle.c,Line43。
在實現上,其實現函數就是mangle模塊的初始化函數init()(位於net/ipv4/netfilter/iptable_mangle.c,Line183)。其工做就是依次註冊packet_mangler表以及5個HOOK。其中NF_IP_LOCAL_OUT的處理函數爲ipt_local_hook(),其他均爲ipt_route_hook(),分別位於net/ipv4/netfilter/iptable_mangle.c,Line132 & 122,兩者的關鍵實現都是經過調用ipt_do_table()實現的。
3. 數據報的修改
對數據報不一樣位的修改都是經過單獨的模塊實現的,也就是說由ipt_tos.o、ipt_ttl.o、ipt_mark.o、ipt_tcpmss.o實現上面所說的四種傳輸特性的修改。
以TOS爲例,其涉及的文件爲net/ipv4/netfilter/ipt_tos.c以及include/linux/netfilter_ipv4/ipt_tos.h。
其專有數據結構爲ipt_tos_info,定義於ipt_tos.h中:
struct ipt_tos_info {
u_int8_t tos;
u_int8_t invert;
};
其模塊的添加/卸載函數很簡單,其實就是添加/刪除TOS的MATCH:tos_match(定義並初始化於ipt_tos.c,Line37):
static struct ipt_match tos_match
= { { NULL, NULL }, "tos", &match, &checkentry, NULL, THIS_MODULE };
而在tos_match的處理函數match()中,已經完成了對相應位的賦值,這彷佛是違反match僅僅匹配而不修改的一個特例。
9、其它高級功能模塊
Netfilter中還有一些其它的高級功能模塊,基本是爲了用戶操做方便的,沒有對它們進行分析,如:
REJECT,丟棄包並通知包的發送者,同時返回給發送者一個可配置的ICMP錯誤信息,由ipt_REJECT.o完成
MIRROR,互換源和目的地址之後並從新發送,由ipt_MIRROR.o完成
LOG, 將匹配的數據報傳遞給系統的syslog()進行記錄,由ipt_LOG.o完成
ULOG,Userspace logging,將數據報排隊轉發到用戶空間中,將匹配的數據適用用戶空間的log進程進行記錄,由ip_ULOG.o完成。這是Netfilter的一個關鍵技術,可使用戶進程能夠進行復雜的數據報操做,從而減輕內核空間中的複雜度
Queuing,這是上面ULOG技術的基礎,由ip_queue.o完成,提供可靠的異步包處理以及性能兩號的libipq庫來進行用戶空間數據報操做的開發。
等等……