A-A+

有关基于应用层协议的路由的一点不成熟的想法

2015年11月25日 站长资讯 暂无评论

一、需求

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

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

如下图所示,具体要求是:来自主机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地址功能的主要部分:

  1. static int parse_to(char *arg, int portok, struct nf_nat_range *p_range)  
  2. {  
  3.     struct nf_nat_range range;//这个就是保存源地址的东西  
  4.     char *colon, *dash, *error;  
  5.     const struct in_addr *ip;  
  6.   
  7.     memset(&range, 0, sizeof(range));  
  8.     colon = strchr(arg, ':');  
  9.   
  10.     if (colon)   
  11.     {  
  12.         int port;  
  13.         if (!portok)  
  14.         xtables_error(PARAMETER_PROBLEM, "Need TCP, UDP, SCTP or DCCP with port specification");  
  15.         range.flags |= IP_NAT_RANGE_PROTO_SPECIFIED;  
  16.   
  17.         port = atoi(colon+1);  
  18.         if (port <= 0 || port > 65535)  
  19.             xtables_error(PARAMETER_PROBLEM,"Port `%s' not valid\n", colon+1);  
  20.   
  21.          error = strchr(colon+1, ':');  
  22.         if (error)  
  23.             xtables_error(PARAMETER_PROBLEM,"Invalid port:port syntax - use dash\n");  
  24.         dash = strchr(colon, '-');  
  25.         if (!dash)   
  26.         {  
  27.             rangerange.min.tcp.port= range.max.tcp.porthtons(port);  
  28.         } else {  
  29.             int maxport;  
  30.             maxport = atoi(dash + 1);  
  31.             if (maxport <= 0 || maxport > 65535)  
  32.                 xtables_error(PARAMETER_PROBLEM, "Port `%s' not valid\n", dash+1);   
  33.             if (maxport < port)  
  34.                 xtables_error(PARAMETER_PROBLEM,"Port range `%s' funky\n", colon+1);  
  35.             range.min.tcp.port = htons(port);  
  36.             range.max.tcp.port = htons(maxport);  
  37.         }  
  38.         if (colon == arg)  
  39.         {  
  40.             *p_range=range;  
  41.             return 1;  
  42.         }  
  43.         *colon = '\0';  
  44.     }  
  45.   
  46.     range.flags |= IP_NAT_RANGE_MAP_IPS;  
  47.     dash = strchr(arg, '-');  
  48.     if (colon && dash && dash > colon)  
  49.     dash = NULL;  
  50.   
  51.     if (dash)  
  52.         *dash = '\0';  
  53.   
  54.     ip = xtables_numeric_to_ipaddr(arg);  
  55.     if (!ip)  
  56.         xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n", arg);  
  57.     range.min_ip = ip->s_addr;  
  58.     if (dash)   
  59.     {  
  60.         ip = xtables_numeric_to_ipaddr(dash+1);  
  61.         if (!ip)  
  62.             xtables_error(PARAMETER_PROBLEM, "Bad IP address \"%s\"\n",dash+1);  
  63.         range.max_ip = ip->s_addr;  
  64.     } else  
  65.     rangerange.max_ip = range.min_ip;  
  66.     *p_range=range;  
  67.     return 2;//snat ip  
  68. }  

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

  1. nat = nfct_nat(conntrack);  
  2. if (!nat)   
  3. {  
  4.   
  5.   
  6.     if (nf_ct_is_confirmed(conntrack))  
  7.     return (pattern_result ^ info->invert);  
  8.     nat = nf_ct_ext_add(conntrack, NF_CT_EXT_NAT, GFP_ATOMIC);  
  9.     if (nat == NULL)   
  10.     {  
  11.         printk(KERN_ERR "failed to add NAT extension\n\n");  
  12.         return (pattern_result ^ info->invert);  
  13.      } http://www.luyouqiwang.com/  
  14. }  
  15. if (ip_hdr(skb)->protocol == IPPROTO_ICMP)   
  16. {  
  17.     return (pattern_result ^ info->invert);  
  18. }  
  19. enum nf_nat_manip_type  
  20. {  
  21.     IP_NAT_MANIP_SRC,  
  22.     IP_NAT_MANIP_DST  
  23. maniptype = IP_NAT_MANIP_SRC;  
  24. if (!nf_nat_initialized(conntrack, maniptype))   
  25. {//没有处理过  
  26.     struct net *net = nf_ct_net(conntrack);  
  27.     struct nf_conntrack_tuple curr_tuple, new_tuple;  
  28.     nf_ct_invert_tuplepr(&curr_tuple,&conntrack->tuplehash[IP_CT_DIR_REPLY].tuple);  
  29.     get_unique_tuple(&new_tuple, &curr_tuple, &range, conntrack, maniptype);  
  30.     if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple))   
  31.     {  
  32.         struct nf_conntrack_tuple reply;  
  33.   
  34.         nf_ct_invert_tuplepr(&reply, &new_tuple);  
  35.         nf_conntrack_alter_reply(conntrack, &reply);//修改nat的回复tuple  
  36.   
  37.         conntrack->status |= IPS_SRC_NAT;  
  38.     }  
  39.     if (maniptype == IP_NAT_MANIP_SRC)   
  40.     {//插入bysource函数  
  41.         unsigned int srchash;  
  42.   
  43.         srchash = hash_by_src(net, &conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple);  
  44.         spin_lock_bh(&nf_nat_lock);  
  45.         // nf_conntrack_alter_reply该函数可能会重新分配空间.  
  46.         nat = nfct_nat(conntrack);  
  47.         nat->ct = conntrack;  
  48.         hlist_add_head_rcu(&nat->bysource,&net->ipv4.nat_bysource[srchash]);  
  49.         spin_unlock_bh(&nf_nat_lock);  
  50.     }  
  51.     set_bit(IPS_SRC_NAT_DONE_BIT, &conntrack->status);  
  52. }   
  53. else  
  54.     printk(KERN_ERR "Already setup manip \n\n");  
  55. nf_nat_packet(conntrack, ctinfo, hooknum, skb);  

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

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

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

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

标签:

给我留言