用Java NAT实现

Home / Article MrLee 2015-12-8 3963

NAT原理

网络地址转换(NAT,Network Address Translation)属接入广域网(WAN)技术,是一种将私有(保留)地址转化为合法IP地址的转换技术。下面介绍两类不同方式实现的NAT: NAT(Network Address Translators):称为基本的NAT

在客户机时 192.168.0.8:4000——6.7.8.9:8000 在网关时 1.2.3.4:4000——6.7.8.9:8000 服务器C 6.7.8.9:8000 其核心是替换IP地址而不是端口,这会导致192.168.0.8使用4000端口后,192.168.0.9如何处理?具体参考RFC 1631 基本上这种类型的NAT设备已经很少了。或许根本我们就没机会见到。 2.NAPT(Network Address/Port Translators):其实这种才是我们常说的 NAT NAPT的特点是在网关时,会使用网关的 IP,但端口会选择一个和临时会话对应的临时端口。如下图:

在客户机时 192.168.0.8:4000——6.7.8.9:8000 在网关时 1.2.3.4:62000——6.7.8.9:8000 服务器C 6.7.8.9:8000 网关上建立保持了一个1.2.3.4:62000的会话,用于192.168.0.8:4000与6.7.8.9:8000之间的通讯。 对于NAPT,又分了两个大的类型,差别在于,当两个内网用户同时与8000端口通信的处理方式不同: 2.1、Symmetric NAT型 (对称型)

在客户机时 192.168.0.8:4000——6.7.8.9:8000 192.168.0.8:4000——6.7.8.10:8000 在网关时,两个不同session但端口号不同 1.2.3.4:62000——6.7.8.9:8000 1.2.3.4:62001——6.7.8.10:8000 服务器C 6.7.8.9:8000 服务器 D 6.7.8.10:8000 这种形式会让很多p2p软件失灵。 2.2、Cone NAT型(圆锥型)

在客户机时 192.168.0.8:4000——6.7.8.9:8000 192.168.0.8:4000——6.7.8.10:8000 在网关时,两个不同session但端口号相同 1.2.3.4:62000——6.7.8.9:8000 1.2.3.4:62000——6.7.8.10:8000 服务器C 6.7.8.9:8000 服务器D 6.7.8.10:8000 目前绝大多数属于这种。Cone NAT又分了3种类型: a)Full Cone NAT(完全圆锥型):从同一私网地址端口192.168.0.8:4000发至公网的所有请求都映射成同一个公网地址端口1.2.3.4:62000 ,192.168.0.8可以收到任意外部主机发到1.2.3.4:62000的数据报。 b)Address Restricted Cone NAT (地址限制圆锥型):从同一私网地址端口192.168.0.8:4000发至公网的所有请求都映射成同一个公网地址端口1.2.3.4:62000,只有当内部主机192.168.0.8先给服务器C 6.7.8.9发送一个数据报后,192.168.0.8才能收到6.7.8.9发送到1.2.3.4:62000的数据报。 c)Port Restricted Cone NAT(端口限制圆锥型):从同一私网地址端口192.168.0.8:4000发至公网的所有请求都映射成同一个公网地址端口1.2.3.4:62000,只有当内部主机192.168.0.8先向外部主机地址端口6.7.8.9:8000发送一个数据报后,192.168.0.8才能收到6.7.8.9:8000发送到1.2.3.4:62000的数据报。 穿越NAT的实现

