问题在下面。这是我当前的测试代码,未成功。
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));
我犯了一个错误并得到了这个异常(exception)
The requested address is not valid in its context
将字节数组固定为
192,168,1,5
似乎可以使传出连接正确。现在,我已经使用监听端口建立了到远程IP的输出连接,我认为wget将能够连接到我。事实并非如此如何进行TCP打洞?
最佳答案
我将使用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不支持绑定(bind)到端口。 wget显然都不是。使用TcpView或Process Explorer验证端口在两侧均正确。您可以使用Wireshark来验证数据包。设置类似
tcp.port = 1234
的过滤器。当您“呼出”打洞时,您可以启用元组(您的IP,您的端口,远程IP,远程端口)进行通信。这意味着所有进一步的通信都必须使用这些值。所有套接字(入站或出站)必须使用这些确切的端口号。如果您不知道,传出连接也可以控制本地端口。这是罕见的。