上一篇文章分析了iptables程式碼下發運作的流程細節,篇幅有限還有很多需要補充.關於netfilter的框架網上已經被講爛了,框架很簡單,但是實現卻不簡單.但不論什麼都要最終歸到實際應用上,才能體現其價值.
下面我們就以iptables1.4.21 ubuntu14 32位 核心版本 3.13.0為環境來開發一個最常規的擴充套件應用.
首先下載iptables原始碼:http://www.netfilter.org (這個網站有很多東西,可以好好看看)
關於linux kernel netfilter開啟的配置,這裡不多說,預設核心已經配置好了.(當然對於ubuntu14 系統預設把netfilter大部分都編譯成了核心模組,需要什麼自己手動安裝即可)
先說說編譯iptables,很簡單 看看裡面的說明文件即可,例INSTALL:
$./configure
$make
$make install
./configure 還可以支援一些選項引數 ,裡面都有說明。例子如下:
./configure –prefix=/home/iptables –host=arm-linux // host可以不指定,自動判定,預設就是x86
這裡還要補充點知識:動作的說明:
iptables動作———DROP、ACCEPT、REJECT
◆ACCEPT
一旦包滿足了指定的匹配條件,就會被ACCEPT,並且不會再去匹配當前鏈中的其他規則或同一個表內的其他規則,但它還要通過其他表中的鏈
◆DROP
如果包符合條件,這個target就會把它丟掉,也就是說包的生命到此結束,不會再向前走一步,效果就是包被阻塞了。在某些情況下,這個target會引起意外的結果,因為它不會向傳送者返回任何資訊,也不會向路由器返回資訊,這就可能會使連線的另一方的sockets因苦等迴音而亡:)
解決這個問題的較好的辦法是使用REJECT target,(注:因為它在丟棄包的同時還會向傳送者返回一個錯誤資訊,這樣另一方就能正常結束),尤其是在阻止埠掃描工具獲得更多的資訊時,可以隱蔽被過濾掉的埠等等(譯者注:因為掃描工具掃描一個埠時,如果沒有返回資訊,一般會認為埠未開啟或被防火牆等裝置過濾掉了)。還要注意如果包在子鏈中被DROP了,那麼它在主鏈裡也不會再繼續前進,不管是在當前的表還是在其他表裡。
◆REJECT
REJECT和DROP基本一樣,區別在於它除了阻塞包之外,還向傳送者返回錯誤資訊。現在,此target還只能用在INPUT、FORWARD、OUTPUT和它們的子鏈裡,而且包含 REJECT的鏈也只能被它們呼叫,否則不能發揮作用。它只有一個選項,是用來控制返回的錯誤資訊的種類的。
還有其他的動作:
LOG 用來記錄與資料包相關的資訊
MARK 設定mark值,這個值是一個無符號的整數
MASQUERADE 和SNAT的作用相同,區別在於它不需要指定–to-source
SNAT 源網路地址轉換
DNAT 目的網路地址轉換
REDIRECT 轉發資料包一另一個埠
REJECT REJECT和DROP都會將資料包丟棄,區別在於REJECT除了丟棄資料包外,還向傳送者返回錯誤資訊
RETURN 使資料包返回上一層
TOS 用來設定IP頭部中的Type Of Service欄位
TTL 用於修改IP頭部中Time To Live欄位的值
ULOG ULOG可以在使用者空間記錄被匹配的包的資訊,這些資訊和整個包都會通過netlink socket被多播
QUEUE 為使用者空間的程式或應用軟體管理包佇列
MIRROR 顛倒IP頭部中的源目地址,然後再轉發包
先一個實際的命令應用:
IPT -A INPUT -m pkttype –pkt-type broadcast -j REJECT
這裡我們自己註冊一個match 傳遞自己的引數並解析處理。
功能是禁止大於特定長度的ip報文通過 size由我們指定。
需要兩個部分的工作
1.使用者空間match的註冊
2.核心空間match的註冊
根據程式碼裡pkttype的實際例子作為參考,很快我們就能幹一票了.
需要修改的檔案:
使用者空間
Xt_pktsize.c // 路徑extensions下
Xt_pktsize.h // 路徑 include/linux/netfilter/
核心:這裡編譯為模組的方式
Xt_pktsize.c
Xt_pktsize.h
註冊match當然首先要初始化它各個節點的元素
使用者空間程式碼如下:
xt_pktsize.h
1 2 3 4 5 6 7 8 |
#ifndef _XT_PKTSIZE_H #define _XT_PKTSIZE_H struct xt_pktsize_info { int pktsize; int invert; }; #endif /*_XT_PKTSIZE_H*/ |
xt_pktsize.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
/* * Shared library add-on to iptables to match * packets by their size * */ #include <stdio.h> #include <string.h> #include <xtables.h> #include <linux/if_packet.h> #include <linux/netfilter/xt_pktsize.h> enum { O_pktsize = 0, }; struct pktsizes { const char *name; unsigned char pktsize; unsigned char printhelp; const char *help; }; static void print_types(void) { unsigned int i; printf("Valid packet sizes:64-65535\n"); } static void pktsize_help(void) { printf( "pktsize match options:\n" "[!] --pkt-size packetsize match packet size\n"); print_types(); } static const struct xt_option_entry pktsize_opts[] = { {.name = "pkt-size", .id = O_pktsize, .type = XTTYPE_STRING, .flags = XTOPT_MAND | XTOPT_INVERT}, XTOPT_TABLEEND, }; static void parse_pktsize(const char *pktsize, struct xt_pktsize_info *info) { unsigned int i,size; char *buffer; printf("pktsize is %s...\n",pktsize); buffer = strdup(pktsize); if(!xtables_strtoui(buffer, NULL, &size, 0, UINT16_MAX)) xtables_error(PARAMETER_PROBLEM, "Bad packet size '%s'", pktsize); info->pktsize=size; free(buffer); } static void pktsize_parse(struct xt_option_call *cb) { struct xt_pktsize_info *info = cb->data; xtables_option_parse(cb); parse_pktsize(cb->arg, info); if (cb->invert) info->invert = 1; } static void print_pktsize(const struct xt_pktsize_info *info) { unsigned int i; printf("%d", info->pktsize); /* in case we didn't find an entry in named-packtes */ } static void pktsize_print(const void *ip, const struct xt_entry_match *match, int numeric) { const struct xt_pktsize_info *info = (const void *)match->data; printf(" pktsize %s= ", info->invert ? "!" : ""); print_pktsize(info); } static void pktsize_save(const void *ip, const struct xt_entry_match *match) { const struct xt_pktsize_info *info = (const void *)match->data; printf("%s --pkt-type ", info->invert ? " !" : ""); print_pktsize(info); } static struct xtables_match pktsize_match = { .family = NFPROTO_UNSPEC, .name = "pktsize", .version = XTABLES_VERSION, .size = XT_ALIGN(sizeof(struct xt_pktsize_info)), .userspacesize = XT_ALIGN(sizeof(struct xt_pktsize_info)), .help = pktsize_help, .print = pktsize_print, .save = pktsize_save, .x6_parse = pktsize_parse, .x6_options = pktsize_opts, }; void _init(void) { xtables_register_match(&pktsize_match); } |
通過程式碼我們看到主要工作就是初始化struct xtables_match,然後註冊而已. 它的核心函式是x6_parse/parse
1 2 3 4 5 6 |
/* Function which parses command options; returns true if it ate an option */ /* entry is struct ipt_entry for example */ int (*parse)(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct xt_entry_match **match); |
或者
1 2 |
/* New parser */ void (*x6_parse)(struct xt_option_call *); |
例子中為 pktsize_parse函式,之前我們已經分析過如何呼叫到某一個match的parse函式.主要解析ipt_entry_match裡data(其實就是解析命令引數賦值給它)
除了parse函式還有options,即解析–XXX的引數需要用到的東西,也需要我們去填寫。
下面看看核心部分:
xt_pktsize.h
1 2 3 4 5 6 7 8 |
#ifndef _XT_PKTSIZE_H #define _XT_PKTSIZE_H struct xt_pktsize_info { int pktsize; int invert; }; #endif /*_XT_PKTSIZE_H*/ |
xt_pktsize.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#include <linux/module.h> #include <linux/skbuff.h> #include <linux/if_ether.h> #include <linux/if_packet.h> #include <linux/in.h> #include <linux/ip.h> #include <linux/ipv6.h> //#include <linux/netfilter/xt_pktsize.h> #include "xt_pktsize.h" #include <linux/netfilter/x_tables.h> MODULE_LICENSE("GPL"); MODULE_AUTHOR("jack chen"); MODULE_DESCRIPTION("Xtables: link layer packet size match"); MODULE_ALIAS("ipt_pktsize"); MODULE_ALIAS("ip6t_pktsize"); static bool pktsize_mt(const struct sk_buff *skb, struct xt_action_param *par) { const struct xt_pktsize_info *info = par->matchinfo; u_int32_t size; const struct iphdr *iph = ip_hdr(skb); size=ntohs(iph->tot_len) - (iph->ihl*4); printk("ip data pktsize is %d......,%d..,proto :%x\n",size,info->pktsize,iph->protocol); return (info->pktsize < size) ; //return 1; } static struct xt_match pktsize_mt_reg __read_mostly = { .name = "pktsize", .revision = 0, .family = NFPROTO_UNSPEC, .match = pktsize_mt, .matchsize = sizeof(struct xt_pktsize_info), .me = THIS_MODULE, }; static int __init pktsize_mt_init(void) { return xt_register_match(&pktsize_mt_reg); } static void __exit pktsize_mt_exit(void) { xt_unregister_match(&pktsize_mt_reg); } module_init(pktsize_mt_init); module_exit(pktsize_mt_exit); |
這裡我們計算ip報文的有效載荷的長度即ip包的總長度-ip頭的長度
size=ntohs(iph->tot_len) – (iph->ihl*4);
同樣或許我們還需要判斷協議型別 protocal字等,簡單說幾個常用:
Decimal Keyword Protocol References
——- ————— ————————————— ——————
1 ICMP Internet Control Message [RFC792]
6 TCP Transmission Control [RFC793]
17 UDP User Datagram [RFC768][JBP]
…
ip報文格式如下:
版本:佔4位(bit),指IP協議的版本號。目前的主要版本為IPV4,即第4版本號,也有一些教育網和科研機構在使用IPV6。在進行通訊時,通訊雙方的IP協議版本號必須一致,否則無法直接通訊。
首部長度:佔4位(bit),指IP報文頭的長度。最大的長度(即4個bit都為1時)為15個長度單位,每個長度單位為4位元組(TCP/IP標準,DoubleWord),所以IP協議報文頭的最大長度為60個位元組,最短為上圖所示的20個位元組。
服務型別:佔8位(bit),用來獲得更好的服務。其中的前3位表示報文的優先順序,後面的幾位分別表示要求更低時延、更高的吞吐量、更高的可靠性、更低的路由代價等。對應位為1即有相應要求,為0則不要求。
總長度:16位(bit),指報文的總長度。注意這裡的單位為位元組,而不是4位元組,所以一個IP報文的的最大長度為65535個位元組。
標識(identification):該欄位標記當前分片為第幾個分片,在資料包重組時很有用。
標誌(flag):該欄位用於標記該報文是否為分片(有一些可能不需要分片,或不希望分片),後面是否還有分片(是否是最後一個分片)。
片偏移:指當前分片在原資料包(分片前的資料包)中相對於使用者資料欄位的偏移量,即在原資料包中的相對位置。
生存時間:TTL(Time to Live)。該欄位表明當前報文還能生存多久。每經過1ms或者一個閘道器,TTL的值自動減1,當生存時間為0時,報文將被認為目的主機不可到達而丟棄。使用過Ping命令的使用者應該有印象,在windows中輸入ping命令,在返回的結果中即有TTL的數值。
協議:該欄位指出在上層(網路7層結構或TCP/IP的傳輸層)使用的協議,可能的協議有UDP、TCP、ICMP、IGMP、IGP等。
首部校驗和:用於檢驗IP報文頭部在傳播的過程中是否出錯,主要校驗報文頭中是否有某一個或幾個bit被汙染或修改了。
源IP地址:32位(bit),4個位元組,每一個位元組為0~255之間的整數,及我們日常見到的IP地址格式。
目的IP地址:32位(bit),4個位元組,每一個位元組為0~255之間的整數,及我們日常見到的IP地址格式。
當然對ip理解的越深刻越好了,那麼這樣就完成了一個簡單的match擴充套件的例子,僅僅作為拋磚引玉,一個小小的開始.