首页 文章

在NAT后面进行UDP打孔

提问于
浏览
11

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

概念:

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

  • A将数据包发送到服务器C,服务器将其保存为's IP-Address and port. C will get the public IP-Address of A'的NAT . 这样做,A前面的NAT将创建一个路由,将该端口上的所有数据包传递给A.

  • B与A相同,向服务器C发送数据包,然后保存它's Address and port, B' s NAT创建路由,依此类推 .

  • 此时,C知道每个客户端的地址和端口 . C将B的地址和端口发送到A,从A发送到B.

  • A向B发送一个数据包,该数据包将被B 's NAT, but doing so will open a 388156 in A'的NAT拒绝,让B的其他数据包通过 .

  • B向A发送一个数据包,它将到达A,因为"hole"之前是"punched" . 这样做也会在B的NAT中打开"hole",让来自A的更多数据包通过 .

  • 现在已经完成了打孔,A和B应该能够相互通信P2P

这一切都优于localhost(这不是一个大惊喜),但在现实世界的例子中,这失败了 .

问题:

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

实施:

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

这是服务器的代码:

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

请注意,这只是一些草图,没有真正的应用 . 服务器应该只接收来自两个客户端的数据包,保存它们的地址和端口并将其传递给另一个客户端 .

这是客户端的代码:

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”的数据包 .

我很抱歉长代码,但我想保持完整 .

如果你们中的任何人能够指出我正在做的错误,我会很高兴,解释一下为什么这不起作用,给我一个有效的例子,或者至少指出一个替代方案 .

3 回答

  • 2

    鉴于你的概念轮廓,我认为在第4点有一个问题 . 虽然A通过自己的NAT打了一个洞,当B试图到达这个洞时,它不知道A的NAT上的端口(或更正确/通常 - NAPT)因此当B尝试通信时,A的NAT丢弃数据包 .

  • 3

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

  • 2

    对于那篇伟大帖子后面的人,请注意,在服务器端,收到的第二个UDP数据包被宣布为:System.out.println("(2nd) Server received:" new String(bytes)" from " address1 " on port " port1 );它应该是System.out.println("(2nd) Server received:" new String(bytes)" from " address2 " on port " port2 );它没有什么大不了的,因为它只是一个信息性的消息,但它让我失去了一些时间只是想知道地球上的地狱如何给同一个端口到两个不同的通信:P

相关问题