Chinaunix首页 | 论坛 | 博客
  • 博客访问: 165560
  • 博文数量: 43
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 675
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-26 00:58
文章分类
文章存档

2014年(2)

2013年(41)

我的朋友

分类: C/C++

2014-02-25 00:23:14

一、需求 

采用基于应用协议的策略路由。

我们把这个功能简称为应用路由。

如下图所示,具体要求是:来自主机1~4的数据包,在应用路由进行处理,并根据不同的应用层协议进行策略路由,比如将所有的P2P下载和网路电视分流到传统网络,将ssh这类安全协议的数据包分流到量子网络的接口。

要实现这个功能,首要前提是协议识别率必须精准,所以应用层协议的识别和标记是这个功能的关键。

二、实现方案

思路:利用的已经开发好的开源模块l7-filter。该模块利用netfilter,实现了应用层协议的识别,可以对每个属于该协议的数据包进行标记,然后根据标记进行路由。

 

存在问题:上路的方法可以保证,当协议包被识别的时候可以进行正确的路由,但是无法正确的进行nat。这就要追溯到netfilter的nat实现方式了。


引发问题的原因:Netfilter采用了连接跟踪机制,简单的说就是通过源地址、目的地址(如果是tcp或者是udp协议会包括端口信息)等信息,来记录一条连接,所有源地址和目的地址满足该连接的数据包都在该连接上。

Netfilter的nat是基于连接跟踪机制的。首先对该连接上的第一个数据包用match函数进行匹配,如果满足要求就执行nat。此后该连接的数据包按照第一个包进行处理,不再进行分析。

这种情况下,如果某种应用层协议无法仅仅通过第一个数据包识别,那么就无法进行正确的nat。比如要求http协议从WAN2转发出去,其他的应用层协议从WAN1转发。http协议首先需要建立tcp连接,那么前几个数据包就是无应用层内容无关的,识别不出来,从WAN2进行转发,并将nat的目的IP填成WAN2对应的IP。当后续的数据包被匹配为http协议,从WAN1转发,由于第一个数据包执行的nat动作时转换成WAN2对应的IP,所以从WAN1发出的数据包的源ID仍是IP2的。这显然是不正确的。

为了实现正确的nat,同时有效的利用现有的协议匹配代码,我们需要在l7-filter的基础上进行修改。


三、l7-filter的工作流程流程

1.结构

利用l7模块的时候,执行了下面的命令

l7-filter主要分为三部分,一部分是protocol,一部分是内核源码,一部分是用户态库文件。

当执行iptables命令的时候,在固定目录下搜索用户态库文件。这些库文件可以用来查看一些帮助文档、进行传入参数分析等(代码在libxt_layer7.c中)。

然后经过iptables模块处理,将满足的参数传入netfilter框架中。(如下图)

然后netfilter调用内核模块进行匹配。


2.流程

内核态的match模块的实现位于xt_layer7.c中,大体流程如下图所示。

四、基于l7-filter的修改。

思路:由于nat是针对一条连接而言的,l7-filter的应用层识别会匹配到每个报文,所以我们考虑对每个匹配的报文进行单独的nat。图中蓝色的部分是针对l7-filter进行修改的部分。

同时还需要在libxt_layer7.c中添加获取nat地址的功能。



用户态模块获取nat地址功能的主要部分:

static int parse_to(char *arg, int portok, struct nf_nat_range *p_range)

