欢迎来到云服务器

网络技术

iptables防火墙的应用层插件编写详解

    对于常规的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 longn");


        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=%pn",

             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并不完美,还有许多问题需要去解决. 
腾讯云代理

Copyright © 2003-2021 MFISP.COM. 国外vps服务器租用 梦飞云服务器租用 版权所有 粤ICP备11019662号