While searching the internet for existing solutions I bounced over the so called "Nat Holepunch".
See the article on wikipedia .This actually made that much sense to me, I started developing my own proof of concept.
Details
At first I will describe the Server, as it is the basic component. This is the so called "Relay Server".
RelayServer.java
package de.boehme.app.natholepunch;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Set;
import de.boehme.app.natholepunch.dto.NATDevice;
public class RelayServer {
private Set<NATDevice> inquiredComputers = new HashSet<NATDevice>();
public static void main(String[] args) {
RelayServer rs = new RelayServer();
try {
rs.startServer();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void startServer() throws IOException, InterruptedException {
DatagramSocket serverSocket = new DatagramSocket(12345);
byte[] receiveData = new byte[50];
while (true) {
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
System.out.println("Listening...");
serverSocket.receive(receivePacket);
String data = new String(receivePacket.getData());
//NAT Device 1
InetAddress incomingIPAddress = receivePacket.getAddress();
int port = receivePacket.getPort();
NATDevice startComp = new NATDevice();
startComp.setPortOfNAT(port);
startComp.setPublicIpOfNAT(incomingIPAddress);
//NAT Device 2
String accordingIP = data.substring(0, data.indexOf(0));
// String accordingPort = data.substring(data.indexOf(':')+1, data.indexOf(0));
NATDevice destinationComp = new NATDevice();
// destinationComp.setPortOfNAT(Integer.valueOf(accordingPort));
destinationComp.setPublicIpOfNAT(InetAddress.getByName(accordingIP));
System.out.println("Checking: "+startComp.getPublicIpOfNAT()+":"+startComp.getPortOfNAT()+" TO: "+destinationComp.getPublicIpOfNAT());
//check here if a matching entry is already present
NATDevice matchComp = checkMatchingNATDevice(destinationComp);
if(matchComp != null){
System.out.println("Already present.. now sending packets to both of them...");
//send to the socket from previously saved first NAT Device infos from the second NAT Device
sendPacket(serverSocket, matchComp, startComp);
Thread.sleep(1000);
sendPacket(serverSocket, startComp, matchComp);
inquiredComputers.remove(startComp);
inquiredComputers.remove(matchComp);
} else{
System.out.println("Adding "+startComp.getPublicIpOfNAT()+":"+startComp.getPortOfNAT()+" AND "+destinationComp.getPublicIpOfNAT()+" for later matching");
inquiredComputers.add(startComp);
}
}
// serverSocket.close();
}
private synchronized NATDevice checkMatchingNATDevice(NATDevice comp){
for(NATDevice c : inquiredComputers){
if(c.getPublicIpOfNAT().equals(comp.getPublicIpOfNAT())){
return c;
}
}
return null;
}
private synchronized void sendPacket(DatagramSocket socket, NATDevice natDeviceHome, NATDevice natDeviceRemote) throws IOException{
byte[] sendData = new byte[50];
InetAddress homeIPAddress = natDeviceHome.getPublicIpOfNAT();
int homePort = natDeviceHome.getPortOfNAT();
//now the remote destination
InetAddress destIPAddress = natDeviceRemote.getPublicIpOfNAT();
int destPort = natDeviceRemote.getPortOfNAT();
String data = destIPAddress.getHostAddress()+":"+destPort+"-"+homePort;
sendData = data.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, homeIPAddress, homePort);
socket.send(sendPacket);
}
}
Explanation of the code:
The server should be run on a machine that can accept connections on port 12345. This is because the server listens on this specific port number.
Note: As this is only for development purposes the port number was hardcoded, i.e. when in production systems you would create it dynamically.
Going onwards to the most important method: startServer();
In the while loop we can see that on every connection attempt we differentiate between NAT Device 1 and NAT Device 2. How does this work? From the received packet we get a port number and an IP Adress. Both are needed to create a new object "NatDevice". In the data part of the packet we can extract the information we need to get the according second NAT Device.
The combination of NAT Device 1 to NAT Device 2 will be then added to an internal HashSet. That means when now NAT Device 2 connects to the server and the according NAT Device 1 is already present both NAT Devices get informed with the connection credentials of their connection peer.
How does the Client Side look like?
ClientBehindNAT.java
package de.boehme.app.natholepunch;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
public class ClientBehindNAT {
private String relayServerIP;
private String destinationIP;
private InetAddress clientIP;
private int socketTimeout;
public static void main(String[] args) {
ClientBehindNAT c = new ClientBehindNAT();
boolean result = c.parseInput(args);
if(result == true){
try {
c.createHole(true);
} catch (SocketTimeoutException e) {
System.out.println("Timeout occured... Try again?");
} catch (IOException e) {
e.printStackTrace();
}
} else{
System.err.println("Usage: <Relay Server IP> (e.g. 192.168.40.1) <Destination IP> (e.g. 192.168.40.128) <Timeout> (in milliseconds)");
}
}
/**
* Main method used for outer access
* @param standAlone
* @return the free NAT port of the Firewall
* @throws IOException
*/
public synchronized int createConnectionToPeer(String relayServerIP, String destinationIP, int socketTimeout) throws SocketTimeoutException, IOException{
this.relayServerIP = relayServerIP;
this.destinationIP = destinationIP;
this.socketTimeout = socketTimeout;
return createHole(false);
}
private boolean parseInput(String[] args){
if(args.length < 3){
return false;
} else if(args[0].equals("") || args[1].equals("") || args[2].equals("")){
return false;
}else{
relayServerIP = args[0];
destinationIP = args[1];
socketTimeout = Integer.valueOf(args[2]);
return true;
}
}
private int createHole(boolean standAlone) throws SocketTimeoutException, IOException{
// InetAddress serverIPAddress = InetAddress.getByName("192.168.40.1");
clientIP = InetAddress.getLocalHost();
byte[] addrInByte = createInternetAddressFromString(relayServerIP);//{(byte) 192, (byte) 168, 40, 1};
InetAddress serverIPAddress = InetAddress.getByAddress(addrInByte);
int port = 12345;
String data = destinationIP;
System.out.println("Try to send: "+data+" TO: "+serverIPAddress+" ON PORT: "+port);
//first send UDP packet to the relay server
DatagramSocket clientSocket = new DatagramSocket();
clientSocket.setSoTimeout(socketTimeout);
sendPacket(clientSocket, serverIPAddress, port, data);
//now wait for the answer of the server
String peer = receivePacket(clientSocket);
clientSocket.close();
//need to make sure all had the chance to receive packets with infos
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return initPeerConnection(peer, standAlone);
}
private byte[] createInternetAddressFromString(String address){
byte[] inetAddressBytes = new byte[4];
int index = address.indexOf('.');
int i=0;
while((index = address.indexOf('.')) != -1){
int part = Integer.valueOf(address.substring(0, index));
inetAddressBytes[i] = (byte) part;
address = address.substring(index+1);
i++;
}
inetAddressBytes[i] = (byte) Integer.valueOf(address).intValue();
return inetAddressBytes;
}
private synchronized void sendPacket(DatagramSocket socket, InetAddress destIPAddress, int port, String data) throws IOException{
byte[] sendData = new byte[data.length()];
sendData = data.getBytes();
DatagramPacket sendPacket = new DatagramPacket(sendData,
sendData.length, destIPAddress, port);
socket.send(sendPacket);
}
private synchronized String receivePacket(DatagramSocket socket) throws SocketTimeoutException, IOException{
byte[] receiveData = new byte[50];
DatagramPacket receivePacket = new DatagramPacket(receiveData,
receiveData.length);
socket.receive(receivePacket);
String answerFromServer = new String(receivePacket.getData());
answerFromServer = answerFromServer.substring(0, answerFromServer.indexOf(0));
System.out.println(clientIP+" GOT FROM SERVER:" + answerFromServer);
return answerFromServer;
}
private synchronized int initPeerConnection(String peer, boolean standAlone) throws IOException{
String accordingIP = peer.substring(0, peer.indexOf(':'));
String accordingPort = peer.substring(peer.indexOf(':')+1, peer.indexOf('-'));
InetAddress destIPAddress = InetAddress.getByName(accordingIP);
int remotePort = Integer.valueOf(accordingPort);
int natPort = Integer.valueOf(peer.substring(peer.indexOf('-')+1));
//this only applies in stand-alone mode
if(standAlone){
Thread receiver = new Thread(new PeerReceiveThread(natPort));
receiver.start();
Thread sender = new Thread(new PeerSendThread(destIPAddress, remotePort));
sender.start();
}
return natPort;
}
private class PeerReceiveThread implements Runnable{
int natPort;
public PeerReceiveThread(int port){
this.natPort = port;
}
public void run() {
try {
DatagramSocket serverSocket = new DatagramSocket(natPort);
byte[] receiveData = new byte[50];
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
System.out.println(clientIP+" listening on port: "+natPort);
while(true){
serverSocket.receive(receivePacket);
String data = new String(receivePacket.getData());
System.out.println(clientIP+" FROM "+destinationIP+" WITH "+data);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private class PeerSendThread implements Runnable{
InetAddress destIPAddress;
int port;
public PeerSendThread(InetAddress destIPAddress, int port) throws UnknownHostException{
this.destIPAddress = destIPAddress;
this.port = port;
}
public void run() {
try {
DatagramSocket clientSocket = new DatagramSocket();
while(true){
sendPacket(clientSocket, destIPAddress, port, "Hello from "+clientIP);
Thread.sleep(2000);
}
} catch (SocketException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The basic principle works like this: (code taken from own videochat application)
ClientBehindNAT c = new ClientBehindNAT();
dataPort = c.createConnectionToPeer(relayServerIP, destAddress, 15000);
controlPort = c.createConnectionToPeer(relayServerIP, destAddress, 15000);
At first we create a new ClientBehindNAT object and then calling createConnectionToPeer() method. This leads to a connection attempt to the listening RelayServer. As we have defined a timeout of 15 seconds the Client won't wait for too long, in case the other peer didn't get connected to the server or any other unforeseen problem occured.
The most important part is the createHole() method. Inside of this method we define the set up to the relay server by sending an initial packet to it. After that we wait until a packet comes back or the timeout occurs.
When everything went well, we extract the valid port number from the data part of the received message and return it back to the method caller.
Download Source Code:
complete Maven Project
natholepunch.zip
i'm not sure this is a udp hole punching implementation. I think the relay server should only give ip:port of client1 to client2 (and vice versa) so they can communicate without any relay server, by "punching" the nat with udp packets ip1:port1 <-> ip2:port2. On Client1, by punching paquets to ip2:port2 of client2, he can receive response from client2 because the client1's nat thinks that these packets are response from client1 paquets (same behaviour for client2's nat). So i think your code is only a kind of udp proxy : paquets are always sent to the relay server.
ReplyDeleteCorrect me if i'm wrong. Sorry for my english, i'm french.
Hi, Download link is down ? any chances of getting natholepunch.zip ?
ReplyDeleteLink is down! Please email file on vitrag007@gmail.com
ReplyDeletePlease email me source on phanthai.uit@gmail.com thanks!
ReplyDeleteLink is down... :( i need natholepunch.zip file
ReplyDeletePlease email on 0verman2ya@gmail.com
Thanks all !!!
Interesting Article
ReplyDeleteOnline Java Training | Core Java Online Training
Interesting Article
Online Java Training | Java EE Online Training
Link is down... :( i need natholepunch.zip file
ReplyDeletePlease email on msshin@iges.kr
Thanks all !!!
Link is down... :( i need natholepunch.zip file
ReplyDeletePlease email on msshin@iges.kr
Thanks all !!!
ReplyDeleteI have read your blog its very attractive and impressive. Nice information. It helped me alot.
Government vacancy
Govt Jobs
Sarkari nokri
latest sarkari vacancy
Online Form
latest govt jobs
I have read your blog its very attractive and impressive. Nice information. It helped me alot.
ReplyDeletebest smartphone
best smartphone in india
best android phone
best mobiles phones 2020
best smartphone in india under 15000
smm panel
ReplyDeletesmm panel
İsilanlariblog.com
İnstagram takipçi satın al
hirdavatci burada
Www.beyazesyateknikservisi.com.tr
servis
tiktok jeton hilesi
pendik toshiba klima servisi
ReplyDeletependik beko klima servisi
ataşehir beko klima servisi
maltepe lg klima servisi
beykoz lg klima servisi
üsküdar lg klima servisi
tuzla lg klima servisi
tuzla daikin klima servisi
ümraniye toshiba klima servisi
Good content. You write beautiful things.
ReplyDeletesportsbet
sportsbet
hacklink
vbet
mrbahis
korsan taksi
taksi
vbet
hacklink
Success Write content success. Thanks.
ReplyDeletedeneme bonusu
betmatik
canlı slot siteleri
canlı poker siteleri
betpark
betturkey
kralbet
çeşme
ReplyDeletemardin
başakşehir
bitlis
edremit
D4J
kuşadası
ReplyDeletelara
sivas
çekmeköy
fethiye
TVRJ3B
salt likit
ReplyDeletesalt likit
İ6HZ
çorlu
ReplyDeleteniğde
urfa
aksaray
hatay
3OPC