TCP穿透(NAT遍历)库或类似的东西?

36

我想在C#中实现TCP打洞(NAT穿透)。如果需要,可以通过会合服务器来完成。我找到了http://sharpstunt.codeplex.com/,但无法让其工作。理想情况下,我需要一种方法,将端口号(int)作为参数传递给该方法,调用该方法后该端口在NAT上可用(“端口转发”)。如果该方法只返回在NAT上可用的某些端口号,也是可以的。有人在C#中完成过这个吗?您能给我一些关于sharpstunt或其他东西的工作示例吗?


嗯...为什么需要这个特殊情况呢? 如果只是关于连接的问题,安装任何代理并在Web客户端中配置即可解决。 - cRichter
2
代理服务器速度慢且不安全。 - user4914655
5个回答

26
在每个网络情景下,TCP打洞方式的操作与UDP打洞方式类似。例如,如果两个对等方A和B位于不同的NAT后面,每个对等方发送到另一对等方的第一个SYN数据包将在其各自的NAT中打开与其公共地址相关联的洞口。如果A的第一个SYN数据包在B的第一个SYN数据包到达B的NAT之前到达B的NAT,则B的NAT将认为A的SYN数据包是未经请求的并将其丢弃。但是,随后B的第一个SYN数据包可以成功地穿过A的NAT,因为A的NAT将B的公共地址识别为A发起的传出会话的目标。
因此,TCP打洞是可行的。我不明白为什么有人会持有其他观点。
还有,你不能手动创建此类行为吗?只要步骤相同以收集所有所需信息,它就不需要依赖于任何特定协议。
通常,TCP打洞(3.2.1)的过程如下:
客户端:A、B 服务器:S
• A使用与S的连接来请求与B的连接。 • S回复A,同时向B发送A的地址和B的私有和公共地址。
• A和B异步进行传出连接尝试(发送SYN数据包),使用他们用于向S注册的相同端口,传出连接尝试的目的是将其地址分别发送到对等方的公共和私有地址。同时,他们在本地TCP端口上侦听TCP传入连接请求。
• A和B等待对于其传出SYN数据包的SYN-ACK响应或传入连接请求(SYN数据包)。如果连接失败,对等方可以在最大超时期内重试它。
• 一旦三向握手过程完成,对等方进行身份验证。如果身份验证失败,对等方将关闭该连接并等待直到另一个连接成功验证。第一个成功验证的连接将用于传输TCP数据。
(我知道这不是太好的答案,但评论区没有足够的空间。)

这将无法与企业NAT(对称NAT)一起使用。为什么A和B发送到私有地址?它们无法通过互联网路由。您所描述的是STUNT。 - tobias
2
我只是在描述打洞过程。STUNT是一套实际的工具,可帮助进行打洞过程。我的主要观点是要表明TCP打洞确实是可能的,因为有人提到它不可能。我知道有些情况下它不起作用,但这不是问题所在。(至于为什么提到私有地址,我不知道。我将大部分数据从其他来源复制而来。) - SilverX
1
维基百科中 STUN 的“限制”内容: 由于STUN服务器的IP地址与终端的IP地址不同,在对称NAT情况下,NAT映射对于STUN服务器和终端是不同的。TURN在对称NAT情况下提供更好的结果。 - SilverX
我不理解的一件事是“私有地址”的含义。私有地址通常指像192.168.x.x这样的地址,但我不明白客户端A或B向其发送连接请求如何有助于任何事情。我在这里没有理解到什么? - Hey'Youssef

5
这个问题很老了,但是对于任何寻找NAT穿透的替代解决方案的人来说,你应该看看Open.NAT项目。它非常容易使用,并且可以同时使用UPNP和PMP,但它与Hole Punching不同。
假设你想将外部端口1700转发到本地端口1600,你所需要做的就是:
var discoverer = new NatDiscoverer();
var device = await discoverer.DiscoverDeviceAsync();
await device.CreatePortMapAsync(new Mapping(Protocol.Tcp, 1600, 1700, "The mapping name"));

您还可以列出所有现有的映射,以便验证您的端口是否已被使用。
var sb = new StringBuilder();
var ip = await device.GetExternalIPAsync();

