1. 程式人生 > >Linux netfilter 學習筆記 之三 ip層netfilter的table、rule、match、target結構分析

Linux netfilter 學習筆記 之三 ip層netfilter的table、rule、match、target結構分析

基於linux2.6.21

上一節分析了ip層hook回撥函式的註冊以及呼叫流程,本節我們就開始分析每一個模組的具體實現。 工欲善其事必先利其器,一個功能模組的程式碼實現與其資料結構的設計有很大的關係,所以我們本節主要是分析table、rule、match、target相關的資料結構,爭取本節能把資料結構的定義以及資料結構之間的關係分析明瞭。

1. table、rule、match、target等相關資料結構分析

在分析table、rule、match、target之前,先把它們之間的聯絡圖貼出來,看了這個圖以後,我們基本上就知道xt_table裡rule、match、target之間的關聯了。

 

 總體框架圖

對於一個xt_table表,通過其private指標,指向了xt_table_info結構,而xt_table_info中的entries指標陣列的每一個指標成員指向xt_table表在相應cpu中的rule的首地址。

一個表中的所有rule在一個連續的記憶體塊中,讓所有rule在一個連續的記憶體塊中,使對rule規則的遍歷定址成為了可能,因為一個規則相對於下一個規則並沒有使用連結串列的形式連線在一起。

而一個完整的rule規則,包括了ipt_entry、ipt_entry_match、ipt_standard_target組成(其實ipt_entry就代表了一個規則,通過ipt_entry->elems即可找到該規則對應的match)。

下面我們一一分析上圖提到的資料結構。

1.1 xt_table

該結構體對應於iptables中的表,目前核心註冊的table有filter、mangle、nat、raw表,而這些table根據pf值新增到xt_af[pf].tables連結串列中。而一個xt_table中包含了該表所支援的hook點與該表裡已新增的所有rule規則。

 
struct xt_table
{
/*用於將xt_table連線在一起的連結串列成員*/
struct list_head list;
 
/*表名*/
char name[XT_TABLE_MAXNAMELEN];
 
/*該表所感興趣的hook點*/
unsigned int valid_hooks;
 
/*讀寫鎖*/
rwlock_t lock;
 
/*指標,指向xt_table_info,這個指標指向的xt_table_info才是表的重要成員
用於存放規則鏈*/
void *private;
 
/* 判斷該表是否屬於一個模組 */
struct module *me;
 
/*協議號*/
int af;
};


 

1.2 xt_table_info

上面分析xt_table時,我們只xt_table通過private指標指向了xt_table_info,xt_table_info裡面包括了表中含有的規則,是表比較重要的結構體。

struct xt_table_info
{
/*在一個cpu內,該xt_table所包含的所有規則的記憶體總數(以位元組為單位)*/
unsigned int size;/*規則的個數*/
unsigned int number;/*初始化時的規則格式*/
unsigned int initial_entries;
/*每條規則鏈相對於第一條規則鏈的偏移量*/
unsigned int hook_entry[NF_IP_NUMHOOKS];/*這個具體用在什麼地方我還沒有搞懂,看別人的解釋為每一條規則鏈範圍的最大
上限,不過我看初始化時,其與hook_entry在對應規則鏈上的值是相等的,目前我在程式碼裡發現對underflow的呼叫,基本上都是將其與hook_entry在對應規則鏈上的值設定為
相同的,還需要深入分析後確認*/
unsigned int underflow[NF_IP_NUMHOOKS];
 
/* ipt_entry tables: one per CPU */
/*每一個cpu裡,ipt_entry指標,指向ipt_entry規則的首地址*/
char *entries[NR_CPUS];
};


關於ipt_tale_info與ipt_table、ipt_entry的關係見上面的總體架構圖。

1.3 ipt_entry

下面我們分析下ipt_entry

struct ipt_entry
{
struct ipt_ip ip;
 
/* Mark with fields that we care about. */
unsigned int nfcache;
 
/* Size of ipt_entry + matches */
/*該規則中target結構相對於該ipt_entry首地址的偏移量*/
u_int16_t target_offset;
/* Size of ipt_entry + matches + target */
/* 下一個規則相對於該ipt_entry首地址的偏移量*/
u_int16_t next_offset;
 
/* 這個變數的用途有兩個:
1.判斷table表中的規則鏈是否存在環路
2.遍歷規則鏈鏈時,用於使用者自定義鏈的規則執行完時返回到主鏈時使用*/
unsigned int comefrom;
 
/* Packet and byte counters. */
struct xt_counters counters;
 
/*由於在設計時需要match結構與ipt_entry的記憶體是連續的,但是一個ipt_entry包含的match個數又是可變的,所以定義了一個可變長度陣列elems,主要是為了實現動態的申請match記憶體空間*/
unsigned char elems[0];
};


1.3.1 ipt_ip

對應的ipt_ip定義如下,其主要用於標準匹配

/*

標準匹配時的匹配條件

*/

