做过P2P通信的人应该都会用到这个技术,udp打洞。通过udp打洞,可以实现两个nat后的主机进行直接通信。
wiki中关于udp打洞的描述:
通过UDP打洞实现NAT穿越是一种在处于使用了NAT的私有网络中的Internet主机之间建立双向UDP连接的方法。由于NAT的行为是非标准化的,因此它并不能应用于所有类型的NAT。
其基本思想是这样的:让位于NAT后的两台主机都与处于公共地址空间的、众所周知的第三台服务器相连,然后,一旦NAT设备建立好UDP状态信息就转为直接通信,并寄希望于NAT设备会在分组其实是从另外一个主机传送过来的情况下仍然保持当前状态。
这项技术需要一个圆锥型NAT设备才能够正常工作。对称型NAT不能使用这项技术。
这项技术在P2P软件和VoIP电话领域被广泛采用。它是Skype用以绕过防火墙和NAT设备的技术之一。
相同的技术有时还被用于TCP连接——尽管远没有UDP成功。
UDP打洞的过程大体上如下:
主机A和主机B都是通过NAT设备访问互联网,主机S位于互联网上。
1. A和B都与S之间通过UDP进行心跳连接
2. A通知S,要与B通信
3. S把B的公网IP、port告诉A,同时把A的公网IP、port告诉B
4. A向B的公网IP、port发送数据(这个数据包应该会被丢弃,但是打开了B回来的窗户)
5. B向A的公网IP、port发送数据(这个数据包就会被A接受,之后A和B就建立起了连接)
上述能够正常工作的前提的,A连接S和连接B的时候,NAT设备对A做地址转换的时候,需要选择相同的IP、port。
比如:A--->NATA--->S
A--->NATA--->NATB-->B
那么NATA对A进行NAT的时候,需要选择相同的IP和port进行SNAT。
如果使用Linux作为防火墙,那么非常幸运,Linux就是这么搞的
现在我们来看看linux是如何实现的:
linux通过SNAT netfilter target 进行源地址转换,源码位于net/ipv4/netfilter/nf_nat_rule.c
-
/* Source NAT */
-
static unsigned int
-
ipt_snat_target(struct sk_buff *skb, const struct xt_target_param *par)
-
{
-
struct nf_conn *ct;
-
enum ip_conntrack_info ctinfo;
-
const struct nf_nat_multi_range_compat *mr = par->targinfo;
-
-
NF_CT_ASSERT(par->hooknum == NF_INET_POST_ROUTING);
-
-
ct = nf_ct_get(skb, &ctinfo);
-
-
/* Connection must be valid and new. */
-
NF_CT_ASSERT(ct && (ctinfo == IP_CT_NEW || ctinfo == IP_CT_RELATED ||
-
ctinfo == IP_CT_RELATED + IP_CT_IS_REPLY));
-
NF_CT_ASSERT(par->out != NULL);
-
-
return nf_nat_setup_info(ct, &mr->range[0], IP_NAT_MANIP_SRC);
-
}
这里实际上调用函数nf_nat_setup_info(net/ipv4/netfilter/nf_nat_core.c)
-
unsigned int
-
nf_nat_setup_info(struct nf_conn *ct,
-
const struct nf_nat_range *range,
-
enum nf_nat_manip_type maniptype)
-
{
-
struct net *net = nf_ct_net(ct);
-
struct nf_conntrack_tuple curr_tuple, new_tuple;
-
struct nf_conn_nat *nat;
-
int have_to_hash = !(ct->status & IPS_NAT_DONE_MASK);
-
-
/* nat helper or nfctnetlink also setup binding */
-
nat = nfct_nat(ct);
-
if (!nat) {
-
nat = nf_ct_ext_add(ct, NF_CT_EXT_NAT, GFP_ATOMIC);
-
if (nat == NULL) {
-
pr_debug("failed to add NAT extension\n");
-
return NF_ACCEPT;
-
}
-
}
-
-
NF_CT_ASSERT(maniptype == IP_NAT_MANIP_SRC ||
-
maniptype == IP_NAT_MANIP_DST);
-
BUG_ON(nf_nat_initialized(ct, maniptype));
-
-
/* What we've got will look like inverse of reply. Normally
-
this is what is in the conntrack, except for prior
-
manipulations (future optimization: if num_manips == 0,
-
orig_tp =
-
conntrack->tuplehash[IP_CT_DIR_ORIGINAL].tuple) */
-
nf_ct_invert_tuplepr(&curr_tuple,
-
&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
-
-
get_unique_tuple(&new_tuple, &curr_tuple, range, ct, maniptype);
-
-
if (!nf_ct_tuple_equal(&new_tuple, &curr_tuple)) {
-
struct nf_conntrack_tuple reply;
-
-
/* Alter conntrack table so will recognize replies. */
-
nf_ct_invert_tuplepr(&reply, &new_tuple);
-
nf_conntrack_alter_reply(ct, &reply);
-
-
/* Non-atomic: we own this at the moment. */
-
if (maniptype == IP_NAT_MANIP_SRC)
-
ct->status |= IPS_SRC_NAT;
-
else
-
ct->status |= IPS_DST_NAT;
-
}
-
-
/* Place in source hash if this is the first time. */
-
if (have_to_hash) {
-
unsigned int srchash;
-
-
srchash = hash_by_src(net, &ct->tuplehash[IP_CT_DIR_ORIGINAL].tuple);
-
spin_lock_bh(&nf_nat_lock);
-
/* nf_conntrack_alter_reply might re-allocate exntension aera */
-
nat = nfct_nat(ct);
-
nat->ct = ct;
-
hlist_add_head_rcu(&nat->bysource,
-
&net->ipv4.nat_bysource[srchash]);
-
spin_unlock_bh(&nf_nat_lock);
-
}
-
-
/* It's done. */
-
if (maniptype == IP_NAT_MANIP_DST)
-
set_bit(IPS_DST_NAT_DONE_BIT, &ct->status);
-
else
-
set_bit(IPS_SRC_NAT_DONE_BIT, &ct->status);
-
-
return NF_ACCEPT;
-
}
这里第33行,进行snat的端口和ip,核心就在这个函数里面
-
static void
-
get_unique_tuple(struct nf_conntrack_tuple *tuple,
-
const struct nf_conntrack_tuple *orig_tuple,
-
const struct nf_nat_range *range,
-
struct nf_conn *ct,
-
enum nf_nat_manip_type maniptype)
-
{
-
struct net *net = nf_ct_net(ct);
-
const struct nf_nat_protocol *proto;
-
-
/* 1) If this srcip/proto/src-proto-part is currently mapped,
-
and that same mapping gives a unique tuple within the given
-
range, use that.
-
-
This is only required for source (ie. NAT/masq) mappings.
-
So far, we don't do local source mappings, so multiple
-
manips not an issue. */
-
if (maniptype == IP_NAT_MANIP_SRC &&
-
!(range->flags & IP_NAT_RANGE_PROTO_RANDOM)) {
-
if (find_appropriate_src(net, orig_tuple, tuple, range)) {
-
pr_debug("get_unique_tuple: Found current src map\n");
-
if (!nf_nat_used_tuple(tuple, ct))
-
return;
-
}
-
}
-
-
/* 2) Select the least-used IP/proto combination in the given
-
range. */
-
*tuple = *orig_tuple;
-
find_best_ips_proto(tuple, range, ct, maniptype);
-
-
/* 3) The per-protocol part of the manip is made to map into
-
the range to make a unique tuple. */
-
-
rcu_read_lock();
-
proto = __nf_nat_proto_find(orig_tuple->dst.protonum);
-
-
/* Change protocol info to have some randomization */
-
if (range->flags & IP_NAT_RANGE_PROTO_RANDOM) {
-
proto->unique_tuple(tuple, range, maniptype, ct);
-
goto out;
-
}
-
-
/* Only bother mapping if it's not already in range and unique */
-
if ((!(range->flags & IP_NAT_RANGE_PROTO_SPECIFIED) ||
-
proto->in_range(tuple, maniptype, &range->min, &range->max)) &&
-
!nf_nat_used_tuple(tuple, ct))
-
goto out;
-
-
/* Last change: get protocol to try to obtain unique tuple. */
-
proto->unique_tuple(tuple, range, maniptype, ct);
-
out:
-
rcu_read_unlock();
-
}
这个函数的第20行,就是一个选择的原则。如果这个port、ip、protocol已经被snat过,那么这次snat优先选择之前使用的ip和port
-
/* Only called for SRC manip */
-
static int
-
find_appropriate_src(struct net *net,
-
const struct nf_conntrack_tuple *tuple,
-
struct nf_conntrack_tuple *result,
-
const struct nf_nat_range *range)
-
{
-
unsigned int h = hash_by_src(net, tuple);
-
const struct nf_conn_nat *nat;
-
const struct nf_conn *ct;
-
const struct hlist_node *n;
-
-
rcu_read_lock();
-
hlist_for_each_entry_rcu(nat, n, &net->ipv4.nat_bysource[h], bysource) {
-
ct = nat->ct;
-
if (same_src(ct, tuple)) {
-
/* Copy source part from reply tuple. */
-
nf_ct_invert_tuplepr(result,
-
&ct->tuplehash[IP_CT_DIR_REPLY].tuple);
-
result->dst = tuple->dst;
-
-
if (in_range(result, range)) {
-
rcu_read_unlock();
-
return 1;
-
}
-
}
-
}
-
rcu_read_unlock();
-
return 0;
-
}
阅读(902) | 评论(0) | 转发(0) |