分类: 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不同,导致无法失败。
这个问题目前还没有想到很好的解决方法。