Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UDP Holepunching behind NAT

I am trying to implement a simple sketch of UDP-Holepunching in Java to test it's concept and use it in my C/C++ application later on.

Concept:

As from Wikipedia I understood the concept as this: Let A and B be clients behind an undefined networkstructure and C a well-known public reachable server.

  1. A sends a packet to the server C, the server saves it's IP-Address and port. C will get the public IP-Address of A's NAT. Doing this, the NAT in front of A will create a route that will pass all packets on this port to A.
  2. B does the same as A, sending a packet to server C, which will then save it's Address and port, B's NAT creates a route and so on.
  3. At this point, C knows both address and port of each client. C will send the address and port of B to A and from A to B.
  4. A sends a packet to B which will be rejected by B's NAT, but doing so will open a "hole" in A's NAT, letting further packets from B pass.
  5. B sends a packet to A which will reach A, as a "hole" was "punched" before. Doing so will also open a "hole" in B's NAT, letting further packets from A pass.
  6. The holepunch is now done and A and B should be able to communicate with each other P2P

This is all working well over localhost (which is not such a big surprise), but in a real-world-example this fails.

Problem:

A and B are both able to connect to server C, which gets their packets, stores their address and port and transmits it to the other client. But at this point it fails. A and B are not able to communicate with each other. So I am asking myself where I did wrong. I spent days searching for working examples in google and stackoverflow but all I stumbled upon is the suggestion to use STUN which is not what I want.

Implementation:

Below I will post my sketch in Java, as I do not know whether I have a problem with my concept or my implementation.

This is the code of the server:

public class Server
{
    public static void main(String[] args)
    {
        int port1 = 0, port2 = 0;
        String address1 = null, address2;
        byte[] bytes = new byte[1024];
        try
        {
            System.out.println("Server waiting");
            DatagramSocket ds = new DatagramSocket(789);
            while(!Thread.interrupted())
            {
                DatagramPacket p = new DatagramPacket(bytes, bytes.length);
                ds.receive(p);
                if(port1 == 0)
                {
                    port1 = p.getPort();
                    address1 = p.getAddress().getHostAddress();
                    System.out.println("(1st) Server received:" + new String(bytes) + " from " + address1 + " on port " + port1);
                }
                else
                {
                    port2 = p.getPort();
                    address2 = p.getAddress().getHostAddress();
                    System.out.println("(2nd) Server received:" + new String(bytes) + " from " + address1 + " on port " + port1);
                    sendConnDataTo(address1, port1, address2, port2, ds);
                    sendConnDataTo(address2, port2, address1, port1, ds);
                }
            }
            ds.close();
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }

    public static void sendConnDataTo(String a1, int p1, String a2, int p2, DatagramSocket ds)
    {
        byte[] bA, bP;
        bA = a1.getBytes();
        bP = Integer.toString(p1).getBytes();
        DatagramPacket pck;
        try
        {
            pck = new DatagramPacket(bA, bA.length, InetAddress.getByName(a2), p2);
            ds.send(pck);
            pck = new DatagramPacket(bP, bP.length, InetAddress.getByName(a2), p2);
            ds.send(pck);
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
    }
}

Please note, that this is just some sketch, no real application. The server should only receive packets from two clients, save their address and port and pass it to the other client.

This is the code of the client:

public class Client
{
    private DatagramSocket socket;
    private int init = 0;
    private String target;
    private int port;

    public Client()
    {
        try
        {
            socket = new DatagramSocket();
        }
        catch(SocketException e)
        {
            e.printStackTrace();
        }
        Thread in = new Thread()
        {
            public void run()
            {
                while(true)
                {
                    byte[] bytes = new byte[1024];
                    DatagramPacket packet = new DatagramPacket(bytes, bytes.length);
                    try
                    {
                        socket.receive(packet);
                        bytes = Arrays.copyOfRange(bytes, 0, packet.getLength());
                        String s = new String(bytes);
                        System.out.println("Received: " + s);
                        if(init == 0)
                        {
                            target = s;
                            System.out.println("Target: " + target);
                            init++;
                        }
                        else if(init == 1)
                        {
                            port = Integer.parseInt(s);
                            System.out.println("Port: " + port);
                            init++;
                        }
                        else System.out.println(new String(bytes));
                    }
                    catch(IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            }
        };
        in.start();
        connectToSupervisor();
    }

    private void connectToSupervisor()
    {
        byte[] bytes = new byte[1024];
        System.out.println("Greeting server");
        System.arraycopy("EHLO".getBytes(), 0, bytes, 0, 4);
        try
        {
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, InetAddress.getByName("localhost"), 789);
            socket.send(packet);
            System.out.println("Greetings sent...");
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }
        send();
    }

    private void send()
    {
        while(init != 2)
        {
            try
            {
                Thread.sleep(20L);
            }
            catch(InterruptedException e)
            {
                e.printStackTrace();
            }
        }
        System.out.println("Init completed!");
        while(true)
        {
            byte[] b2 = "Hello".getBytes();
            byte[] b1 = new byte[6];
            System.arraycopy(b2, 0, b1, 0, b2.length);
            try
            {
                DatagramPacket packet = new DatagramPacket(b1, b1.length, InetAddress.getByName(target), port);
                socket.send(packet);
            }
            catch(Exception e)
            {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args)
    {
        new Client();
    }
}

The client will just send a packet to the server, listen for packets from it, grab the connection-data from the other client and will then continuously send packets containing "Hello" to it.

I am sorry for the long code but I wanted to keep it complete.

I would be glad if anyone of you could point me to the mistakes I am doing, explain me why this is not working, give me a working example or at least point me to an alternative.

like image 949
Prior99 Avatar asked Sep 30 '13 05:09

Prior99


People also ask

Does UDP work over NAT?

UDP hole punching will not work with symmetric NAT devices (also known as bi-directional NAT) which tend to be found in large corporate networks.

Does NAT use UDP or TCP?

For an originating NAT to pass TCP or UDP successfully, it must recompute the TCP or UDP header checksum based on the translated IP addresses, not the original ones, and put that checksum into the TCP or UDP header of the first packet of the fragmented set of packets.

What is UDP push?

Whenever mobile registers to internet, it creates a UDP port and binds to it. It makes 1 time HTTPS connection to server and sends its IP address and port. There can be optional dummy data exchange between server and client using UDP (to and from)


Video Answer


2 Answers

Your code seems to be correct. I tested your code and it works fine. The concept is also correct. But please check whether both the clients you run are within same NAT device or different NAT devices. If your are running both the clients under same NAT device then it may not work because not all NAT devices support hair pinning i.e, both clients send packets to NAT's external IP which needs to be passed to itself. For more information refer this link: https://www.rfc-editor.org/rfc/rfc4787#section-6

like image 83
Kunjan Thadani Avatar answered Oct 07 '22 12:10

Kunjan Thadani


Given your conceptual outline, I think there is an issue at point 4. Although A punches a hole through its own NAT, when B attempts to reach this hole it is unaware of the port on A's NAT (or more correctly/commonly - NAPT) and hence A's NAT drops the packet when B attempts to communicate.

like image 41
Junkers Avatar answered Oct 07 '22 11:10

Junkers