{

    struct nf_nat_range range;//这个就是保存源地址的东西

    char *colon, *dash, *error;

    const struct in_addr *ip;


    memset(&range, 0, sizeof(range));

    colon = strchr(arg, ':');


    if (colon) 
    {

        int port;

        if (!portok)

        xtables_error(PARAMETER_PROBLEM, "Need TCP, UDP, SCTP or DCCP with port specification");

        range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED;


        port = atoi(colon+1);

        if (port <= 0 || port > 65535)

            xtables_error(PARAMETER_PROBLEM,"Port `%s' not valid\n", colon+1);


         error = strchr(colon+1, ':');

        if (error)

            xtables_error(PARAMETER_PROBLEM,"Invalid port:port syntax - use dash\n");

        dash = strchr(colon, '-');

        if (!dash) 
        {

            range.min.tcp.port= range.max.tcp.port= htons(port);

        } else {

            int maxport;

            maxport = atoi(dash + 1);

            if (maxport <= 0 || maxport > 65535)

                xtables_error(PARAMETER_PROBLEM, "Port `%s' not valid\n", dash+1);

            if (maxport < port)
                xtables_error(PARAMETER_PROBLEM,"Port range `%s' funky\n", colon+1);

            range.min.tcp.port = htons(port);

            range.max.tcp.port = htons(maxport);
        }

        if (colon == arg)

        {

            *p_range=range;

            return 1;

        }

        *colon = '\0';

    }


    range.flags |= IP_NAT_RANGE_MAP_IPS;

    dash = strchr(arg, '-');

    if (colon && dash && dash > colon)

    dash = NULL;


    if (dash)

        *dash = '\0';


    ip = xtables_numeric_to_ipaddr(arg);

    if (!ip)

        xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n", arg);

    range.min_ip = ip->s_addr;

    if (dash) 
    {

        ip = xtables_numeric_to_ipaddr(dash+1);

        if (!ip)

            xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",dash+1);

        range.max_ip = ip->s_addr;

    } else

    range.max_ip = range.min_ip;

    *p_range=range;

    return 2;//snat ip

}

内核态match中nat功能的实现:

nat = nfct_nat(conntrack);

if (!nat) 

{

    if (nf_ct_is_confirmed(conntrack))

    return (pattern_result ^ info->invert);

    nat = nf_ct_ext_add(conntrack, NF_CT_EXT_NAT, GFP_ATOMIC);

    if (nat == NULL) 

    {

        printk(KERN_ERR "failed to add NAT extension\n\n");

        return (pattern_result ^ info->invert);

     }

}

if (ip_hdr(skb)->protocol == IPPROTO_ICMP) 

{

    return (pattern_result ^ info->invert);

}

enum nf_nat_manip_type

{

    IP_NAT_MANIP_SRC,

    IP_NAT_MANIP_DST

} maniptype = IP_NAT_MANIP_SRC;

if (!nf_nat_initialized(conntrack, maniptype)) 

{//没有处理过

    struct net *net = nf_ct_net(conntrack);

    struct nf_conntrack_tuple curr_tuple, new_tuple;

    nf_ct_invert_tuplepr(&curr_tuple,&conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);

    get_unique_tuple(&new_tuple, &curr_tuple, &range, conntrack, maniptype);


    if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) 

    {

        struct nf_conntrack_tuple reply;


        nf_ct_invert_tuplepr(&reply, &new_tuple);

        nf_conntrack_alter_reply(conntrack, &reply);//修改nat的回复tuple


        conntrack->status |= IPS_SRC_NAT;

    }

    if (maniptype == IP_NAT_MANIP_SRC) 

    {//插入bysource函数

        unsigned int srchash;


        srchash = hash_by_src(net, &conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple);

        spin_lock_bh(&nf_nat_lock);

        // nf_conntrack_alter_reply该函数可能会重新分配空间.

        nat = nfct_nat(conntrack);

        nat->ct = conntrack;

        hlist_add_head_rcu(&nat->bysource,&net->ipv4.nat_bysource[srchash]);

        spin_unlock_bh(&nf_nat_lock);
    }

    set_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);

else

    printk(KERN_ERR "Already setup manip \n\n");

nf_nat_packet(conntrack, ctinfo, hooknum, skb);


这里还存在一个问题。有两类情况无法正确实现nat功能。

第一、对于TCP而言,双方建立连接后,该连接的后续数据包以另一个源地址发送到目的地址,目的端会因为无法识别该源地址,返回RST。这样就无法对基于TCP的应用层协议进行nat。

第二、如果基于UDP的协议,在应用层模拟连接,由于与第一个建立连接的报文源IP不同,导致无法失败。

这个问题目前还没有想到很好的解决方法。

阅读(2562) | 评论(0) | 转发(0) |
0

上一篇:typedef 函数指针

下一篇:没有了

给主人留下些什么吧!~~