How do I do TCP hole punching?

后端 未结 1 1301
遥遥无期
遥遥无期 2020-12-24 03:39

Question is below. Here is my current test code which did not succeed.

static void Main(string[] args)
{
    if (args.Count() != 3)
    {
        Console.Wri         


        
相关标签:
1条回答
  • 2020-12-24 04:41

    I'd use the "sequential hole punching technique" detailed in http://www.bford.info/pub/net/p2pnat/index.html. It seems much simpler to do that simultaneous connections and socket reuse. It is not necessary for hole punching to do anything exactly simultaneously (that is a meaningless notion in distributed systems anyway).

    I have implemented hole punching. My router seems not to like it. Wireshark shows the outbound hole punching SYN is correct but the remote party can't get through to me. I verifies all ports with TcpView.exe and disabled all firewalls. Must be a router issue. (It is a strange and invasive router.)

    class HolePunchingTest
    {
        IPEndPoint localEndPoint;
        IPEndPoint remoteEndPoint;
        bool useParallelAlgorithm;
    
        public static void Run()
        {
            var ipHostEntry = Dns.GetHostEntry("REMOTE_HOST");
    
            new HolePunchingTest()
            {
                localEndPoint = new IPEndPoint(IPAddress.Parse("LOCAL_IP"), 1234),
                remoteEndPoint = new IPEndPoint(ipHostEntry.AddressList.First().Address, 1235),
                useParallelAlgorithm = true,
            }.RunImpl();
        }
    
        void RunImpl()
        {
            if (useParallelAlgorithm)
            {
                Parallel.Invoke(() =>
                {
                    while (true)
                    {
                        PunchHole();
                    }
                },
                () => RunServer());
            }
            else
            {
    
                PunchHole();
    
                RunServer();
            }
        }
    
        void PunchHole()
        {
            Console.WriteLine("Punching hole...");
    
            using (var punchSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                EnableReuseAddress(punchSocket);
    
                punchSocket.Bind(localEndPoint);
                try
                {
                    punchSocket.Connect(remoteEndPoint);
                    Debug.Assert(false);
                }
                catch (SocketException socketException)
                {
                    Console.WriteLine("Punching hole: " + socketException.SocketErrorCode);
                    Debug.Assert(socketException.SocketErrorCode == SocketError.TimedOut || socketException.SocketErrorCode == SocketError.ConnectionRefused);
                }
            }
    
            Console.WriteLine("Hole punch completed.");
        }
    
        void RunServer()
        {
            using (var listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
            {
                EnableReuseAddress(listeningSocket);
    
                listeningSocket.Bind(localEndPoint);
                listeningSocket.Listen(0);
    
                while (true)
                {
                    var connectionSocket = listeningSocket.Accept();
                    Task.Run(() => ProcessConnection(connectionSocket));
                }
            }
        }
    
        void ProcessConnection(Socket connectionSocket)
        {
            Console.WriteLine("Socket accepted.");
    
            using (connectionSocket)
            {
                connectionSocket.Shutdown(SocketShutdown.Both);
            }
    
            Console.WriteLine("Socket shut down.");
        }
    
        void EnableReuseAddress(Socket socket)
        {
            if (useParallelAlgorithm)
                socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
        }
    }
    

    You can try both values for useParallelAlgorithm. Both should work.

    This code is for the server. It punches a hole into the local NAT. You can then connect from the remote side using any client that allows to pick the local port. I used curl.exe. Apparently, telnet on Windows does not support binding to a port. wget apparently neither.

    Verify that the ports are correct on both sides using TcpView or Process Explorer. You can use Wireshark to verify packets. Set a filter like tcp.port = 1234.

    When you "call out" to punch a hole you enable the tuple (your-ip, your-port, remote-ip, remote-port) to communicate. This means that all further communication must use those values. All sockets (inbound or outbound) must use these exact port numbers. In case you aren't aware: outgoing connections can control the local port as well. This is just uncommon.

    0 讨论(0)
提交回复
热议问题