對於常規的iptables match或者target擴充套件肯定不能滿足我們的需要,並且預設iptables也只識別到五元組,在深入識別已經很吃力了.顯然在實際的需求面前,我們不會止步於此.下面就講講iptables功能擴充套件的外掛,支援Layer7.
在Linux的防火牆體系Netfilter下有一個獨立的模組L7 filter 。從字面上看Netfilter是對網路資料的過濾,L7 filter是基於資料流應用層內容的過濾。不過實際上 L7 filter的本職工作不是對資料流進行過濾而是對資料流進行分類。它使用模式匹配演算法把進入裝置的資料包應用層內容與事先定義好的協議規則進行比對,如果匹配成功就說明這個資料包屬於某種協議。
L7 filter是基於資料流工作的,建立在Netfilter connstrack功能之上。因為一個資料流或者說一個連線的所有資料都是屬於同一個應用的,所以L7 filter沒有必要對所以的資料包進行模式匹配,而只匹配一個流的前面幾個資料包 (比如10個資料包)。當一個流的前面幾個資料包包含了某種應用層協議的特徵碼時 (比如QQ),則這個資料流被L7 filter識別;當前面幾個資料包的內容沒有包含某種應用層協議的特徵碼時,則L7 filter放棄繼續做模式匹配,這個資料流也就沒有辦法被識別.
準備工作,需要下載netfilter-layer7-v2.22和l7-protocols 網址: http://l7-filter.sourceforge.net/
它的工作和之前註冊match流程是一樣的 需要使用者空間註冊match 和核心的註冊.
先看看使用者空間:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
static struct xtables_match layer7 = { .family = AF_INET, .name = "layer7", .version = XTABLES_VERSION, .size = XT_ALIGN(sizeof(struct xt_layer7_info)), .userspacesize = XT_ALIGN(sizeof(struct xt_layer7_info)), .help = &help, .parse = &parse, .final_check = &final_check, .print = &print, .save = &save, .extra_opts = opts }; |
再看看opts:
1 2 3 4 5 |
static const struct option opts[] = { { .name = "l7proto", .has_arg = 1, .val = 'p' }, { .name = "l7dir", .has_arg = 1, .val = 'd' }, { .name = NULL } }; |
主要還是看parse函式:
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 |
/* Function which parses command options; returns true if it ate an option */ static int parse(int c, char **argv, int invert, unsigned int *flags, const void *entry, struct xt_entry_match **match) { struct xt_layer7_info *layer7info = (struct xt_layer7_info *)(*match)->data; switch (c) { case 'p': parse_layer7_protocol(argv[optind-1], layer7info); if (invert) layer7info->invert = true; *flags = 1; break; case 'd': if(strlen(argv[optind-1]) >= MAX_FN_LEN) xtables_error(PARAMETER_PROBLEM, "directory name too long\n"); strncpy(l7dir, argv[optind-1], MAX_FN_LEN); *flags = 1; break; default: return 0; } return 1; } |
由於它是支援到iptable1.4.3 和核心2.6.28的,如果現在比較新的版本需要自己對應修改部分程式碼.
這裡需要註冊的是struct xt_layer7_info :
1 2 3 4 5 6 7 |
#define MAX_PATTERN_LEN 8192 #define MAX_PROTOCOL_LEN 256 struct xt_layer7_info { char protocol[MAX_PROTOCOL_LEN]; char pattern[MAX_PATTERN_LEN]; u_int8_t invert; }; |
我們拿一個實際的例子說明:
#iptables -t nat -A POSTROUTING -m layer7 –17proto qq -j DROP
parse函式的作用就是解析引數後,讀取特徵碼pat檔案資訊匹配,然後賦值給xt_layer7_info
通過parse_layer7_protocol:預設pat檔案放在/etc/l7-protocols ,也可以自己指定.
看qq.pat:
#…
# that.”
# So now the pattern allows any of the first three bytes to be 02. Delete
# one of the “.?” to restore to the old behaviour.
# pattern written by www.routerclub.com wsgtrsys
qq
^.?.?\x02.+\x03$
即:protocol =“qq”;pattern=“^.?.?\x02.+\x03$”
我們可以看到協議識別的資訊是一串正規表示式顯然不能直接用.
對於layer7它只支援ip報文,協議只支援tcp、udp和icmp:
1 2 3 4 5 6 7 8 9 10 |
static int can_handle(const struct sk_buff *skb) { if(!ip_hdr(skb)) /* not IP */ return 0; if(ip_hdr(skb)->protocol != IPPROTO_TCP && ip_hdr(skb)->protocol != IPPROTO_UDP && ip_hdr(skb)->protocol != IPPROTO_ICMP) return 0; return 1; } |
下面看看核心部分:
1 2 3 4 5 6 7 8 9 10 |
static struct xt_match xt_layer7_match[] __read_mostly = { { .name = "layer7", .family = AF_INET, .checkentry = check, .match = match, .destroy = destroy, .matchsize = sizeof(struct xt_layer7_info), .me = THIS_MODULE } |
核心中的工作就是呼叫match解析正規表示式.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
struct xt_action_param { union { const struct xt_match *match; const struct xt_target *target; }; union { const void *matchinfo, *targinfo; }; const struct net_device *in, *out; int fragoff; unsigned int thoff; unsigned int hooknum; u_int8_t family; bool hotdrop; }; |
1.首先獲取使用者傳的資訊:const struct xt_layer7_info * info =par->matchinfo;
2.判斷是否是支援的協議型別(ip —> tcp/udp/icmp)
3.獲取當前連線ct和master ct資訊
4. 判斷skb是否線性
5.找到應用資料的地址:
1 2 3 |
4. /* now that the skb is linearized, it's safe to set these. */ 5. app_data = skb->data + app_data_offset(skb); 6. appdatalen = skb_tail_pointer(skb) - app_data; |
6.利用regcomp編譯傳遞的字串正規表示式到regexp結構體中
1 2 3 4 5 6 7 8 9 10 |
#define NSUBEXP 10 typedef struct regexp { char *startp[NSUBEXP]; char *endp[NSUBEXP]; char regstart; /* Internal use only. */ char reganch; /* Internal use only. */ char *regmust; /* Internal use only. */ int regmlen; /* Internal use only. */ char program[1]; /* Unwarranted chumminess with compiler. */ } regexp; |
7.利用total_acct_packets計算是否為第一個來的報文,如果是申請app_data空間給ct裡的layer7欄位
8.判斷skb->cb 是否為null,為空則附加data到主ct的layer7.app_data,如果有下個報文處理則追加data.(追加的條件是什麼呢?) ,(當skb附加data後會設定skb->cb[0]=1)
9.利用regexec判斷匹配
10.設定skb->cb[0]=1;然後返回
這裡我們特別說明下第三個:
關於ct->master的問題 這裡涉及expt 即期望連線的問題,稍微回顧一下,在新建一個ct的時候會查詢expect hlist看看是否是某一個ct期望的連線.
1 2 3 4 5 6 7 |
exp = nf_ct_find_expectation(net, zone, tuple); if (exp) { pr_debug("conntrack: expectation arrives ct=%p exp=%p\n", ct, exp); /* Welcome, Mr. Bond. We've been expecting you... */ __set_bit(IPS_EXPECTED_BIT, &ct->status); ct->master = exp->master; |
還有一個之前說的只匹配前幾個包的問題(算是一個隱式bug):
1 2 3 |
/* if we've classified it or seen too many packets */ if(total_acct_packets(master_conntrack) > num_packets || master_conntrack->layer7.app_proto) { |
具體的編譯和安裝測試這裡不再說明.當然我們會發現這個layer7並不完美,還有許多問題需要去解決.