通過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);
- }
點擊(此處)摺疊或打開
- 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;
- }
點擊(此處)摺疊或打開
- 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();
- }
點擊(此處)摺疊或打開
- /* 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;
- }