Perfuração UDP por trás do NAT

Estou tentando implementar um esboço simples de UDP-Holepunching em Java para testar seu conceito e usá-lo no meu aplicativo C / C ++ posteriormente.

Conceito:

A partir da Wikipedia, entendi o conceito da seguinte forma: sejam A e B clientes por trás de uma estrutura de rede indefinida e C um servidor público acessível conhecido.

A envia um pacote para o servidor C, o servidor salva seu endereço IP e porta. C obterá o endereço IP público do NAT de A. Fazendo isso, o NAT na frente de A criará uma rota que passará todos os pacotes nessa porta para A.B faz o mesmo que A, enviando um pacote para o servidor C, que salvará seu endereço e porta, o NAT de B cria uma rota e assim por diante.Neste ponto, C conhece o endereço e a porta de cada cliente. C enviará o endereço e a porta de B para A e de A para B.A envia um pacote para B, que será rejeitado pelo NAT de B, mas isso abrirá um "buraco" no NAT de A, permitindo que outros pacotes de B passem.B envia um pacote para A que alcançará A, pois um "buraco" foi "perfurado" antes. Isso também abrirá um "buraco" no NAT de B, permitindo que outros pacotes de A passem.O furo está pronto e A e B devem poder se comunicar P2P

Tudo isso funciona bem no host local (o que não é uma surpresa tão grande), mas em um exemplo do mundo real isso falha.

Problema:

A e B podem se conectar ao servidor C, que obtém seus pacotes, armazena seu endereço e porta e os transmite para o outro cliente. Mas neste momento ele falha. A e B não conseguem se comunicar. Então, eu estou me perguntando onde eu errei. Passei dias pesquisando exemplos de trabalho no google e no stackoverflow, mas só encontrei a sugestão de usar o STUN, que não é o que eu quero.

Implementação:

Abaixo, postarei meu esboço em Java, pois não sei se tenho algum problema com meu conceito ou com minha implementação.

Este é o código do servidor:
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();
        }
    }
}

Observe que este é apenas um esboço, nenhuma aplicação real. O servidor deve receber apenas pacotes de dois clientes, salvar seu endereço e porta e passá-lo para o outro cliente.

Este é o código do cliente:
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();
    }
}

O cliente apenas envia um pacote ao servidor, escuta pacotes dele, pega os dados de conexão do outro cliente e envia continuamente pacotes contendo "Olá" a ele.

Sinto muito pelo código longo, mas queria mantê-lo completo.

Eu ficaria feliz se alguém de vocês pudesse me indicar os erros que estou cometendo, me explicar por que isso não está funcionando, me dar um exemplo de trabalho ou pelo menos me indicar uma alternativa.

questionAnswers(3)

yourAnswerToTheQuestion