對於常規的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 和內核的注冊.
先看看用戶空間:
- 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:
- static const struct option opts[] = {
- { .name = "l7proto", .has_arg = 1, .val = 'p' },
- { .name = "l7dir", .has_arg = 1, .val = 'd' },
- { .name = NULL }
- };
主要還是看parse函數:
- /* 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 :
- #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:
- 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;
- }
下面看看內核部分:
- 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解析正則表達式.
- 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.找到應用數據的地址:
- 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結構體中
- #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期望的連接.
- 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):
- /* if we've classified it or seen too many packets */
- if(total_acct_packets(master_conntrack) > num_packets ||
- master_conntrack->layer7.app_proto) {
具體的編譯和安裝測試這裡不再說明.當然我們會發現這個layer7並不完美,還有許多問題需要去解決.
--------------------------------------分割線 --------------------------------------
CentOS 7.0關閉默認防火牆啟用iptables防火牆 http://www.linuxidc.com/Linux/2015-05/117473.htm
iptables使用范例詳解 http://www.linuxidc.com/Linux/2014-03/99159.htm
Linux防火牆iptables詳細教程 http://www.linuxidc.com/Linux/2013-07/87045.htm
iptables的備份、恢復及防火牆腳本的基本使用 http://www.linuxidc.com/Linux/2013-08/88535.htm
Linux下防火牆iptables用法規則詳解 http://www.linuxidc.com/Linux/2012-08/67952.htm
--------------------------------------分割線 --------------------------------------