sb.AppendFormat("\nAdded mapping: {0}:1700 -> 127.0.0.1:1600\n", ip);
sb.AppendFormat("\n+------+-------------------------------+--------------------------------+------------------------------------+-------------------------+");
sb.AppendFormat("\n| PROT | PUBLIC (Reacheable)           | PRIVATE (Your computer)        | Descriptopn                        |                         |");
sb.AppendFormat("\n+------+----------------------+--------+-----------------------+--------+------------------------------------+-------------------------+");
sb.AppendFormat("\n|      | IP Address           | Port   | IP Address            | Port   |                                    | Expires                 |");
sb.AppendFormat("\n+------+----------------------+--------+-----------------------+--------+------------------------------------+-------------------------+");
foreach (var mapping in await device.GetAllMappingsAsync())
{
    sb.AppendFormat("\n|  {5} | {0,-20} | {1,6} | {2,-21} | {3,6} | {4,-35}|{6,25}|",
        ip, mapping.PublicPort, mapping.PrivateIP, mapping.PrivatePort, mapping.Description, mapping.Protocol == Protocol.Tcp ? "TCP" : "UDP", mapping.Expiration.ToLocalTime());
}
sb.AppendFormat("\n+------+----------------------+--------+-----------------------+--------+------------------------------------+-------------------------+");
Console.WriteLine(sb.ToString());

此外,MSDN上还有一篇关于NAT穿透的博客文章:https://blogs.msdn.microsoft.com/ncl/2009/07/27/end-to-end-connectivity-with-nat-traversal/


是的,让它完美地工作有点棘手,而且在某些网络硬件上可能根本无法工作。 - JPelletier
3
请确认,UPnP并不等同于防火墙打洞。它们都是NAT遍历的形式,然而UPnP只是在NAT中创建端口转发条目的一种方式,而打洞可以在不明确定义这些条目的情况下连接两个对等方。 - stenlan
@stenlan 你说得完全正确,我编辑了答案以使其更清晰。 - JPelletier

2
我们组建了一个名为IceLink的库,它使用ICE/STUN/TURN进行P2P流媒体传输,并具有完整的NAT穿越功能。基于STUN的打洞技术可用于大多数路由器之间建立直接连接,对于存在问题的路由器,则会回退到基于TURN的中继连接。

似乎这是一个商业库(仅供参考) - Valmond

1

我希望拥有STUNT而不是STUN,但还是谢谢,这可能是一个起点。 - TobiHeidi
STUNT 是使用 TCP 而不是 UDP 的 stun。你不能简单地将 UDP 替换为 TCP,然后像魔术一样工作。不幸的是,SipSorcery.core -> SipSorcery.Net -> Stun 并没有提供 STUNT... - TobiHeidi
2
没有TCP打洞,只有UDP打洞。 - Pavel Radzivilovsky

0

听起来你可能把TCP和UDP搞混了。TCP是一种面向连接的协议,防火墙和路由器很容易理解,需要一个发起者(客户端)和一个监听者(服务器)。如果客户端和服务器都在防火墙或NAT后面,你不能打洞而不让它们两个连接到某个代理服务器(该服务器没有防火墙)。这样做的问题是代理将负责中继它们所有的流量。

从你的问题来看,似乎你更感兴趣的是UDP打洞,它利用了UDP是无状态且非面向连接的特点。因此,大多数状态跟踪防火墙会对UDP数据流进行“最佳猜测”,并假定离开给定端口的流量将在同一端口接收回复,并自动路由它们回来。如果使用某些非通道手段(例如仅传递地址而不是数据的TCP服务器),两个对等方都可以在相同的端口上相互传输数据,则它们各自的防火墙/NAT路由器将打开允许流量进入的洞口。

至于如何实现,这完全取决于你将如何将对等方的IP地址传递给彼此。一旦你拥有它,只需在约定的端口上开始传输UDP数据包,并等待回复即可。


我可以进行UDP打洞,也就是所谓的STUN,这很容易。 但是你也可以进行TCP打洞,只不过更加复杂,被称为STUNT(最后一个T代表TCP)。http://sharpstunt.codeplex.com/声称可以做到,但我无法使其正常工作。还有一些JAVA库可以实现它... - TobiHeidi
如果客户端和服务器都在防火墙或 NAT 后面,没有让它们连接到某个代理服务器,你就无法打通隧道。+1 - Valmond
请记住,STUN不能与所有类型的NAT一起使用。ICE将TURN定义为备用选项。 - tobias
TCP穿透是可能的,但需要服务器启动连接(但在链接建立后,服务器就不再需要了——至少这是我理解的方式)。https://en.wikipedia.org/wiki/TCP_hole_punching - Markaos

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接