- filter表的實現
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 }
};
-
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);
}
-
初始化各種變量,如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的匹配過程複雜一些,同樣在下面單獨分析。
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;
if (!t->u.kernel.target->target) {……}
static struct ipt_target ipt_standard_target
= { { NULL, NULL }, IPT_STANDARD_TARGET, NULL, NULL, NULL };
-
ipt_local_out_hook,定義於net/ipv4/netfilter/iptable_filter.c,Line99其功能與ipt_hook()相似,只不過爲了防止DOS攻擊而增加了對ratelimit的檢查。
六、連接跟蹤模塊(Conntrack)
1. 概述
-
NF_IP_PRE_ROUTING
-
NF_IP_LOCAL_OUT
-
同時當使用NAT時,Conntrack也會有基於NF_IP_LOCAL_IN和NF_IP_POST_ROUTING的,只不過優先級很小。
2. 連接狀態的管理
-
多元組
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;
};
-
連接記錄
-
`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,Line1136、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表
struct ip_conntrack_tuple_hash
{
struct list_head list;
struct ip_conntrack_tuple tuple;
struct ip_conntrack *ctrack;
};
3. 連接跟蹤的實現
- 所謂“預期鏈接”
- ip_conntrack的狀態轉換
- 在NF_IP_PRE_ROUTING上對分片IP數據報的處理
4. 協議的擴展
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)(……)`:其指向函數的作用是判斷是否有數據報匹配預期的連接
七、網絡地址轉換模塊(Network Address Translation)
1. 概述
-
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規則。
2. 基於連接跟蹤的相關數據結構
#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
這是一個叫做nat的子結構,其中有3個成員:
-
一個ip_nat_info結構,這個結構下面會具體分析
-
一個ip_conntrack_nat_help結構,是一個空結構,爲擴展功能而設
-
一個爲僞裝功能而設的index,從源碼中對這個變量的使用看來,是對應所僞裝網絡接口的ID,也就是net_device中的ifindex成員
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表的實現
-
首先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(),並增加連接跟蹤的計數器。
-
它首先在ip_nat_info->manip數組中查找能夠匹配的綁定
-
然後調用manip_pkt()函數(位於net/ipv4/netfilter/ip_nat_core.c,Line701)進行相應的地址轉換:
-
manip_pkt()這個函數會根據不同的方向進行轉換,並且對校驗和進行處理
-
同時,它是遞歸調用自己以處理不同協議的情況
-
最後調用helper模塊進行執行(當然,如果有的話),特別是避免在同一個數據報上執行多次同一個helper模塊
4. 協議的擴展
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方向
八、數據報修改模塊──mangle表
1. 概述
-
TOS(服務類型):修改IP數據報頭的TOS字段值
-
TTL(生存時間):修改IP數據報頭的TTL字段值
-
MARK:修改skb的nfmark域設置的nfmark字段值。
-
nfmark是數據報的元數據之一,是一個用戶定義的數據報的標記,可以是unsigned long範圍內的任何值。該標記值用於基於策略的路由,通知ipqmpd(運行在用戶空間的隊列分揀器守護進程)將該數據報排隊給哪個進程等信息。
-
-
TCP MSS(最大數據段長度):修改TCP數據報頭的MSS字段值
2. mangle表的實現
3. 數據報的修改
struct ipt_tos_info {
u_int8_t tos;
u_int8_t invert;
};
static struct ipt_match tos_match
= { { NULL, NULL }, "tos", &match, &checkentry, NULL, THIS_MODULE };
九、其它高級功能模塊
-
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庫來進行用戶空間數據報操作的開發。
-
等等……