NAT 后面的 UDP 打孔

2022-01-22 00:00:00 networking udp p2p java nat

我正在尝试在 Java 中实现一个简单的 UDP-Holepunching 草图来测试它的概念并稍后在我的 C/C++ 应用程序中使用它.

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.

根据维基百科,我将这个概念理解为:假设 A 和 B 是一个未定义的网络结构背后的客户端,而 C 是一个众所周知的公共可达服务器.

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向服务器C发送一个数据包,服务器保存它的IP地址和端口.C 将获得 A 的 NAT 的公共 IP 地址.这样做,A 前面的 NAT 将创建一个路由,将这个端口上的所有数据包传递给 A.
  2. B 和 A 一样,向服务器 C 发送一个数据包,然后服务器 C 会保存它的地址和端口,B 的 NAT 创建一个路由等等.
  3. 此时,C 知道每个客户端的地址和端口.C 将 B 的地址和端口发送给 A,从 A 发送给 B.
  4. A 向 B 发送一个数据包,该数据包将被 B 的 NAT 拒绝,但这样做会在 A 的 NAT 中打开一个漏洞",让来自 B 的更多数据包通过.
  5. B 向 A 发送一个数据包,该数据包将到达 A,因为之前打孔"了一个洞".这样做也会在 B 的 NAT 中打开一个洞",让更多来自 A 的数据包通过.
  6. 现在打孔已经完成,A 和 B 应该能够相互 P2P 通信

这一切都在 localhost 上运行良好(这并不令人意外),但在现实世界的示例中却失败了.

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

A 和 B 都能够连接到服务器 C,服务器 C 获取它们的数据包,存储它们的地址和端口并将其传输给另一个客户端.但在这一点上它失败了.A 和 B 无法相互通信.所以我问自己哪里做错了.我花了几天时间在 google 和 stackoverflow 中搜索工作示例,但我偶然发现的是使用 STUN 的建议,这不是我想要的.

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.

下面我将用 Java 发布我的草图,因为我不知道我的概念或实现是否有问题.

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

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.

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();
    }
}

客户端只会向服务器发送一个数据包,监听来自它的数据包,从另一个客户端获取连接数据,然后会不断地向它发送包含Hello"的数据包.

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.

推荐答案

您的代码似乎是正确的.我测试了你的代码,它工作正常.这个概念也是正确的.但请检查您运行的两个客户端是在同一个 NAT 设备内还是在不同的 NAT 设备内.如果您在同一个 NAT 设备下运行两个客户端,那么它可能无法工作,因为并非所有 NAT 设备都支持发夹,即两个客户端都将数据包发送到 NAT 的外部 IP,该外部 IP 需要传递给它自己.有关更多信息,请参阅此链接:https://www.rfc-editor.org/rfc/rfc4787#section-6

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

相关文章