UDP穿透NAT的原理与实现

NAT全称Network Address Translator,即网络地址转换,它的作用是解决了计算机内网和外网不能互访的问题。众所周知,内网计算机分配到的是私有IP,形如192.168.x.x,这些IP不允许在外网使用,只有公有IP才能在外网使用,这样内网就无法访问外网, 同样外网也无法访问内网,于是出现了NAT,它将内网计算机数据包中的源IP(内网IP)修改成NAT外网IP,使之可以在外网传输,同时记下映射关系,待外网响应时转发给之前发起请求的内网计算机。



虽然有了NAT,外网计算机仍是不能直接访问内网计算机,必须是内网计算机先发起请求建立映射关系,否则外网计算机发送来的数据包NAT会认为是不安全的直接丢弃,因此穿透NAT本质是在NAT上建立映射关系以便外网计算机能访问内网计算机。

说明:在映射关系中内网IP、内网端口和对方外网IP是一一对应的,其他外网计算机发送的数据包NAT也是丢弃处理,这就好比你告诉了邮局自己的地址以及只收哪些人寄来的邮件,不满足条件的邮局一律丢弃。

为了方便说明,下边统一下名称:

引用内容 引用内容
内网IP:192.168.0.100
内网端口:1111
NAT IP:61.241.223.184
NAT端口:2222
外网IP:218.85.157.99

NAT的种类

1.NAT

早期的NAT进行网络地址转换时,只修改数据包中的源IP成NAT IP,不修改源端口(NAT端口和内网端口相同),并且同一时间只有一台内网计算机可以使用外网IP,有什么局限性你懂的。

2.NAPT

NAPT全称Network Address/Port Translator,即网络地址/端口转换,它在进行转换时,不仅修改数据包的源IP成NAT IP,还修改源端口(NAT和内网端口不一定相同),同一时间允许多台内网计算机使用外网IP。NAPT又分成了Symmetric NAT和Cone NAT,它们的区别是同一台内网计算机同一端口向不同的外网计算机发起请求时,对于修改后的源端口(NAT端口),前者使用不同的端口号,后者使用相同的端口号,我们现在说的NAT默认都指Cone NAT。

Cone NAT的作用

不同用户通常处在不同的NAT下,如果他们之间要进行访问,前提条件是双方知道彼此的NAT IP及NAT端口并事先进行设置,否则访问无法进行,这实现起来很难,所以通常是借助一台服务器来完成,过程大致是这样的:用户A和B安装了软件客户端,客户端A连接服务器登记自己的NAT IP和NAT端口,客户端B也连接服务器登记自己的NAT IP和NAT端口,客户端A要发送数据给客户端B时,先从服务器取得B的NAT IP和NAT端口,同时让服务器通知B建立和自己的映射关系,最后A给B发送数据即可成功。

这种机制下,要求服务器和客户端B使用相同的NAT端口给客户端A发送数据,在Symmetric NAT网络结构下A给B发送数据时启用了新的NAT端口,而B使用服务器登记的NAT端口给A发送数据只会被丢弃,所以只有Cone NAT才能满足要求,目前大部分NAT设备也都使用Cone NAT。

Cone NAT又分成三种类型:

①.FULL CONE:所有从同一个内网IP和端口的请求都被映射到同一个外网IP和端口上,但是,任何外网主机可以通过NAT IP和NAT端口向此内网主机发送数据包。

②.Restricted Cone:与FULL CONE方式不同的是,只有先前向其发送过数据包的外网主机可以通过NAT IP和NAT端口向此内网主机发送数据包。

③.Port Restricted Cone:在Restricted Cone基础上增加对外网主机端口号的限制,只有先前向其发送过数据包的外网主机及端口可以通过NAT IP和NAT端口向此内网主机发送数据包。

C#实现UDP穿透NAT

1.服务器端

static void Server(int port)
{
    UdpClient udpclient = new UdpClient(port);
    while (true)
    {              
        //1.接收
        IPEndPoint remote = null;
        byte[] rebytes = udpclient.Receive(ref remote);
        string redata = Encoding.Default.GetString(rebytes, 0, rebytes.Length);
        Console.WriteLine("{0:HH:mm:ss}->接收数据(from {1}:{2}):{3}", DateTime.Now, remote.Address, remote.Port, redata);
        //2.发送
        byte[] sedata = Encoding.Default.GetBytes("接收到一条消息:");
        udpclient.Send(sedata, sedata.Length, remote);
        sedata = Encoding.Default.GetBytes(redata);
        udpclient.Send(sedata, sedata.Length, remote);                
    }
    udpclient.Close();
}

2.客户端

static void Client(string ip, int port, string message)
{
    UdpClient udpclient = new UdpClient(); //由系统分配端口
    //1.发送
    IPEndPoint remote = new IPEndPoint(IPAddress.Parse(ip), port);
    byte[] sedata = Encoding.Default.GetBytes(message);
    udpclient.Send(sedata, sedata.Length, remote);
    Console.WriteLine("{0:HH:mm:ss}->发送数据(to {1}):{2}", DateTime.Now, ip, message);
    //2.接收(小技巧:先发送再接收使UdpClient发送接收用同一端口)
    while (true)
    {
        IPEndPoint remote2 = null;
        byte[] rebytes = udpclient.Receive(ref remote2);
        string redata = Encoding.Default.GetString(rebytes, 0, rebytes.Length);
        Console.WriteLine("{0:HH:mm:ss}->接收数据(from {1}:{2}):{3}", DateTime.Now, remote.Address, remote.Port, redata);
    }
    udpclient.Close();
}



说明:客户端整个运行周期必须使用同一个UdpClient对象,测试发现new新对象内网端口和NAT端口都会发生变化。

---------------------------------------------------------------------------------------------------
2014-05-17:用户双方都在NAT下的UDP穿透



源码下载:http://www.mzwu.com/pic/201405/MZMessage_source.rar

参考资料

[1].P2P之UDP穿透NAT的原理与实现:http://blog.csdn.net/rzhghost/article/details/894010
[2].P2P之UDP穿透NAT的原理与实现--增强篇:http://blog.csdn.net/Rzhghost/article/details/894013
[3].使用TCP协议的NAT穿透技术:http://genghouwang.diandian.com/post/2012-08-18/40036571114
[4].QQ通信原理:http://www.360doc.com/content/11/1018/14/7635_157150760.shtml

评论: 0 | 引用: 0 | 查看次数: 8365
发表评论
登录后再发表评论!