struct ipt_ip {
/* 源、目的ip地址 */
struct in_addr src, dst;
/* 源、目的ip地址的掩碼*/
struct in_addr smsk, dmsk;
/*資料包入口、出口的網路介面名稱*/
char iniface[IFNAMSIZ], outiface[IFNAMSIZ];
/*入口、出口的網路介面掩碼*/
unsigned char iniface_mask[IFNAMSIZ], outiface_mask[IFNAMSIZ];
 
/* Protocol, 0 = ANY */
/*協議號*/
u_int16_t proto;
 
/* Flags*/
u_int8_t flags;
 
/*是否是反轉匹配*/
/* Inverse flags */
u_int8_t invflags;
};


 

一個ipt_entry的整體結構如上圖。

1.4 ipt_entry_match

我們使用到ipt_entry_match時,就說明這是一個擴充套件match,對於一個標準match是通過呼叫函式ip_packet_match,對ipt_entry->ip進行判斷來實現的,只有擴充套件match才會使用該結構體在ipt_entry中新增一個ipt_entry_match變數。

match結構體,包括使用者態與核心態聯合體,通過這個聯合體我們發現

只有match_size是共用的。當用戶態需要新增新的規則時,對於新規則的

match,使用者態只在ipt_entry_match.u.user.name中設定match的名稱,而核心在新增規則時

就會根據ipt_entry_match.u.user.name在連結串列xt[af].match中查詢符合要求的ipt_entry_match,當查詢

到時就會對ipt_entry_match.u.kernel.match 進行賦值

struct ipt_entry_match
{
union {
struct {
/*該match所佔用的記憶體大小(以位元組為單位)*/
u_int16_t match_size;
 
/*該match的名稱*/
char name[IPT_FUNCTION_MAXNAMELEN-1];
/*該match的版本,
通過match的名稱與版本資訊可以唯一確定一個match。
*/
u_int8_t revision;
} user;
struct {
/*該match所佔用的記憶體大小(以位元組為單位)*/
u_int16_t match_size;
 
/*指向ipt_match結構,對於*/
struct ipt_match *match;
} kernel;
 
/* Total length */
u_int16_t match_size;
} u;
 
/*可變長度陣列,與下一個match或者target關聯*/
unsigned char data[0];
};


ipt_entry_match相關的結構體圖如下:

 

上圖是一個ipt_entry_match與ipt_match以及一個ipt_match與xt_af[2].match之間的關聯,當我們定義了一個xt_match(xt_match與ipt_match是同一個結構)時,需要將其插入到相應的xt_af[pf].match連結串列中。而當我們為一個rule規則新增一個擴充套件match時,根據ipt_entry_match.u.user.name查詢xt_af[pf].match連結串列,當查詢到相應的xt_match時,就將其地址賦值給ipt_entry_match.u.kernel.match(將該圖與總體框架圖搭配使用,就基本明瞭ipt_table、ipt_table_info、ipt_entry、ipt_entry_match、xt_match、xt_af[pf].match這幾個資料結構之間的關係)。

1.4.1 xt_match

而xt_match的定義如下:

struct xt_match
{
struct list_head list; //連結串列,使該match新增到match連結串列中
 
const char name[XT_FUNCTION_MAXNAMELEN-1];//match名稱
 
u_int8_t revision;
 
/* Return true or false: return FALSE and set *hotdrop = 1 to
           force immediate packet drop. */
/* Arguments changed since 2.6.9, as this must now handle
   non-linear skb, using skb_header_pointer and
   skb_ip_make_writable. */
 /*
 match處理函式
*/
int (*match)(const struct sk_buff *skb,
     const struct net_device *in,
     const struct net_device *out,
     const void *matchinfo,
     int offset,
     unsigned int protoff,
     int *hotdrop);
 
/* Called when user tries to insert an entry of this type. */
/* Should return true or false. */
/*合法性檢查函式,在建立一個ipt_entry時,在為其match指標賦值後,即會呼叫該函式
進行合法性檢查,當檢查失敗後,即會返回失敗,且說明ipt_table建立失敗*/
int (*checkentry)(const char *tablename,
  const void *ip,
  void *matchinfo,
  unsigned int matchinfosize,
  unsigned int hook_mask);
 
/* Called when entry of this type deleted. */
/*銷燬函式*/
void (*destroy)(void *matchinfo, unsigned int matchinfosize);
 
/* Set this to THIS_MODULE if you are a module, otherwise NULL */
struct module *me;
};
 


如果我們想要新增一個擴充套件match,就要初始化一個xt_match結構,並且將插入到xt_af[pf].match連結串列

1.5 ipt_standard_target

該結構體主要是對ipt_entry_target的封裝,且增加了變數verdict。

這個verdict起到了很大的作用。

對於target,我們需要知道target有兩大類,標準target與擴充套件target

a)對於標準target,其ipt_entry_target.u.kernel.target為NULL,其只需返回NF_ACCEPT/NF_DROP等操作。

b)對於擴充套件target,需要呼叫ipt_entry_target.u.kernel.target,執行擴充套件target操作,並根據返回值決定是否允許資料通行。

即擴充套件匹配是根據ipt_entry_target.u.kernel.target決定資料包下一步的執行操作,那標準匹配是根據什麼決定資料包下一步的操作的呢?

