【问题标题】:How do I do TCP hole punching?如何进行 TCP 打孔?
【发布时间】:2014-12-28 22:21:06
【问题描述】:

问题如下。这是我当前没有成功的测试代码。

static void Main(string[] args)
{
    if (args.Count() != 3)
    {
        Console.WriteLine("Bad args");
    }
    var ep = new IPEndPoint(IPAddress.Parse(args[0]), int.Parse(args[1]));
    var lp = new IPEndPoint(IPAddress.Any, int.Parse(args[2]));

    var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    s.Bind(lp);

    var c = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    c.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
    c.Bind(lp);

    Task.Run(() => { try { c.Connect(ep); } catch { } });
    s.Listen(10);
    var v = s.Accept();
    v.Close();
}

如何进行 TCP 打孔?我正在使用远程服务器进行测试。我正在运行wget local_public_ip:port/test。我的路由器设置为端口 80,因此不需要打孔。我的代码建立了联系。现在我尝试了其他端口,但我无法完全弄清楚如何打孔。

我所做的是(C#代码)

var l = new TcpListener(8090);
l.Start();
try { var o = new TcpClient(); o.Connect("myserverip", 123); }
catch(Exception ex) {}
var e = l.AcceptSocket();
Console.WriteLine(e.RemoteEndPoint.AddressFamily);

我想也许我需要在 out tcp 连接上设置本地端点。

TcpClient(new System.Net.IPEndPoint(new System.Net.IPAddress(bytearray), port));

我犯了一个错误,得到了这个异常

The requested address is not valid in its context

将字节数组修复为192,168,1,5 似乎使传出连接正确。现在我已经使用我的侦听端口与远程 IP 建立了连接,我认为 wget 能够连接到我。事实并非如此

如何进行 TCP 打孔?

【问题讨论】:

  • 你把o的本地端口设置为8090了吗?您谈论它,但代码不匹配。请更新代码。;删除所有未定义的变量,例如字节数组和端口。 wget 需要从端口 123 连接。你做到了吗?如果不使用其他客户端。
  • @usr: 我写了wget http://mylocalip:8090/test TcpClient o 是为了建立一个虚拟的传出连接。端口= 8090。我想如果传出连接的本地端口是我正在侦听的本地端口,那么当远程连接尝试使用相同的端口进行连接时,路由器会让它通过。也许它确实有效,但不适用于我的路由器。但是有一些软件可以成功地在我的路由器上打一个洞。
  • @usr:经过一番摆弄后,我注意到我的问题中的代码有效。我只需要仔细选择 linux 端口。如果我在我的 linux 机器上阻止传入连接(模拟路由器阻止连接),它似乎不起作用我使用了这个 iptable 规则gist.github.com/anonymous/514cdd23f749ca5d2385 所以现在我想知道我是否需要能够在我之前连接到远程方可以打孔。这确实违背了这一点,因为这个问题假设两方无法沟通,并且第三部分用于帮助打孔。也许我可以请朋友稍后运行代码
  • @usr:这个链接很长。我会尝试在赏金结束之前阅读它。另外我忘了提及如果我不阻止连接但忘记发送连接它不起作用(这是有道理的)。我的直觉表明,iptables 中的 REJECT 和 DROP 可能会发送某种信号,从而破坏打孔器
  • @usr 如果你想要一个可以监听端口的简单测试程序,socat 很方便,并且有一个 windows 端口……它就像 telnet 或 netcat,但有更多有趣的选项。 ..

标签: sockets tcp hole-punching


【解决方案1】:

我会使用http://www.bford.info/pub/net/p2pnat/index.html 中详述的“连续打孔技术”。执行同时连接和套接字重用似乎要简单得多。打孔没有必要完全同时做任何事情(无论如何,这在分布式系统中是一个毫无意义的概念)。

我已经实现了打孔。我的路由器似乎不喜欢它。 Wireshark 显示出站打孔 SYN 是正确的,但远程方无法与我联系。我使用 TcpView.exe 验证所有端口并禁用所有防火墙。应该是路由器问题。 (这是一个奇怪的侵入性路由器。)

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

您可以对useParallelAlgorithm 尝试这两个值。两者都应该工作。

此代码用于服务器。它在本地 NAT 上打了一个洞。然后,您可以使用任何允许选择本地端口的客户端从远程端进行连接。我用curl.exe。显然,Windows 上的 telnet 不支持绑定到端口。 wget 显然两者都没有。

使用 TcpView 或 Process Explorer 验证两端的端口是否正确。您可以使用 Wireshark 来验证数据包。设置像tcp.port = 1234这样的过滤器。

当您“呼叫”打孔时,您可以使元组(您的 IP、您的端口、远程 IP、远程端口)进行通信。这意味着所有进一步的通信都必须使用这些值。所有套接字(入站或出站)都必须使用这些确切的端口号。如果您不知道:传出连接也可以控制本地端口。这并不常见。

【讨论】:

  • 这似乎有效。我使用useParallelAlgorithm=false 对其进行了测试。在我的远程服务器上运行它之前,我等待这个洞被打孔。有用。它似乎也在 iptables 上打了一个洞
  • @acidzombie24 太好了,很高兴知道!最后,网上有干净的打孔代码。我发现的所有代码都很糟糕。
  • 这是一个干净的hello world like hole punch you may like。它只使用 TcpClient 和 TcpListener。没有重用地址或原始套接字。您确定您的路由器不喜欢/不支持打孔器吗?如果你有一个远程 linux 机器可以测试,我可以给你一些 IPTable 规则来测试块
  • 我不能 100% 确定是路由器,但我想不出任何其他解释。我有一个直接连接到 Internet 的 Windows 服务器。我用它来测试这个。
  • @DiaaEddin 它对我也不起作用,原因不明,但它确实适用于其他人。如果您发现它不起作用的原因,请发表评论。
猜你喜欢
  • 1970-01-01
  • 2012-02-07
  • 2016-12-05
  • 2011-09-19
  • 2015-02-10
  • 2014-03-08
  • 2017-01-25
  • 2014-06-08
  • 2022-01-18
相关资源
最近更新 更多