在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
- };
点击(此处)折叠或打开
- static const struct option opts[] = {
- { .name = "l7proto", .has_arg = 1, .val = 'p' },
- { .name = "l7dir", .has_arg = 1, .val = 'd' },
- { .name = NULL }
- };
点击(此处)折叠或打开
- /* 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;
- }
这里需要注册的是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
^.?.?\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
- }
点击(此处)折叠或打开
- 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;
- };
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;
点击(此处)折叠或打开
- #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;
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;
点击(此处)折叠或打开
- /* if we've classified it or seen too many packets */
- if(total_acct_packets(master_conntrack) > num_packets ||
- master_conntrack->layer7.app_proto) {