| 概述 寫一個 iptables/netfilter 匹配模組的大體步驟如下:
- 找到你要匹配的具體情況。
- 寫用於接受參數的使用者空間部分程式。
- 寫用於分析包資訊,得出是否匹配結論的核心空間部分程式。
1.0 iptables 模組iptables 庫的用途基本上講就是和使用者互動,它捕獲使用者要傳送給核心態程式的參數。 1.1 可用的資料結構和函數首先是一些基本資料結構。 <iptables/include/iptables.h> 稍後文中就將可以看到這些結構的用途了。
/* Include file for additions: new matches and targets. */ struct iptables_match { struct iptables_match *next;
ipt_chainlabel name;
const char *version;
/* Size of match data. */ size_t size;
/* Size of match data relevent for userspace comparison purposes */ size_t userspacesize;
/* Function which prints out usage message. */ void (*help)(void);
/* Initialize the match. */ void (*init)(struct ipt_entry_match *m, unsigned int *nfcache);
/* Function which parses command options; returns true if it ate an option */ int (*parse)(int c, char **argv, int invert, unsigned int *flags, const struct ipt_entry *entry, unsigned int *nfcache, struct ipt_entry_match **match);
/* Final check; exit if not ok. */ void (*final_check)(unsigned int flags);
/* Prints out the match iff non-NULL: put space at end */ void (*print)(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric);
/* Saves the match info in parsable form to stdout. */ void (*save)(const struct ipt_ip *ip, const struct ipt_entry_match *match);
/* Pointer to list of extra command-line options */ const struct option *extra_opts;
/* Ignore these men behind the curtain: */ unsigned int option_offset; struct ipt_entry_match *m; unsigned int mflags; #ifdef NO_SHARED_LIBS unsigned int loaded; /* simulate loading so options are merged properly */ #endif }; |
1.2 深入骨架程式 1.2.1 初始化我們首先初始化 'iptables_match' 結構中的常用欄位: static struct iptables_match ipaddr = { 'Name' 是你的函數庫的檔案名稱(也就是 libipt_ipaddr)。 你不能在這個位置放其它的東西,這是用來自動載入你的庫的。 .name = "ipaddr", 下一個欄位 'version' 是 iptables 的版本。後面的兩個欄位都是用於保持使用者態程式和核心態共用結構的大小一致性的。 .version = IPTABLES_VERSION, .size = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)), .userspacesize = IPT_ALIGN(sizeof(struct ipt_ipaddr_info)), 'Help' 是使用者輸入 'iptables -m module -h' 的時候要調用的函數。'Parse' 是使用者輸入一條新規則的時候調用的,用於驗證參數的合法性。'print' 就是使用 'iptables -L' 的時候顯示前面添加的規則的。 .help = &help, .init = &init, .parse = &parse, .final_check = &final_check, .print = &print, .save = &save, .extra_opts = opts }; iptables 架構能夠支援多個共用庫。每個共用庫必須使用 <iptables/iptables.c> 中定義的 'register_match()' 向 iptables 註冊。這個函數將在模組被 iptables 載入的時候調用。 更多資訊請參考:'man dlopen'。 void _init(void) { register_match(&ipaddr); } 1.2.2 save 函數如果我們有一個需要儲存的規則集,可以利用 iptables 提供的工具 'iptables-save',它可以儲存下所有的規則。顯然你需要擴充這個工具來儲存下來這些規則。這個擴充通過 save 函數完成。 static void save(const struct ipt_ip *ip, const struct ipt_entry_match *match) { const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match->data; 如果源地址是規則的一部分的話,列印它。 if (info->flags & IPADDR_SRC) { if (info->flags & IPADDR_SRC_INV) printf("! "); printf("--ipsrc "); print_ipaddr((u_int32_t *)&info->ipaddr.src); }如果目的地址是規則的一部分的話就列印目的地址。 if (info->flags & IPADDR_DST) { if (info->flags & IPADDR_DST_INV) printf("! "); printf("--ipdst "); print_ipaddr((u_int32_t *)&info->ipaddr.dst); } } 1.2.3 print 函數和上面的 save 所蘊含的哲學一樣,也有一個 print 函數用於列印規則。它在 'iptables -L' 的時候被調用。我們將在下文裡看到參數 'ipt_entry_match *match' 的用途,不過我們已經對它有了一點概念了,是吧? static void print(const struct ipt_ip *ip, const struct ipt_entry_match *match, int numeric) { const struct ipt_ipaddr_info *info = (const struct ipt_ipaddr_info *)match->data;
if (info->flags & IPADDR_SRC) { printf("src IP "); if (info->flags & IPADDR_SRC_INV) printf("! "); print_ipaddr((u_int32_t *)&info->ipaddr.src); }
if (info->flags & IPADDR_DST) { printf("dst IP "); if (info->flags & IPADDR_DST_INV) printf("! "); print_ipaddr((u_int32_t *)&info->ipaddr.dst); } } 1.2.4 final check 函數這個函數是最後一次正確性檢查的機會。它在使用者輸入完規則之後、參數解析剛剛完成的時候被調用。 static void final_check(unsigned int flags) { if (!flags) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Invalid parameters."); } 1.2.5 parse 函數parse 是最重要的一個函數,因為這裡要檢查參數的正確性,並寫入我們將共用給核心態程式的資訊。它在每次參數被發現的時候被調用,也就是說,如果使用者輸入了兩個參數,這個函數就將被以不同的參數代碼 c 調用兩次。 static int parse(int c, char **argv, int invert, unsigned int *flags, const struct ipt_entry *entry, unsigned int *nfcache, struct ipt_entry_match **match) { 我們使用特殊結構來儲存我們要傳遞給核心態程式的資訊。'match' 指標被傳遞給多個函數,我們可以每次使用同樣的資料結構。一旦規則被載入了,這個指標就被複製到了核心態程式裡。通過這個方式,核心模組可以知道使用者想要分析什麼(這正是問題的關鍵,不是麼?)。 struct ipt_ipaddr_info *info = (struct ipt_ipaddr_info *)(*match)->data; 每個參數對應著一個單獨的值,於是我們能根據進入的參數決定採取何種行動。下文中我們將看到我們如何把參數變成數值。
switch(c) {首先,我們檢查參數是否被使用了多次。如果使用了多次的話,調用 <iptables/iptables.c> 中定義的 'exit_error()' 函數,這樣程式會立刻帶著 <iptables/include/iptables_common.h> 中定義的 'PARAMETER_PROBLEM' 的錯誤狀態推出。否則,我們在我們的標頭檔中定義的 'IPADDR_SRC' 中設定 'flags' 和 'info->flags'。稍後我們將介紹這個標頭檔。 雖然這兩個標誌看起來差不多,但是是完全不同的。'flag' 的範圍就是這個函數,而 'info->flags' 是我們用於和核心態程式共用資訊的結構的一部分。 case '1': if (*flags & IPADDR_SRC) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipsrc once!"); *flags |= IPADDR_SRC; info->flags |= IPADDR_SRC; 檢查如果取反標誌 '!' 是否存在,如果有的話,在 'info->flags' 中寫相應的值。 之後調用為這個骨架程式所寫的內建函式 'parse_ipaddr' 來把 IP 位址從字串轉化為 32 位值。 if (invert) info->flags |= IPADDR_SRC_INV;
parse_ipaddr(argv[optind-1], &info->ipaddr.src); break; 同樣考慮,我們檢查是否存在多次設定,置恰當的標誌。 case '2': if (*flags & IPADDR_DST) exit_error(PARAMETER_PROBLEM, "ipt_ipaddr: Only use --ipdst once!"); *flags |= IPADDR_DST; info->flags |= IPADDR_DST; if (invert) info->flags |= IPADDR_DST_INV;
parse_ipaddr(argv[optind-1], &info->ipaddr.dst); break;
default: return 0; }
return 1; } 1.2.6 options 結構前文中,我們已經談到了要將每個參數映射到一個值。 'struct option' 就是一個達到這個目的的好辦法。要想得到關於這個結構的進一步資訊,強烈建議閱讀 'man 3 getopt'。 static struct option opts[] = { { .name = "ipsrc", .has_arg = 1, .flag = 0, .val = '1' }, { .name = "ipdst", .has_arg = 1, .flag = 0, .val = '2' }, { .name = 0 } };
1.2.7 init 函數init 函數用於初始化一些特定的東西,比如 netfilter 的 cache 系統。現在不必過多考慮這個函數的具體用途。 static void init(struct ipt_entry_match *m, unsigned int *nfcache) { /* Can't cache this */ *nfcache |= NFC_UNKNOWN; }
1.2.7 help 函數這個函數通過 'iptables -m match_name -h' 被調用,用於顯示可用的參數。 static void help(void) { printf ( "IPADDR v%s options:/n" "[!] --ipsrc /t/t The incoming ip addr matches./n" "[!] --ipdst /t/t The outgoing ip addr matches./n" "/n", IPTABLES_VERSION ); }
1.2.8 標頭檔 'ipt_ipaddr.h'這個檔案定義了我們需要的一些東西。 #ifndef _IPT_IPADDR_H #define _IPT_IPADDR_H 我們已經在上文中使用了這些特定的值了。 #define IPADDR_SRC 0x01 /* Match source IP addr */ #define IPADDR_DST 0x02 /* Match destination IP addr */
#define IPADDR_SRC_INV 0x10 /* Negate the condition */ #define IPADDR_DST_INV 0x20 /* Negate the condition */ 結構 'ipt_ipaddr_info' 是將要被拷貝到核心態程式的那個資料結構。 struct ipt_ipaddr { u_int32_t src, dst; };
struct ipt_ipaddr_info {
struct ipt_ipaddr ipaddr;
/* Flags from above */ u_int8_t flags;
};
#endif 1.3 第一章小結第一部分中,我們討論了 iptables 庫的作用。我們記述了每個函數的內容和 'ipt_ipaddr_info' 這個用於儲存資訊的將要被拷貝到核心態程式來做進一步處理的重要結構。我們也看到了 iptables 結構和如何註冊一個新的庫。 應該注意,這僅僅是一個用於示範架構如何工作的骨架程式。而且,'ipt_ipaddr_info' 和其他類似的東西並不是 iptables/netfilter 的一部分,而僅僅是這個例子的一部分。 2.0 netfilter 模組一個匹配模組的工作就是察看每一個收到的包並決定是否符合某個判決準則。這個模組要做如下工作:
- 接收每個包,並察看匹配模組相關的表
- 告知 netfilter,我們的模組是否匹配上了這個包
2.1 可用的函數與資料結構首先是一些基本資料結構,這些資料結構定義在 <linux/netfilter_ipv4/ip_tables.h>。 如果你對這個結構以及前面的 iptables 部分還有興趣的話,你可以看看 Rusty Russell 和 Harald Welte 寫的 netfilter hacking howto 。 struct ipt_match { struct list_head list;
const char name[IPT_FUNCTION_MAXNAMELEN];
/* Return true or false: return FALSE and set *hotdrop = 1 to force immediate packet drop. */ /* Arguments changed since 2.4, as this must now handle non-linear skbs, using skb_copy_bits and skb_ip_make_writable. */ int (*match)(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const void *matchinfo, int offset, int *hotdrop);
/* Called when user tries to insert an entry of this type. */ /* Should return true or false. */ int (*checkentry)(const char *tablename, const struct ipt_ip *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. */ struct module *me; }; 2.2 深入骨架程式 2.2.1 初始化首先,我們初始化 'ipt_match' 資料結構中的常用域。 static struct ipt_match ipaddr_match = { 'name' 是你的模組的檔案名稱字串(也就是說 ipt_ipaddr)。 .name = "ipaddr", 下面的欄位是架構將要使用的回呼函數.'match'是當一個包傳送給你的模組的時候要調用的函數. .match = match, .checkentry = checkentry, .me = THIS_MODULE, }; 你的核心模組的 init 函數需要通過指向一個 'struct ipt_match' 的指標調用 'ipt_register_match()' 來向 netfilter 架構註冊.這個函數在模組被載入的時候調用. static int __init init(void) { printk(KERN_INFO "ipt_ipaddr: init!/n"); return ipt_register_match(&ipaddr_match); } 當把模組從核心中移出的時候這個函數會被調用.這裡我們進行的工作是登出匹配器。 static void __exit fini(void) { printk(KERN_INFO "ipt_ipaddr: exit!/n"); ipt_unregister_match(&ipaddr_match); } 設定讓這兩個函數在模組裝入和移出的時候被調用。 module_init(init); module_exit(fini);
2.2.2 match 函數Linux 的 TCP/IP 協議棧包括5個 netfilter 鉤子。這樣,一個包近來之後,協議棧把包送到相應的鉤子,依次進入每個表,再依次疊帶每條規則。當你的模組得到包的時候,你的模組就可以進行它的工作了。 static int match(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const void *matchinfo, int offset, const void *hdr, u_int16_t datalen, int *hotdrop) { 希望你還記著我們在使用者態程式裡面做了些什麼! :)。現在把使用者態程式拷貝過來的資料結構映射到我們這裡 const struct ipt_skeleton_info *info = matchinfo; 'skb' 包含了我們想要處理的包。想要得到關於這個在 linux 的 TCP/IP 協議棧中到處都是功能強大的資料結構的資訊,可以看看 Harald Welte 寫的一出色的文章 article (ftp://ftp.gnumonks.org/pub/doc/skb-doc.html) 。 struct iphdr *iph = skb->nh.iph; 這裡,我們就是列印一些有趣的東西來看看他們長成什麼樣子。宏 'NIPQUAD' 用於以可讀的方式顯示一個 IP 位址,它是在 <linux/include/linux/kernel.h> 中定義的。 printk(KERN_INFO "ipt_ipaddr: IN=%s OUT=%s TOS=0x%02X " "TTL=%x SRC=%u.%u.%u.%u DST=%u.%u.%u.%u " "ID=%u IPSRC=%u.%u.%u.%u IPDST=%u.%u.%u.%u/n",
in ? (char *)in : "", out ? (char *)out : "", iph->tos, iph->ttl, NIPQUAD(iph->saddr), NIPQUAD(iph->daddr), ntohs(iph->id), NIPQUAD(info->ipaddr.src), NIPQUAD(info->ipaddr.dst) ); 如果輸入了 '--ipsrc' 參數,我們察看源地址是否和規則指定的地址相匹配。別忘了考慮反標誌 '!'。如果沒有匹配,我們返回 0. if (info->flags & IPADDR_SRC) { if ( (ntohl(iph->saddr) != ntohl(info->ipaddr.src)) ^ !!(info->flags & IPADDR_SRC_INV) ) {
printk(KERN_NOTICE "src IP %u.%u.%u.%u is not matching %s./n", NIPQUAD(info->ipaddr.src), info->flags & IPADDR_SRC_INV ? " (INV)" : ""); return 0; } }這裡,我們進行完全相同的工作,只是察看 '--ipdst' 參數。 if (info->flags & IPADDR_DST) { if ( (ntohl(iph->daddr) != ntohl(info->ipaddr.dst)) ^ !!(info->flags & IPADDR_DST_INV) ) {
printk(KERN_NOTICE "dst IP %u.%u.%u.%u is not matching%s./n", NIPQUAD(info->ipaddr.dst), info->flags & IPADDR_DST_INV ? " (INV)" : ""); return 0; } }如果都不成功,返回 1,表明我們匹配了這個包。 return 1; } 2.2.3 checkentry 函數checkentry 通常是最後一次合法性檢查的機會。關於它何時被調用有些難以理解。看看 post (http://www.mail-archive.com/netfilter-devel@lists.samba.org/msg00625.html) 作為一個解釋吧。這篇文章也是一篇 netfilter hacking howto。 static int checkentry(const char *tablename, const struct ipt_ip *ip, void *matchinfo, unsigned int matchsize, unsigned int hook_mask) { const struct ipt_skeleton_info *info = matchinfo;
if (matchsize != IPT_ALIGN(sizeof(struct ipt_skeleton_info))) { printk(KERN_ERR "ipt_skeleton: matchsize differ, you may have forgotten to recompile me./n"); return 0; }
printk(KERN_INFO "ipt_skeleton: Registered in the %s table, hook=%x, proto=%u/n", tablename, hook_mask, ip->proto);
return 1; }
2.3 第二章小結在第二部分,我們講了 netfilter 模組以及如何使用特定結構註冊它。另外我們還討論了如何根據使用者空間部分給出的判據匹配特定的情況。 3.0 運行 iptables/netfilter我們已經看到了如何寫一個新的 iptables/netfilter 匹配模組。現在我們將把它添加到核心中來運行它。這裡,我假設你知道如何編譯核心。首先把骨架匹配檔案從本文下載頁面下載下來。 3.1 iptables現在,如果你還沒有 iptables 的原始碼的話,可以從 ftp://ftp.netfilter.org/pub/iptables/ 下載。然後拷貝 'libipt_ipaddr.c' 到 <iptables/extensions/>。 這是 <iptables/extensions/Makefile> 中的一行,你應該加上 'ipaddr'。 PF_EXT_SLIB:=ah addrtype comment connlimit connmark conntrack dscp ecn esp hashlimit helper icmp iprange length limit ipaddr mac mark multiport owner physdev pkttype realm rpc sctp standard state tcp tcpmss tos ttl udp unclean CLASSIFY CONNMARK DNAT DSCP ECN LOG MARK MASQUERADE MIRROR NETMAP NOTRACK REDIRECT REJECT SAME SNAT TARPIT TCPMSS TOS TRACE TTL ULOG 3.2 核心首先,你應該拷貝 'ipt_ipaddr.c' 到 <linux/net/ipv4/netfilter/>,拷貝 ' ipt_ipaddr.h' 到 <linux/net/ipv4/netfilter/>。有些讀者可能還在使用 2.4 核心,所以我同時提供了 2.4和 2.6 的檔案。 對於 2.4 核心,編輯 <linux/net/ipv4/netfilter/Config.in> ,加入下面加重的行。 # The simple matches. dep_tristate ' limit match support' CONFIG_IP_NF_MATCH_LIMIT $CONFIG_IP_NF_IPTABLES dep_tristate ' ipaddr match support' CONFIG_IP_NF_MATCH_IPADDR $CONFIG_IP_NF_IPTABLES 然後,編輯 <linux/Documentation/Configure.help> 加入加重的行。我複製了一些文本來協助你找到要加入內容的地方。 limit match support CONFIG_IP_NF_MATCH_LIMIT limit matching allows you to control the rate at which a rule can be ... ipaddr match support CONFIG_IP_NF_MATCH_IPADDR ipaddr matching. etc etc. 最後,你必須把加重的行加入到 <linux/net/ipv4/netfilter/Makefile> 之中。 # matches obj-$(CONFIG_IP_NF_MATCH_HELPER) += ipt_helper.o obj-$(CONFIG_IP_NF_MATCH_LIMIT) += ipt_limit.o obj-$(CONFIG_IP_NF_MATCH_IPADDR) += ipt_ipaddr.o Now for 2.6, files to edit are <linux/net/ipv4/netfilter/Kconfig> and <linux/net/ipv4/netfilter/Makefile>. 對 2.6 核心,編輯的檔案應該是 <linux/net/ipv4/netfilter/Kconfig>和 <linux/net/ipv4/netfilter/Makefile>。 總結下面剩下的的就是從新編譯以及我忘了說的了。 Happy hacking!! 感謝 Samuel Jean. |