A1在客户机时 192.168.0.8:4000——6.7.8.9:8000 X1在网关时 1.2.3.4:62000——6.7.8.9:8000 服务器C 6.7.8.9:8000 B1在客户机时 192.168.1.8:4000——6.7.8.9:8000 Y1在网关时 1.2.3.5:31000——6.7.8.9:8000 两内网用户要实现通过各自网关的直接呼叫,需要以下过程: 1、 客户机A1、B1顺利通过格子网关访问服务器C ,均没有问题(类似于登录) 2、 服务器C保存了 A1、B1各自在其网关的信息(1.2.3.4:62000、1.2.3.5:31000)没有问题。并可将该信息告知A1、B2。 3、 此时A1发送给B1网关的1.2.3.5:31000是否会被B1收到?答案是基本上不行(除非Y1设置为完全圆锥型,但这种设置非常少),因为Y1上检测到其存活的会话中没有一个的目的IP或端口于1.2.3.4:62000有关而将数据包全部丢弃! 4、 此时要实现A1、B1通过X1、Y1来互访,需要服务器C告诉它们各自在自己的网关上建立“UDP隧道”,即命令A1发送一个 192.168.0.8:4000——1.2.3.5:31000的数据报,B1发送一个192.168.1.8:4000——1.2.3.4:62000的数据报,UDP形式,这样X1、Y1上均存在了IP端口相同的两个不同会话(很显然,这要求网关为Cone NAT型,否则,对称型Symmetric NAT设置网关将导致对不同会话开启了不同端口,而该端口无法为服务器和对方所知,也就没有意义)。 5、 此时A1发给Y1,或者B1发给X1的数据报将不会被丢弃且正确的被对方收到. 综合P2P可实现的条件需要: 1、 中间服务器保存信息、并能发出建立UDP隧道的命令 2、 网关均要求为Cone NAT类型。Symmetric NAT不适合。 3、 完全圆锥型网关可以无需建立udp隧道,但这种情况非常少,要求双方均为这种类型网关的更少。 4、 假如X1网关为Symmetric NAT, Y1为Address Restricted Cone NAT 或Full Cone NAT型网关,各自建立隧道后,A1可通过X1发送数据报给Y1到B1(因为Y1最多只进行IP级别的甄别),但B2发送给X1的将会被丢弃(因为发送来的数据报中端口与X1上存在会话的端口不一致,虽然IP地址一致),所以同样没有什么意义。 5、 假如双方均为Symmetric NAT的情形,新开了端口,对方可以在不知道的情况下尝试猜解,也可以达到目的,但这种情形成功率很低,且带来额外的系统开支,不是个好的解决办法。 6、 不同网关型设置的差异在于,对内会采用替换IP的方式、使用不同端口不同会话的方式,使用相同端口不同会话的方式;对外会采用什么都不限制、限制IP地址、限制IP地址及端口。 7、 这里还没有考虑同一内网不同用户同时访问同一服务器的情形,如果此时网关采用AddressRestricted Cone NAT 或Full Cone NAT型,有可能导致不同用户客户端可收到别人的数据包,这显然是不合适的。 一些现在常用的技术: ALG(应用层网关):它可以是一个设备或插件,用于支持SIP协议,主要类似与在网关上专门开辟一个通道,用于建立内网与外网的连接,也就是说,这是一种定制的网关。更多只适用于使用他们的应用群体内部之间。 UpnP:它是让网关设备在进行工作时寻找一个全球共享的可路由IP来作为通道,这样避免端口造成的影响。要求设备支持且开启upnp功能,但大部分时候,这些功能处于安全考虑,是被关闭的。即时开启,实际应用效果还没经过测试。 STUN(Simple Traversalof UDP Through Network):这种方式即是类似于我们上面举例中服务器C的处理方式。也是目前普遍采用的方式。但具体实现要比我们描述的复杂许多,光是做网关Nat类型判断就由许多工作,RFC3489中详细描述了。 TURN(Traveral Using Relay NAT):该方式是将所有的数据交换都经由服务器来完成,这样NAT将没有障碍,但服务器的负载、丢包、延迟性就是很大的问题。目前很多游戏均采用该方式避开NAT的问题。这种方式不叫p2p。 ICE(Interactive Connectivity Establishment):是对上述各种技术的综合,但明显带来了复杂性。
最后附一个JAVA的NAT实现,未验证
早就听说用UDP穿透NAT可以解决P2P软件中的两个通过NAT上网的客户端直接通信的问题。当然,需要一个中介来帮助找到对方。终于用Java做了这个试验。
代码贴出来吧。
UDPAgent.java:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.regex.Pattern;
/**
* 
* @author Leo Luo
* 
*/
public class UDPAgent implements Runnable {
public static void main(String[] args) throws Exception {
   new UDPAgent(-1).start();
}
DatagramSocket ds;
byte[] recbuf = new byte[1024];
DatagramPacket rec = new DatagramPacket(recbuf, recbuf.length);
static String ipPattern = "([0-9]{1,3}.){3}[0-9]{1,3}";
static String portPattern = "[0-9]{1,5}";
static Pattern sendPattern = Pattern.compile("send " + ipPattern + " "
    + portPattern + " .*");
int port;
public UDPAgent(int port) {
   this.port = port;
}
public void init() throws Exception {
   if (port < 1024 || port > 655535) {
    ds = new DatagramSocket();
   } else {
    ds = new DatagramSocket(port);
   }
}
public void start() throws Exception {
   println("start");
   println("LocalPort:" + port);
   init();
   new Thread(this).start();// recive thread
   receive();
}
public void receive() {
   for (;;) {
    try {
     ds.receive(rec);
     String msg = new String(rec.getData(), rec.getOffset(), rec
       .getLength());
     String line = rec.getSocketAddress() + ":" + msg;
     println(line);
     onReceive(rec);
    } catch (Exception e) {
     e.printStackTrace();
    }
   }
}
public void onReceive(DatagramPacket rec) {
}
public void doCommand(String cmd) throws Exception {
   // command:
   // 1. send xxx.xxx.xxx.xxx xxx *******************
   if (sendPattern.matcher(cmd).matches()) {
    doSend(cmd);
   }
}
public void doSend(String cmd) throws Exception {
   println("CMD: " + cmd);
   String[] s = cmd.split(" ", 4);
   int port = Integer.parseInt(s[2]);
   InetSocketAddress target = new InetSocketAddress(s[1], port);
   byte[] bs = s[3].getBytes();
   doSend(target, bs);
}
public void doSend(SocketAddress addr, byte[] data) throws Exception {
   DatagramPacket pack = new DatagramPacket(data, data.length, addr);
   ds.send(pack);
}
public void run() {
   BufferedReader reader = new BufferedReader(new InputStreamReader(
     System.in));
   try {
    String line = reader.readLine();
    while (!"exit".equals(line)) {
     doCommand(line);
     line = reader.readLine();
    }
    System.exit(0);
   } catch (Exception e) {
    e.printStackTrace();
   }
}
public void println(String s) {
   System.out.println(System.currentTimeMillis() + ":" + s);
}
}


