自己開發一個match模塊
今天我們來寫一個很簡單的match來和大家分享如何爲iptables開發擴展功能模塊。這個模塊是根據IP報文中有效載荷字段的長度來對其進行匹配,支持固定包大小,也支持一個區間範圍的的數據包,在用戶空間的用法是:
iptables -A FORWARD -m pktsize --size XX[:YY] -j DROP
這條規則在FORWARD鏈上對於大小爲XX(或大小介於XX和YY之間)的數據包進行匹配,數據包的長度不包括IP頭部的長度。爲了簡單起見,這個模塊沒有處理“!”情況,因爲只是闡述開發過程。
OK,下面我們開始動手吧。我們這個模塊名字叫pktsize,所以內核中該模塊對應的文件是ipt_pktsize.h和ipt_pktsize.c;用戶空間的文件名爲libipt_pktsize.c。
我們先來定義頭文件,因爲這個匹配模塊功能很單一,所以設計它的數據結構主要包含兩部分,如下:
#ifndef __IPT_PKTSIZE_H #define __IPT_PKTSIZE_H
#define PKTSIZE_VERSION "0.1" //我們自己定義的用戶保存規則中指定的數據包大小的結構體 struct ipt_pktsize_info { u_int32_t min_pktsize,max_pktsize; //數據包的最小和最大字節數(不包括IP頭) };
#endif //__IPT_EXLENGTH_H |
一、用戶空間的開發
我們知道用戶空間的match是用struct iptables_match{}結構表示的,所以我們需要去實例化一個該對象,然後對其關鍵成員進行初始化賦值。一般情況我們需要實現parse函數、help函數、final_check函數、print和save函數就已經可以滿足基本要求了。我們先把整體代碼框架搭起來:
#include #include #include #include #include #include #include #include
static void help(void) { //Todo: your code }
/*用於解析命令行參數的回調函數; 如果成功則返回true */ static int parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct ipt_entry_match **match) { return 1; }
static void final_check(unsigned int flags) { //Todo: your code }
static void print(const void *ip, const struct ipt_entry_match *match, int numeric) { //Todo: your code }
static void save(const void *ip, const struct ipt_entry_match *match) { //Todo: your code }
static struct iptables_match pktsize= { .next = NULL, .name = "pktsize", .version = IPTABLES_VERSION, .size = IPT_ALIGN(sizeof(struct ipt_pktsize_info)), .userspacesize = IPT_ALIGN(sizeof(struct ipt_pktsize_info)), .help = &help, .parse = &parse, .final_check = &final_check, .print = &print, .save = &save };
void _init(void) { register_match(&pktsize); } |
下面我們分別來實現這些回調函數,並對其做簡單解釋:
help()函數:當我們在命令輸入iptables -m pktsize -h時用於顯示該模塊用法的幫助信息,所以很簡單,你想怎麼提示用戶都可以:
static void help(void) { printf( "pktsize v%s options:\n" " --size size[:size] Match packet size against value or range\n" "\nExamples:\n" " iptables -A FORWARD -m pktsize --size 65 -j DROP\n" " iptables -A FORWARD -m pktsize --size 80:120 -j DROP\n" , PKTSIZE_VERSION); } |
print()函數:該函數是用於打印用戶的輸入參數的,因爲其他地方也有可能會輸出規則參數,所以我們將其封裝成一個子函數__print()供其他人來調用,如下:
static void __print(struct ipt_pktsize_info * info){ if (info->max_pktsize == info->min_pktsize) printf("%u ", info->min_pktsize); else printf("%u:%u ", info->min_pktsize, info->max_pktsize); }
static void print(const void *ip, const struct ipt_entry_match *match, int numeric) { printf("size "); __print((struct ipt_pktsize_info *)match->data); } |
從命令行終端輸入的數據包大小的規則參數“XX:YY”其實最終是在ipt_entry_match結構體的data成員裏保存着的,關於該結構體參見博文三的圖解。
save()函數:該函數和print類似:
static void save(const void *ip, const struct ipt_entry_match *match) { printf("--size "); __print((struct ipt_pktsize_info *)match->data); } |
final_check()函數:如果你的模塊有些長參數格式是必須的,那麼當用戶調用了你的模塊但又沒進一步制定必須參數時,一般在這個函數裏做校驗限制。如,我的模塊帶了一個必須按參數--size ,而且後面必須跟數值,所以該函數內容如下:
static void final_check(unsigned int flags) { if (!flags) exit_error(PARAMETER_PROBLEM, "\npktsize-parameter problem: for pktsize usage type: iptables -m pktsize --help\n"); } |
parse()函數:該函數是我們的核心,參數的解析最終是在該函數中完成的。因爲我們用到長參數格式,所以必須引入一個結構體struct option{},我們在博文十三中已經見過,不清楚原理和用法的童鞋可以回頭複習一下。
這裏我們的模塊只有一個擴展參數,所以該結構非常簡單,如果你有多個,則必須一一處理:
static struct option opts[] = { { "size", 1, NULL, '1' }, {0} }; //並且還要將該結構體對象賦給:pktsize.extra_opts= opts; //解析參數的具體函數單獨出來,會使得parse()函數的結構很優美 /* 我們的輸入參數的可能格式如下: xx 指定數據包大小 XX :XX 範圍是0~XX YY: 範圍是YY~65535 xx:YY 範圍是XX~YY */ static void parse_pkts(const char* s,struct ipt_pktsize_info *info){ char* buff,*cp; buff = strdup(s);
if(NULL == (cp=strchr(buff,':'))){ info->min_pktsize = info->max_pktsize = strtol(buff,NULL,0); }else{ *cp = '\0'; cp++;
info->min_pktsize = strtol(buff,NULL,0); info->max_pktsize = (cp[0]? strtol(cp,NULL,0):0xFFFF); }
free(buff);
if (info->min_pktsize > info->max_pktsize) exit_error(PARAMETER_PROBLEM, "pktsize min. range value `%u' greater than max. " "range value `%u'", info->min_pktsize, info->max_pktsize); }
static int parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct ipt_entry_match **match) { struct ipt_pktsize_info *info = (struct ipt_pktsize_info *)(*match)->data; switch(c){ case '1': if (*flags) exit_error(PARAMETER_PROBLEM, "size: `--size' may only be " "specified once"); parse_pkts(argv[optind-1], info); *flags = 1; break; default: return 0; } return 1; } |
該文件的最終版本從“ libipt_pktsize.zip ”下載。
用戶空間要用的libipt_pktsize.so的源代碼我們就算編寫完成了,迫不及待的去試一下吧。當前,我的iptables確實不認識pktsize模塊。
我將libipt_pktsize.c拷貝到/usr/src/iptables-1.4.0/ extensions目錄下,並修改該目錄下的Makefile文:
然後在/usr/src/iptables-1.4.0/目錄下單獨執行一次make命令,最後將extensions/目錄下編譯出來的libipt_pktsize.so拷貝到iptables的庫目錄裏,例如/lib/iptables-1.4.0/iptables。
此時,當我們再在命令行執行一次iptables -m pktsize -h時,在末尾處可以看到如下的信息:
就證明我們的模塊已經被iptables正確識別併成功加載了。
一、內核空間的開發
同樣的,開發內核的Netfilter模塊時,我們還是先搭其框架:
#include #include #include #include #include #include
MODULE_AUTHOR("Koorey Wung "); MODULE_DESCRIPTION("iptables pkt size range match module."); MODULE_LICENSE("GPL");
static int match(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const struct xt_match *match, const void *matchinfo, int offset, unsigned int protoff, int *hotdrop) { return 1; }
static struct ipt_match pktsize_match = { .name = "test", .family = AF_INET, .match = match, .matchsize = sizeof(struct ipt_pktsize_info), .destroy = NULL, .me = THIS_MODULE, };
static int __init init(void) { return xt_register_match(&pktsize_match); }
static void __exit fini(void) { xt_unregister_match(&pktsize_match); }
module_init(init); module_exit(fini); |
通過前面幾篇博文我們已經知道,內核中用struct ipt_match{}結構來表示一個match模塊。我們要開發match的內核部分時,也必須去實例化一個struct ipt_match{}對象,然後對其進行必要的初始化設置,最後通過xt_register_match()將其註冊到xt[AF_INET].match全局鏈表中就OK了,就這麼簡單。
我們這裏例子非常簡單,只實現最關鍵的核心函數:match()函數。不過這已經滿足我們需求了,我們的match函數做的事情也很simple,就是計算數據包的有效載荷:
static int match(const struct sk_buff *skb, const struct net_device *in, const struct net_device *out, const struct xt_match *match, const void *matchinfo, int offset, unsigned int protoff, int *hotdrop) { const struct ipt_pktsize_info *info = matchinfo; const struct iphdr *iph = skb->nh.iph;
int pkttruesize = ntohs(iph->tot_len)-(iph->ihl*4);
if(pkttruesize>=info->min_pktsize && pkttruesize <=info->max_pktsize){ return 1; } else{ return 0; } return 1; } |
但有一點需要明確,如果數據包匹配了match函數返回1;否則返回0.
該文件的最終版本從“ ipt_pktsize.zip ”下載。
至此,我們的pktsize模塊的內核部分就算開發完了,接下將其編譯成ipt_pktsize.ko放到系統目錄中去。詳細參見博文十三,我係統執行了如下步驟:
當我們的模塊已經被內核認親後,那感覺真的是無以言表啊。廢話不多說,我們趕緊執行一條規則看看:
曾經有個哥們說他在使用owner模塊時出現了同樣的問題,這會不會是由於同樣的原因導致的呢?如果你是嚴格遵循我的教程來的,那麼這裏我要說一定就是:這個問題是我特意留出的。細心的童鞋回頭看代碼時應該很容易找出問題了。原因:
這裏有一點要提醒大家注意,內核中的模塊名和用戶空間的模塊名必須一致。這裏我們將pktsize_mach.name改爲“pktsize”,重新編譯,然後將其拷貝。在重新執行insmod前,先執行rmmod ipt_pktsize將原來的模塊卸載掉,最後再次執行那條規則:
今天通過這個簡單的例子,向大家示範一下爲Netfitler/iptables開發功能模塊的方法。整體來說還是比較簡單,當然要寫出更有意義,更高效的模塊需要對協議棧、TCP/IP原理、網絡編程等有較好的基礎才行。
未完,待續…