答案就是verdict,當verdict為小於0時,則-verdict則為資料包下一步的執行操作;當verdict大於0時,則說明該target需要跳轉到一個使用者自定義鏈的鏈首地址,其值為使用者自定義鏈相對於表的第一條鏈規則的偏移量。

  基於以上,我們知道verdict的存在即指明瞭標準target的動作,又為使用者鏈的存在並生效提供了可能。

struct ipt_standard_target
{
struct ipt_entry_target target;
int verdict;
};
 


1.6 ipt_entry_target

雖然與ipt_entry_match定義相同,但是ipt_entry_target既可以表示一個標準target,也可以表示一個擴充套件target。當是一個標準的target時,其ipt_entry_target.u.kernel.target為NULL。

下面我們分析一下這個結構體:

/*

target結構體,包括使用者態與核心態聯合體,通過這個聯合體我們發現

只有target_size是共用的。當用戶態需要新增新的規則時,對於新規則的

target,使用者態只在ipt_entry_target.u.user.name中設定target的名稱,而核心在新增規則時

就會根據ipt_entry_target.u.user.name在連結串列xt[af].target中查詢符合要求的ipt_standard_target,當查詢

到時就會對ipt_entry_target.u.kernel.target 進行賦值

*/

struct ipt_entry_target
{
union {
struct {
u_int16_t target_size; //target 所佔用的記憶體大小
 
/* Used by userspace */
char name[IPT_FUNCTION_MAXNAMELEN-1];//target 的名字
/*target的版本號,這個值也有很大的作用,這個值讓target的向 上相容成為了可能。
存在以下情況:
對於target名稱為"ABC ",revision為0的target,我們想對這個 target的擴充套件target函式做新的架構修改,但是又不想改target的 名稱,也不想直接改原target的擴充套件target函式,這時我們可以重 新新增一個target名稱為"ABC",revision為1,且擴充套件target函式
為我們新編寫的target。這樣既保證了針對原來target "ABC"的 iptables規則能正確執行,又能滿足我們新的需求。
通過name與revision可以唯一確定一個target
*/
u_int8_t revision;
} user;
struct {
/*target 所佔用的記憶體大小*/
u_int16_t target_size;
/* 擴充套件target使用,用於指向xt_target */
struct ipt_target *target;
} kernel;
 
/*target 所佔用的記憶體大小*/
u_int16_t target_size;
} u;
/*可變長陣列,與下一個ipt_entry關聯*/
unsigned char data[0];
};
 


ipt_entry_target相關的結構體圖如下:

 

上圖是一個ipt_entry_target與ipt_target以及一個ipt_target與xt_af[2].target之間的關聯,當我們定義了一個xt_target(xt_target與ipt_target是同一個結構)時,需要將其插入到相應的xt_af[pf].target連結串列中。而當我們為一個rule規則新增一個擴充套件target時,根據ipt_entry_target.u.user.name查詢xt_af[pf].target連結串列,當查詢到相應的xt_target時,就將其地址賦值給ipt_entry_target.u.kernel.tagret(將該圖與總體框架圖搭配使用,就基本明瞭ipt_table、ipt_table_info、ipt_entry、ipt_entry_target、xt_target、xt_af[pf].target這幾個資料結構之間的關係)。

1.6.1 xt_target

xt_target的定義如下:

struct xt_target
{
struct list_head list;//連結串列,使該match新增到target連結串列中
 
const char name[XT_FUNCTION_MAXNAMELEN-1];//target 名稱
 
u_int8_t revision;
 
 
/*
target處理函式,對於SNAT、DNAT即在其target函式裡,更新request或者reply方向 ip_conntrack_tuple值
*/
unsigned int (*target)(struct sk_buff **pskb,
       const struct net_device *in,
       const struct net_device *out,
       unsigned int hooknum,
       const void *targinfo,
       void *userdata);
 
/*合法性檢查函式,在建立一個ipt_entry時,在為其target指標賦值後,即會呼叫該函式
進行合法性檢查,當檢查失敗後,即會返回失敗,且說明ipt_table建立失敗*/
int (*checkentry)(const char *tablename,
  const void *entry,
  void *targinfo,
  unsigned int targinfosize,
  unsigned int hook_mask);
 
/*銷燬函式,當刪除一個規則時呼叫*/
void (*destroy)(void *targinfo, unsigned int targinfosize);
 
/* 該target是否屬於一個模組 */
struct module *me;
};


當我們想要新增一個擴充套件target,就要初始化一個xt_target結構,並且將插入到xt_af[pf].target連結串列

至此,基本上就把三層netfilter相關的資料結構一一介紹完了,綜合以上分析,結合上面的結構關聯圖,就把結構體ipt_table、ipt_table_info、ipt_entry、ipt_entry_target、xt_target、xt_af[pf].target、xt_af[pf].match、xt_af[pf].tables、xt_match、ipt_ip之間的關聯也介紹清楚了。至此,萬事俱備,就差分析表的建立、初始化、新增規則、刪除規則、遍歷表規則、新增match、新增target等具體實現了。只要對資料結構之間的關係清楚了,分析這些功能的實現就順理成章了。