UDPClient.java
____________________________________________________
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class UDPClient extends UDPAgent {
/**
* @param args
*/
public static void main(String[] args) throws Exception {
   new UDPClient("www.javadoc.cn", 2008, -1).start();
}
String serverName;
int serverPort;
SocketAddress server;
public UDPClient(String host, int port, int localPort) {
   super(localPort);
   this.server = new InetSocketAddress(host, port);
}
public void start() throws Exception {
   println("start");
   init();
   register();
   new Thread(this).start();// recive thread
   receive();
}
public void onReceive(DatagramPacket rec) {
   try {
    report(rec);
    if (rec.getSocketAddress().equals(server)) {
     doCommand(new String(rec.getData(), rec.getOffset(), rec
       .getLength()));
    }
   } catch (Exception e) {
    e.printStackTrace();
   }
}
public void report(DatagramPacket rec) throws Exception {
   String s = rec.getSocketAddress()
     + new String(rec.getData(), rec.getOffset(), rec.getLength());
   byte[] buf = s.getBytes();
   ds.send(new DatagramPacket(buf, buf.length, server));
}
public void register() throws Exception {
   String msg = "register " + getLocalAddress() + " " + ds.getLocalPort();
   doSend(server, msg.getBytes());
}
public String getLocalAddress() throws Exception {
   InetAddress addr = InetAddress.getLocalHost();
   return addr.getHostAddress();
}
}

UDPServer.java
_______________________________________________________________

public class UDPServer extends UDPAgent {
public static void main(String[] args) throws Exception {
   new UDPServer(2008).start();
}
public UDPServer(int port) {
   super(port);
}
}

1。启动一个Server. 
2。启动两个Client. 
然后从Server端的Console里边可以看到两个Client的NAT后的地址和端口。
在Server段输入命令 send a.a.a.a A send b.b.b.b B hello
a.a.a.a是第一个Client的NAT后的ip,A端口号。
b是第二个。。。
输入这个命令后,A就会直接发给B一个 hello。 发送成功。 如果是同一个NAT后边,可能要让A发送到B的内网地址才能成功。

 

本文链接:https://www.it72.com/7144.htm

推荐阅读
最新回复 (0)
返回