NAT 穿透是如何工作的:技术原理及企业级实践

via: https://arthurchiao.art/blog/how-nat-traversal-works-zh/

本文翻译自 2020 年的一篇英文博客: How NAT traversal works

设想这样一个问题:在北京和上海各有一台局域网的机器(例如一台是家里的台式机,一 台是连接到星巴克 WiFi 的笔记本),二者都是私网 IP 地址,但可以访问公网, 如何让这两台机器通信呢?

既然二者都能访问公网,那最简单的方式当然是在公网上架设一个中继服务器: 两台机器分别连接到中继服务,后者完成双向转发。这种方式显然有很大的性能开销,而 且中继服务器很容易成为瓶颈。

有没有办法不用中继,让两台机器直接通信呢?

如果有一定的网络和协议基础,就会明白这事儿是可能的。Tailscale 的这篇史诗级长文由浅入深地展示了这种“可能”,如果完全实现本文所 介绍的技术,你将得到一个企业级的 NAT/​防火墙穿透工具。 此外,如作者所说,去中心化软件领域中的许多有趣想法,简化之后其实都变成了 跨过公网(互联网)实现端到端直连 这一问题,因此本文的意义并不仅限于 NAT 穿透本身。

由于译者水平有限,本文不免存在遗漏或错误之处。如有疑问,请查阅原文。

以下是译文。

在前一篇文章 How Tailscale Works 中, 我们已经用较长篇幅介绍了 Tailscale 是如何工作的。但其中并没有详细描述我们是 如何穿透 NAT 设备,从而实现终端设备直连的 —— 不管这些终端之间 有什么设备(防火墙、NAT 等),以及有多少设备。本文试图补足这一内容。

1.1 背景:IPv4 地址短缺,引入 NAT

全球 IPv4 地址早已不够用,因此人们发明了 NAT(网络地址转换)来缓解这个问题。

简单来说,大部分机器都使用私有 IP 地址,如果它们需要访问公网服务,那么,

  • 出向流量:需要经过一台 NAT 设备,它会对流量进行 SNAT,将私有 srcIP+Port 转 换成 NAT 设备的公网 IP+Port(这样应答包才能回来),然后再将包发出去;
  • 应答流量(入向):到达 NAT 设备后进行相反的转换,然后再转发给客户端。

整个过程对双方透明。

更多关于 NAT 的内容,可参考 (译) NAT — 网络地址转换(2016)。 译注。

以上是本文所讨论问题的基本背景

1.2 需求:两台经过 NAT 的机器建立点对点连接

在以上所描述的 NAT 背景下,我们从最简单的问题开始:如何在两台经过 NAT 的机器之间建立 点对点连接(直连)。如下图所示:

nat-intro.png

直接用机器的 IP 互连显然是不行的,因为它们都是私有 IP(例如 192.168.1.x)。 在 Tailscale 中,我们会建立一个 WireGuard® 隧道 来解决这个问题 —— 但这并不是太重要,因为我们将过去几代人努力都整合到了一个工具集, 这些技术广泛适用于各种场景。例如,

  1. WebRTC 使用这些技术在浏览器之间完成 peer-​to-​peer 语音、视频和数据传输,
  2. VoIP 电话和一些视频游戏也使用类似机制,虽然不是所有情况下都很成功。

接下来,本文将在一般意义上讨论这些技术,并在合适的地方拿 Tailscale 和其他一些东西作为例子。

1.3 方案:NAT 穿透

1.3.1 两个必备前提:UDP + 能直接控制 socket

如果想设计自己的协议来实现 NAT 穿透,那必须满足以下两个条件:

  1. 协议应该基于 UDP

    理论上用 TCP 也能实现,但它会给本已相当复杂的问题再增加一层复杂性, 甚至还需要定制化内核 —— 取决于你想实现到什么程度。本文接下来都将关注在 UDP 上。

    如果考虑 TCP 是想在 NAT 穿透时获得面向流的连接( stream-​oriented connection),可以考虑用 QUIC 来替代,它构 建在 UDP 之上,因此我们能将关注点放在 UDP NAT 穿透,而仍然能获得一个 很好的流协议(stream protocol)。

  2. 对收发包的 socket 有直接控制权

    例如,从经验上来说,无法基于某个现有的网络库实现 NAT 穿透,因为我们 必须在使用的“主要”协议之外,发送和接收额外的数据包

    某些协议(例如 WebRTC)将 NAT 穿透与其他部分紧密集成。但如果你在构建自己的协议, 建议将 NAT 穿透作为一个独立实体,与主协议并行运行,二者仅 仅是共享 socket 的关系,如下图所示,这将带来很大帮助:

    nat-deep-integration.png

1.3.2 保底方式:中继

在某些场景中,直接访问 socket 这一条件可能很难满足。

退而求其次的一个方式是设置一个 local proxy(本地代理),主协议与这个 proxy 通信 ,后者来完成 NAT 穿透,将包中继(relay)给对端。这种方式增加了一个额外的间接层 ,但好处是:

  1. 仍然能获得 NAT 穿透,
  2. 不需要对已有的应用程序做任何改动

1.4 挑战:有状态防火墙和 NAT 设备

有了以上铺垫,下面就从最基本的原则开始,一步步看如何实现一个企业级的 NAT 穿透方案。

我们的目标是:在两个设备之间通过 UDP 实现双向通信, 有了这个基础,上层的其他协议(WireGuard, QUIC, WebRTC 等)就能做一些更酷的事情。

但即便这个看似最基本的功能,在实现上也要解决两个障碍

  1. 有状态防火墙
  2. NAT 设备

有状态防火墙是以上两个问题中相对比较容易解决的。实际上,大部分 NAT 设备都自带了一个有状态防火墙, 因此要解决第二个问题,必须先解决有第一个问题。

有状态防火墙具体有很多种类型,有些你可能见过:

  • Windows Defender firewall
  • Ubuntu’s ufw (using iptables/​nftables)
  • BSD/​macOS pf
  • AWS Security Groups(安全组

2.1 有状态防火墙

2.1.1 默认行为(策略)

以上防火墙的配置都是很灵活的,但大部分配置默认都是如下行为:

  1. 允许所有出向连接(allows all “outbound” connections)
  2. 禁止所有入向连接(blocks all “inbound” connections)

可能有少量例外规则,例如 allowing inbound SSH。

2.1.2 如何区分入向和出向包

连接(connection)和方向(direction)都是协议设计者头脑中的概念,到了 物理传输层,每个连接都是双向的;允许所有的包双向传输。 那防火墙是如何区分哪些是入向包、哪些是出向包的呢? 这就要回到“有状态”(stateful)这三个字了:有状态防火墙会记录它 看到的每个包,当收到下一个包时,会利用这些信息(状态)来判断应该做什么。

对 UDP 来说,规则很简单:如果防火墙之前看到过一个出向包(outbound),就会允许 相应的入向包(inbound)通过,以下图为例:

nat-firewalls-1a.png

笔记本电脑中自带了一个防火墙,当该防火墙看到从这台机器出去的 2.2.2.2:1234 -> 5.5.5.5:5678 包时,就会记录一下:5.5.5.5:5678 -> 2.2.2.2:1234 入向包应该放行。 这里的逻辑是:我们信任的世界(即笔记本)想主动与 5.5.5.5:5678 通信,因此应该放行(allow)其回包路径。

某些非常宽松的防火墙只要看到有从 2.2.2.2:1234 出去的包,就 会允许所有从外部进入 2.2.2.2:1234 的流量。这种防火墙对我们的 NAT 穿透来说非 常友好,但已经越来越少见了。

2.2 防火墙朝向(face-off)与穿透方案

2.2.1 防火墙朝向相同

场景特点:服务端 IP 可直接访问

在 NAT 穿透场景中,以上默认规则对 UDP 流量的影响不大 —— 只要路径上所有防火墙的“朝向”是一样的。 一般来说,从内网访问公网上的某个服务器都属于这种情况。

我们唯一的要求是:连接必须是由防火墙后面的机器发起的。这是因为 在它主动和别人通信之前,没人能主动和它通信,如下图所示:

穿透方案:客户端直连服务端,或 hub-​and-​spoke 拓扑

nat-firewalls-2.png

但上图是假设了通信双方中,其中一端(服务端)是能直接访问到的。 在 VPN 场景中,这就形成了所谓的 hub-​and-​spoke 拓扑:中心的 hub 没有任何防火墙策略,谁都能访问到; 防火墙后面的 spokes 连接到 hub。如下图所示:

nat-firewalls-3.png

2.2.2 防火墙朝向不同

场景特点:服务端 IP 不可直接访问

但如果两个“客户端”想直连,以上方式就不行了,此时两边的防火墙相向而立,如下图所示:

nat-firewalls-4.png

根据前面的讨论,这种情况意味着:两边要同时发起连接请求,但也意味着 两边都无法发起有效请求,因为对方先发起请求才能在它的防火墙上打开一条缝让我们进去! 如何破解这个问题呢?一种方式是让用户重新配置一边或两边的防火墙,打开一个端口, 允许对方的流量进来。

  1. 这显然对用户不友好,在像 Tailscale 这样的 mesh 网络中的扩展性也不好,在 mesh 网络中,我们假设对端会以一定的粒度在公网上移动。
  2. 此外,在很多情况下用户也没有防火墙的控制权限:例如在咖啡馆或机场中,连接的路 由器是不受你控制的(否则你可能就有麻烦了)。

因此,我们需要寻找一种不用重新配置防火墙的方式。

穿透方案:两边同时主动建连,在本地防火墙为对方打开一个洞

解决的思路还是先重新审视前面提到的有状态防火墙规则:

  • 对于 UDP,其规则(逻辑)是:包必须先出去才能进来(packets must flow out before packets can flow back in)。
  • 注意,这里除了要满足包的 IP 和端口要匹配这一条件之外,并没有要求包必须是相关的(related)。 换句话说,只要某些包带着正确的源和目的地址出去了,任何看起来像是响应的包都会被防火墙放进来 —— 即使对端根本没收到你发出去的包。

因此,要穿透这些有状态防火墙,我们只需要共享一些信息:让两端提前知道对方使用的 ip:port

  • 手动静态配置是一种方式,但显然扩展性不好;
  • 我们开发了一个 coordination server, 以灵活、安全的方式来同步 ip:port 信息。

有了对方的 ip:port 信息之后,两端开始给对方发送 UDP 包。在这个过程中,我们预 料到某些包将会被丢弃。因此,双方必须要接受某些包会丢失的事实, 因此如果是重要信息,你必须自己准备好重传。对 UDP 来说丢包是可接受的,但这里尤其需要接受。

来看一下具体建连(穿透)过程:

  1. 如图所示,笔记本出去的第一包,2.2.2.2:1234 -> 7.7.7.7:5678,穿过 Windows Defender 防火墙进入到公网。

    nat-firewalls-5a.png

    对方的防火墙会将这个包拦截掉,因为它没有 7.7.7.7:5678 -> 2.2.2.2:1234 的流量记录。 但另一方面,Windows Defender 此时已经记录了出向连接,因此会允许 7.7.7.7:5678 -> 2.2.2.2:1234 的应答包进来。

  2. 接着,第一个 7.7.7.7:5678 -> 2.2.2.2:1234 穿过它自己的防火墙到达公网。

    nat-firewalls-5b.png

    到达客户端侧时,Windows Defender 认为这是刚才出向包的应答包,因此就放行它进入了! 此外,右侧的防火墙此时也记录了:2.2.2.2:1234 -> 7.7.7.7:5678 的包应该放行。

  3. 笔记本收到服务器发来的包之后,发送一个包作为应答。这个包穿过 Windows Defender 防火墙 和服务端防火墙(因为这是对服务端发送的包的应答包),达到服务端。

    nat-firewalls-5c.png

成功!这样我们就建立了一个穿透两个相向防火墙的双向通信连接。 而初看之下,这项任务似乎是不可能完成的。

2.3 关于穿透防火墙的一些思考

穿透防火墙并非永远这么轻松,有时会受一些第三方系统的间接影响,需要仔细处理。 那穿透防火墙需要注意什么呢?重要的一点是:通信双方必须几乎同时发起通信, 这样才能在路径上的防火墙打开一条缝,而且两端还都是活着的。

2.3.1 双向主动建连:旁路信道

如何实现“同时”呢?一种方式是两端不断重试,但显然这种方式很浪费资源。假如双方都 知道何时开始建连就好了。

  • 这听上去是鸡生蛋蛋生鸡的问题了:双方想要通信,必须先提前通个信
  • 但实际上,我们可以通过旁路信道(side channel)来达到这个目的 ,并且这个旁路信道并不需要很 fancy:它可以有几秒钟的延迟、只需要传送几 KB 的 信息,因此即使是一个配置非常低的虚拟机,也能为几千台机器提供这样的旁路通信服务。

    • 在遥远的过去,我曾用 XMPP 聊天消息作为旁路,效果非常不错。
    • 另一个例子是 WebRTC,它需要你提供一个自己的“信令信道”(signalling channel, 这个词也暗示了 WebRTC 的 IP telephony ancestry),并将其配置到 WebRTC API。
    • 在 Tailscale,我们的协调服务器(coordination server)和 DERP (Detour Encrypted Routing Protocol) 服务器集群是我们的旁路信道。

2.3.2 非活跃连接被防火墙清理

有状态防火墙内存通常比较有限,因此会定期清理不活跃的连接(UDP 常见的是 30s), 因此要保持连接 alive 的话需要定期通信,否则就会被防火墙关闭,为避免这个问题, 我们,

  1. 要么定期向对方发包来 keepalive,
  2. 要么有某种带外方式来按需重建连接。

2.3.3 问题都解决了?不,挑战刚刚开始

对于防火墙穿透来说, 我们并不需要关心路径上有几堵墙 —— 只要它们是有状态防火墙且允许出 向连接,这种同时发包(simultaneous transmission)机制就能穿透任意多层防火墙。 这一点对我们来说非常友好,因为只需要实现一个逻辑,然后能适用于任何地方了。

…对吗?

其实,不完全对。这个机制有效的前提是:我们能提前知道对方的 ip:port。 而这就涉及到了我们今天的主题:NAT,它会使前面我们刚获得的一点满足感顿时消失。

下面,进入本文正题

3.1 NAT 设备与有状态防火墙

可以认为 NAT 设备是一个增强版的有状态防火墙,虽然它的增强功能 对于本文场景来说并不受欢迎:除了前面提到的有状态拦截/放行功能之外,它们还会在数据包经过时修改这些包。

3.2 NAT 穿透与 SNAT/​DNAT

具体来说,NAT 设备能完成某种类型的网络地址转换,例如,替换源或目的 IP 地址或端口。

  • 讨论连接问题和 NAT 穿透问题时,我们只会受 source NAT —— SNAT 的影响
  • DNAT 不会影响 NAT 穿透。

3.3 SNAT 的意义:解决 IPv4 地址短缺问题

SNAT 最常见的使用场景是将很多设备连接到公网,而只使用少数几个公网 IP。 例如对于消费级路由器,会将所有设备的(私有) IP 地址映射为单个连接到公网的 IP 地址。

这种方式存在的意义是:我们有远多于可用公网 IP 数量的设备需要连接到公网,(至少 对 IPv4 来说如此,IPv6 的情况后面会讨论)。NAT 使多个设备能共享同一 IP 地址,因 此即使面临 IPv4 地址短缺的问题,我们仍然能不断扩张互联网的规模。

3.4 SNAT 过程:以家用路由器为例

假设你的笔记本连接到家里的 WiFi,下面看一下它连接到公网某个服务器时的情形:

  1. 笔记本发送 UDP packet 192.168.0.20:1234 -> 7.7.7.7:5678

    nat-overview-1.png

    这一步就好像笔记本有一个公网 IP 一样,但源地址 192.168.0.20 是私有地址, 只能出现在私有网络,公网不认,收到这样的包时它不知道如何应答。

  2. 家用路由器出场,执行 SNAT。

    包经过路由器时,路由器发现这是一个它没有见过的新会话(session)。 它知道 192.168.0.20 是私有 IP,公网无法给这样的地址回包,但它有办法解决:

    1. 在它自己的公网 IP 上挑一个可用的 UDP 端口,例如 2.2.2.2:4242
    2. 然后创建一个 NAT mapping192.168.0.20:1234 <--> 2.2.2.2:4242
    3. 然后将包发到公网,此时源地址变成了 2.2.2.2:4242 而不是原来的 192.168.0.20:1234。因此服务端看到的是转换之后地址,
    4. 接下来,每个能匹配到这条映射规则的包,都会被路由器改写 IP 和 端口。

    nat-overview-2.png

  3. 反向路径是类似的,路由器会执行相反的地址转换,将 2.2.2.2:4242 变回 192.168.0.20:1234。对于笔记本来说,它根本感知不知道这正反两次变换过程。

这里是拿家用路由器作为例子,但办公网的原理是一样的。不同之处在 于,办公网的 NAT 可能有多台设备组成(高可用、容量等目的),而且它们有不止一个公 网 IP 地址可用,因此在选择可用的公网 ip:port 来做映射时,选择空间更大,能支持 更多客户端。

nat-overview-3.png

3.5 SNAT 给穿透带来的挑战

现在我们遇到了与前面有状态防火墙类似的情况,但这次是 NAT 设备:通信双方 不知道对方的 ip:port 是什么,因此无法主动建连,如下图所示:

nat-stun-1.png

但这次比有状态防火墙更糟糕,严格来说,在双方发包之前,根本无法确定(自己及对方的)ip:port 信息,因为 只有出向包经过路由器之后才会产生 NAT mapping(即,可以被对方连接的 ip:port 信息)。

因此我们又回到了与防火墙遇到的问题,并且情况更糟糕:双方都需要主动和对 方建连,但又不知道对方的公网地址是多少,只有当对方先说话之后,我们才能拿到它的地址信息。

如何破解以上死锁呢?这就轮到 STUN 登场了。

STUN 既是一些对 NAT 设备行为的详细研究,也是一种协助 NAT 穿透的协议。本文主要关注 STUN 协议。

4.1 STUN 原理

STUN 基于一个简单的观察:从一个会被 NAT 的客户端访问公网服务器时, 服务器看到的是 NAT 设备的公网 ip:port 地址,而非该 客户端的局域网 ip:port 地址

也就是说,服务器能告诉客户端它看到的客户端的 ip:port 是什么。 因此,只要将这个信息以某种方式告诉通信对端(peer),后者就知道该和哪个地址建连了! 这样就又简化为前面的防火墙穿透问题了

本质上这就是 STUN 协议的工作原理,如下图所示:

  • 笔记本向 STUN 服务器发送一个请求:“从你的角度看,我的地址什么?”
  • STUN 服务器返回一个响应:“我看到你的 UDP 包是从这个地址来的:ip:port”。

nat-stun-2.png

The STUN protocol has a bunch more stuff in it — there’s a way of obfuscating the ip:port in the response to stop really broken NATs from mangling the packet’s payload, and a whole authentication mechanism that only really gets used by TURN and ICE, sibling protocols to STUN that we’ll talk about in a bit. We can ignore all of that stuff for address discovery.

4.2 为什么 NAT 穿透逻辑和主协议要共享同一个 socket

理解了 STUN 原理,也就能理解为什么我们在文章开头说,如果 要实现自己的 NAT 穿透逻辑和主协议,就必须让二者共享同一个 socket

  1. 每个 socket 在 NAT 设备上都对应一个映射关系(私网地址 -> 公网地址),
  2. STUN 服务器只是辅助穿透的基础设施,
  3. 与 STUN 服务器通信之后,在 NAT 及防火墙设备上打开了一个连接,允许入向包进来(回忆前面内容, 只要目的地址对,UDP 包就能进来,不管这些包是不是从 STUN 服务器来的),
  4. 因此,接下来只要将这个地址告诉我们的通信对端(peer),让它往这个地址发包,就能实现穿透了。

4.3 STUN 的问题:不能穿透所有 NAT 设备(例如企业级 NAT 网关)

有了 STUN,我们的穿透目的似乎已经实现了:每台机器都通过 STUN 来获取自己的私网 socket 对应的公网 ip:port,然后把这个信息告诉对端,然后两端 同时发起穿透防火墙的尝试,后面的过程就和上一节介绍的防火墙穿透一样了,对吗

答案是:看情况。某些情况下确实如此,但有些情况下却不行。通常来说,

  • 对于大部分家用路由器场景,这种方式是没问题的;
  • 但对于一些企业级 NAT 网关来说,这种方式无法奏效。

NAT 设备的说明书上越强调它的安全性,STUN 方式失败的可能性就越高。(但注意,从实际意义上来说, NAT 设备在任何方面都并不会增强网络的安全性,但这不是本文重点,因此不展开。)

4.4 重新审视 STUN 的前提

再次审视前面关于 STUN 的假设:当 STUN 服务器告诉客户端在公网看来它的地址是 2.2.2.2:4242 时,那所有目的地址是 2.2.2.2:4242 的包就都能穿透防火墙到达该客户端。

这也正是问题所在:这一点并不总是成立

  • 某些 NAT 设备的行为与我们假设的一致,它们的有状态防火墙组件只要看到有客户端自己 发起的出向包,就会允许相应的入向包进入;因此只要利用 STUN 功能,再加上两端同时 发起防火墙穿透,就能把连接打通;

    in theory, there are also NAT devices that are super relaxed, and don’t ship with stateful firewall stuff at all. In those, you don’t even need simultaneous transmission, the STUN request gives you an internet ip:port that anyone can connect to with no further ceremony. If such devices do still exist, they’re increasingly rare.
  • 另外一些 NAT 设备就要困难很多了,它会针对每个目的地址来生成一条相应的映射关系。 在这样的设备上,如果我们用相同的 socket 来分别发送数据包到 5.5.5.5:1234 and 7.7.7.7:2345,我们就会得到 2.2.2.2 上的两个不同的端口,每个目的地址对应一个。 如果反向包的端口用的不对,包就无法通过防火墙。如下图所示:

    nat-stun-3.png

知道 NAT 设备的行为并不是完全一样之后,我们来引入一些正式术语。

5.1 早期术语

如果之前接触过 NAT 穿透,可能会听说过下面这些名词:

  • “Full Cone”
  • “Restricted Cone”
  • “Port-​Restricted Cone”
  • “Symmetric” NATs

这些都是 NAT 穿透领域的早期术语。

但其实这些术语相当让人困惑。我每次都要 查一下 Restricted Cone NAT 是什么意思。从实际经验来看,我并不是唯一对此感到困惑的人。 例如,如今互联网上将 “easy” NAT 归类为 Full Cone,而实际上它们更应该归类为 Port-​Restricted Cone。

5.2 近期研究与新术语

最近的一些研究和 RFC 已经提出了一些更准确的术语。

  • 首先,它们明确了如下事实:NAT 设备的行为差异表现在多个维度, 而并非只有早期研究中所说的 “cone” 这一个维度,因此基于 “cone” 来划分类别并不是很有帮助
  • 其次,新研究和新术语能更准确地描述 NAT 在做什么

前面提到的所谓 “easy” 和 “hard” NAT,只在一个维度有不同:NAT 映射是否考虑到目的地址信息。 RFC 4787 中,

  • easy NAT 及其变种称为 “Endpoint-​Independent Mapping” (EIM,终点无关的映射)

    但是,从“命名很难”这一程序员界的伟大传统来说,EIM 这个词其实 也并不是 100% 准确,因为这种 NAT 仍然依赖 endpoint,只不过依赖的是源 endpoint:每个 source ip:port 对应一个映射 —— 否则你的包就会和别人的包混在一起,导致混乱。

    严格来说,EIM 应该称为 “Destination Endpoint Independent Mapping” (DEIM?), 但这个名字太拗口了,而且按照惯例,Endpoint 永远指的是 Destination Endpoint。

  • hard NAT 以及变种称为 “Endpoint-​Dependent Mapping”(EDM,终点相关的映射) 。

    EDM 中还有一个子类型,依据是只根据 dst_​ip 做映射,还是根据 dst_​ip + dst_​port 做映射。 对于 NAT 穿透来说,这种区分对来说是一样的:它们都会导致 STUN 方式不可用

5.3 老的 cone 类型划分

你可能会有疑问:根据是否依赖 endpoint 这一条件,只能组合出两种可能,那为什么传 统分类中会有四种 cone 类型呢?答案是 cone 包含了两个正交维度的 NAT 行为

  • NAT 映射行为:前面已经介绍过了,
  • 有状态防火墙行为:与前者类似,也是分为与 endpoint 相关还是无关两种类型。

因此最终组合如下:

NAT Cone Types


Endpoint 无关 NAT mapping Endpoint 相关 NAT mapping (all types)
Endpoint 无关防火墙 Full Cone NAT N/​A*
Endpoint 相关防火墙 (dst. IP only) Restricted Cone NAT N/​A*
Endpoint 相关防火墙 (dst. IP+port) Port-​Restricted Cone NAT Symmetric NAT

分解到这种程度之后就可以看出,cone 类型对 NAT 穿透场景来说并没有什么意义。 我们关心的只有一点:是否是 Symmetric —— 换句话说,一个 NAT 设备是 EIM 还是 EDM 类型的。

5.4 针对 NAT 穿透场景:简化 NAT 分类

以上讨论可知,虽然理解防火墙的具体行为很重要,但对于编写 NAT 穿透代码来说,这一点并不重要。 我们的两端同时发包方式(simultaneous transmission trick)能 有效穿透以上三种类型的防火墙。在真实场景中, 我们主要在处理的是 IP-​and-​port endpoint-​dependent 防火墙。

因此,对于实际 NAT 穿透实现,我们可以将以上分类简化成:


Endpoint-​Independent NAT mapping Endpoint-​Dependent NAT mapping (dst. IP only)
Firewall is yes Easy NAT Hard NAT

5.5 更多 NAT 规范(RFC)

想了解更多新的 NAT 术语,可参考

  • RFC 4787 (NAT Behavioral Requirements for UDP)
  • RFC 5382 (for TCP)
  • RFC 5508 (for ICMP)

如果自己实现 NAT,那应该(should)遵循这些 RFC 的规范,这样才能使你的 NAT 行为符合业界惯例,与其他厂商的设备或软件良好兼容。

6.1 问题回顾与保底方式(中继)

补完基础知识(尤其是定义了什么是 hard NAT)之后,回到我们的 NAT 穿透主题。

  • 第 1~4 节已经解决了 STUN 和防火墙穿透的问题,
  • hard NAT 对我们来说是个大问题,只要路径上出现一个这种设备,前面的方案就行不通了。

准备放弃了吗? 这才进入 NAT 真正有挑战的部分:如果已经试过了前面介绍的所有方式 仍然不能穿透,我们该怎么办呢?

  • 实际上,确实有很多 NAT 实现在这种情况下都会选择放弃,向用户报一个“无法连接”之类的错误。
  • 但对我们来说,这么快就放弃显然是不可接受的 —— 解决不了连通性问题,Tailscale 就没有存在的意义。

我们的保底解决方式是:创建一个中继连接(relay)实现双方的无障碍地通信。 但是,中继方式性能不是很差吗?这要看具体情况:

  • 如果能直连,那显然没必要用中继方式;
  • 但如果无法直连,而中继路径又非常接近双方直连的真实路径,并且带宽足够大,那中 继方式并不会明显降低通信质量。延迟肯定会增加一点,带宽会占用一些,但 相比完全连接不上,还是更能让用户接受的

不过要注意:我们只有在无法直连时才会选择中继方式。实际场景中,

  1. 对于大部分网络,我们都能通过前面介绍的方式实现直连,
  2. 剩下的长尾用中继方式来解决,并不算一个很糟的方式。

此外,某些网络会阻止 NAT 穿透,其影响比这种 hard NAT 大多了。例如,我们观察到 UC Berkeley guest WiFi 禁止除 DNS 流量之外的所有 outbound UDP 流量。 不管用什么 NAT 黑科技,都无法绕过这个拦截。因此我们终归还是需要一些可靠的 fallback 机制。

6.2 中继协议:TURN、DERP

有多种中继实现方式。

  1. TURN (Traversal Using Relays around NAT):经典方式,核心理念是

    1. 用户(人)先去公网上的 TURN 服务器认证,成功后后者会告诉你:“我已经为你分配了 ip:port,接下来将为你中继流量”,
    2. 然后将这个 ip:port 地址告诉对方,让它去连接这个地址,接下去就是非常简单的客户端/服务器通信模型了。

    Tailscale 并不使用 TURN。这种协议用起来并不是很好,而且与 STUN 不同, 它没有真正的交互性,因为互联网上并没有公开的 TURN 服务器。

  2. DERP (Detoured Encrypted Routing Protocol)

    这是我们创建的一个协议,DERP

    1. 它是一个通用目的包中继协议,运行在 HTTP 之上,而大部分网络都是允许 HTTP 通信的。
    2. 它根据目的公钥(destination’s public key)来中继加密的流量(encrypted payloads)。

    前面也简单提到过,DERP 既是我们在 NAT 穿透失败时的保底通信方式(此时的角色 与 TURN 类似),也是在其他一些场景下帮助我们完成 NAT 穿透的旁路信道。 换句话说,它既是我们的保底方式,也是有更好的穿透链路时,帮助我们进行连接升 级(upgrade to a peer-​to-​peer connection)的基础设施。

6.3 小结

有了“中继”这种保底方式之后,我们穿透的成功率大大增加了。 如果此时不再阅读本文接下来的内容,而是把上面介绍的穿透方式都实现了,我预计:

  • 90% 的情况下,你都能实现直连穿透;
  • 剩下的 10% 里,用中继方式能穿透一些(some);

这已经算是一个“足够好”的穿透实现了。

如果你并不满足于“足够好”,那我们可以做的事情还有很多!

本节将介绍一些五花八门的 tricks,在某些特殊场景下会帮到我们。单独使用这项技术都 无法解决 NAT 穿透问题,但将它们巧妙地组合起来,我们能更加接近 100% 的穿透成功率。

7.1 穿透 hard NAT:暴力端口扫描

回忆 hard NAT 中遇到的问题,如下图所示,关键问题是:easy NAT 不知道该往 hard NAT 方的哪个 ip:port 发包。

nat-birthday-attack-1.png

必须要往正确的 ip:port 发包,才能穿透防火墙,实现双向互通。 怎么办呢?

  1. 首先,我们能知道 hard NAT 的一些 ip:port,因为我们有 STUN 服务器。

    这里先假设我们获得的这些 IP 地址都是正确的(这一点并不总是成立,但这里先这么假 设。而实际上,大部分情况下这一点都是成立的,如果对此有兴趣,可以参考 REQ-​2 in RFC 4787)。

  2. IP 地址确定了,剩下的就是端口了。总共有 65535 中可能,我们能遍历这个端口范围吗?

    如果发包速度是 100 packets/s,那最坏情况下,需要 10 分钟来找到正确的端口。 还是那句话,这虽然不是最优的,但总比连不上好。

    这很像是端口扫描(事实上,确实是),实际中可能会触发对方的网络入侵检测软件。

7.2 基于生日悖论改进暴力扫描:hard side 多开端口 + easy side 随机探测

利用 birthday paradox 算法, 我们能对端口扫描进行改进。

  • 上一节的基本前提是:hard side 只打开一个端口,然后 easy side 暴力扫描 65535 个端口来寻找这个端口;
  • 这里的改进是:在 hard size 开多个端口,例如 256 个(即同时打开 256 个 socket,目的地址都是 easy side 的 ip:port), 然后 easy side 随机探测这边的端口。

这里省去算法的数学模型,如果你对实现干兴趣,可以看看我写的 python calculator。 计算过程是“经典”生日悖论的一个小变种。 下面是随着 easy side random probe 次数(假设 hard size 256 个端口)的变化,两边打开的端口有重合(即通信成功)的概率:

随机探测次数 成功概率
174 50%
256 64%
1024 98%
2048 99.9%

根据以上结果,如果还是假设 100 ports/​s 这样相当温和的探测速率,那 2 秒钟就有约 50% 的成功概率。 即使非常不走运,我们仍然能在 20s 时几乎 100% 穿透成功,而此时只探测了总端口空间的 4%

非常好!虽然这种 hard NAT 给我们带来了严重的穿透延迟,但最终结果仍然是成功的。 那么,如果是两个 hard NAT,我们还能处理吗?

7.3 双 hard NAT 场景

nat-birthday-attack-2.png

这种情况下仍然可以用前面的 多端口+随机探测 方式,但成功概率要低很多了:

  • 每次通过一台 hard NAT 去探测对方的端口(目的端口)时,我们自己同时也生成了一个随机源端口
  • 这意味着我们的搜索空间变成了二维 {src port, dst port} 对,而不再是之前的一维 dst port 空间。

这里我们也不就具体计算展开,只告诉结果:仍然假设目的端打开 256 个端口,从源端发起 2048 次(20 秒), 成功的概率是:0.01%

如果你之前学过生日悖论,就并不会对这个结果感到惊讶。理论上来说,

  • 要达到 99.9% 的成功率,我们需要两边各进行170,000 次探测 —— 如果还是以 100 packets/​sec 的速度,就需要 28 分钟
  • 要达到 50% 的成功率,“只”需要 54,000 packets,也就是 9 分钟
  • 如果不使用生日悖论方式,而且暴力穷举,需要 1.2 年时间

对于某些应用来说,28 分钟可能仍然是一个可接受的时间。用半个小时暴力穿透 NAT 之后, 这个连接就可以一直用着 —— 除非 NAT 设备重启,那样就需要再次花半个小时穿透建个新连接。但对于 交互式应用来说,这样显然是不可接受的。

更糟糕的是,如果去看常见的办公网路由器,你会震惊于它的 active session low limit 有多么低。 例如,一台 Juniper SRX 300 最多支持 64,000 active sessions。 也就是说,

  • 如果我们想创建一个成功的穿透连接,就会把它的整张 session 表打爆 (因为我们要暴力探测 65535 个端口,每次探测都是一条新连接记录)! 这显然要求这台路由器能从容优雅地处理过载的情况
  • 这只是创建一条连接带来的影响!如果 20 台机器同时对这台路由器发起穿透呢?绝对的灾难!

至此,我们通过这种方式穿透了比之前更难一些的网络拓扑。这是一个很大的成就,因为 家用路由器一般都是 easy NAT,hard NAT 一般都是办公网路由器或云 NAT 网关。 这意味着这种方式能帮我们解决

  • home-to-office(家->办公室)
  • home-​to-​cloud (家->云)

的场景,以及一部分

  • office-​to-​cloud (办公室->云)
  • cloud-​to-​cloud (云->办公室)

场景。

7.4 控制端口映射(port mapping)过程:UPnP/NAT-PMP/PCP 协议

如果我们能让 NAT 设备的行为简单点,不要把事情搞这么复杂,那建 立连接(穿透)就会简单很多。真有这样的好事吗?还真有,有专门的一种协议叫 端口映射协议(port mapping protocols)。通过这种协议禁用掉前面 遇到的那些乱七八糟的东西之后,我们将得到一个非常简单的“请求-响应”。

下面是三个具体的端口映射协议:

  1. UPnP IGD (Universal Plug’n’Play Internet Gateway Device)

    最老的端口控制协议, 诞生于 1990s 晚期,因此使用了很多上世纪 90 年代的技术 (XML、SOAP、multicast HTTP over UDP —— 对,HTTP over UDP ),而且很难准确和安全地实现这个协议。但以前很多路由器都内置了 UPnP 协议, 现在仍然很多。

    请求和响应:

    • “你好,请将我的 lan-ip:port 转发到公网(WAN)”,
    • “好的,我已经为你分配了一个公网映射 wan-ip:port ”。
  2. NAT-​PMP

    UPnP IGD 出来几年之后,Apple 推出了一个功能类似的协议,名为 NAT-​PMP (NAT Port Mapping Protocol)。

    但与 UPnP 不同,这个协议做端口转发,不管是在客户端还是服务端,实现起来都非常简单。

  3. PCP

    稍后一点,又出现了 NAT-​PMP v2 版,并起了个新名字PCP (Port Control Protocol)。

因此要更好地实现穿透,可以

  1. 先判断本地的默认网关上是否启用了 UPnP IGD, NAT-​PMP and PCP
  2. 如果探测发现其中任何一种协议有响应,我们就申请一个公网端口映射

    可以将这理解为一个加强版 STUN:我们不仅能发现自己的公网 ip:port,而且能指示我们的 NAT 设备对我们的通信对端友好一些 —— 但并不是为这个端口修改或添加防火墙规则。

  3. 接下来,任何到达我们 NAT 设备的、地址是我们申请的端口的包,都会被设备转发到我们。

但我们不能假设这个协议一定可用

  1. 本地 NAT 设备可能不支持这个协议;
  2. 设备支持但默认禁用了,或者没人知道还有这么个功能,因此从来没开过;
  3. 安全策略要求关闭这个特性。

    这一点非常常见,因为 UPnP 协议曾曝出一些高危漏洞(后面都修复了,因此如果是较新的设备,可以安全地使用 UPnP —— 如果实现没问题)。 不幸的是,某些设备的配置中,UPnP, NAT-PMP,PCP 是放在一个开关里的(可能 统称为 “UPnP” 功能),一开全开,一关全关。因此如果有人担心 UPnP 的安全性,他连另 外两个也用不了。

最后,终归来说,只要这种协议可用,就能有效地减少一次 NAT,大大方便建连过程。 但接下来看一些不常见的场景。

7.5 多 NAT 协商(Negotiating numerous NATs)

目前为止,我们看到的客户端和服务端都各只有一个 NAT 设备。如果有多个 NAT 设备会 怎么样?例如下面这种拓扑:

nat-multiple-layers.png

这个例子比较简单,不会给穿透带来太大问题。包从客户端 A 经过多次 NAT 到达公网的过程,与前面分析的穿过多层有状态防火墙是一样的:

  • 额外的这层(NAT 设备)对客户端和服务端来说都不可见,我们的穿 透技术也不关心中间到底经过了多少层设备。
  • 真正有影响的其实只是最后一层设备,因为对端需要在这一层设备上 找到入口让包进来。

具体来说,真正有影响的是端口转发协议。

  1. 客户端使用这种协议分配端口时,为我们分配端口的是最靠近客户端的这层 NAT 设备;
  2. 而我们期望的是让最离客户端最远的那层 NAT 来分配,否则我们得到的就是一个网络中间层分配的 ip:port,对端是用不了的;
  3. 不幸的是,这几种协议都不能递归地告诉我们下一层 NAT 设备是多少 —— 虽然可以用 traceroute 之类的工具来探测网络路径,再加上 猜路上的设备是不是 NAT 设备(尝试发送 NAT 请求) —— 但这个就看运气了。

这就是为什么互联网上充斥着大量的文章说 double-​NAT 有多糟糕,以 及警告用户为保持后向兼容不要使用 double-NAT。但实际上,double-NAT 对于绝大部分 互联网应用来说都是不可见的(透明的),因为大部分应用并不需要主动地做这种 NAT 穿 透。

但我也绝不是在建议你在自己的网络中设置 double-​NAT。

  1. 破坏了端口映射协议之后,某些视频游戏的多人(multiplayer)模式就会无法使用,
  2. 也可能会使你的 IPv6 网络无法派上用场,后者是不用 NAT 就能双向直连的一个好方案。

但如果 double-​NAT 并不是你能控制的,那除了不能用到这种端口映射协议之外,其他大部分东西都是不受影响的。

double-​NAT 的故事到这里就结束了吗?—— 并没有,而且更大型的 double-​NAT 场景将展现在我们面前。

7.6 运营商级 NAT 带来的问题

即使用 NAT 来解决 IPv4 地址不够的问题,地址仍然是不够用的,ISP(互联网服务提供商) 显然 无法为每个家庭都分配一个公网 IP 地址。那怎么解决这个问题呢?ISP 的做法是不够了就再嵌套一层 NAT

  1. 家用路由器将你的客户端 SNAT 到一个 “intermediate” IP 然后发送到运营商网络,
  2. ISP’s network 中的 NAT 设备再将这些 intermediate IPs 映射到少量的公网 IP。

后面这种 NAT 就称为“运营商级 NAT”(carrier-​grade NAT,或称电信级 NAT),缩写 CGNAT。如下图所示:

nat-cgnat-1.png

CGNAT 对 NAT 穿透来说是一个大麻烦。

  • 在此之前,办公网用户要快速实现 NAT 穿透,只需在他们的路由器上手动设置端口映射就行了。
  • 但有了 CGNAT 之后就不管用了,因为你无法控制运营商的 CGNAT!

好消息是:这其实是 double-​NAT 的一个小变种,因此前面介绍的解决方式大部分还仍然是适用的。 某些东西可能会无法按预期工作,但只要肯给 ISP 交钱,这些也都能解决。 除了 port mapping protocols,其他我们已经介绍的所有东西在 CGNAT 里都是适用的。

新挑战:同一 CGNAT 侧直连,STUN 不可用

但我们确实遇到了一个新挑战:如何直连两个在同一 CGNAT 但不同家用路由器中的对端呢?如下图所示:

nat-cgnat-2.png

在这种情况下,STUN 就无法正常工作了:STUN 看到的是客户端在公网(CGNAT 后面)看到的地址, 而我们想获得的是在 “middle network” 中的 ip:port,这才是对端真正需要的地址,

解决方案:如果端口映射协议能用:一端做端口映射

怎么办呢?

如果你想到了端口映射协议,那恭喜,答对了!如果 peer 中任何一个 NAT 支持端口映射协议, 对我们就能实现穿透,因为它分配的 ip:port 正是对端所需要的信息。

这里讽刺的是:double-NAT(指 CGNAT)破坏了端口映射协议,但在这里又救了我们! 当然,我们假设这些协议一定可用,因为 CGNAT ISP 倾向于在它们的家用路由器侧关闭 这些功能,已避免软件得到“错误的”结果,产生混淆。

解决方案:如果端口映射协议不能用:NAT hairpin 模式

如果不走运,NAT 上没有端口映射功能怎么办?

让我们回到基于 STUN 的技术,看会发生什么。两端在 CGNAT 的同一侧,假设 STUN 告诉我们 A 的地址是 2.2.2.2:1234,B 的地址是 2.2.2.2:5678

那么接下来的问题是:如果 A 向 2.2.2.2:5678 发包会怎么样?期望的 CGNAT 行为是:

  1. 执行 A 的 NAT 映射规则,即对 2.2.2.2:1234 -> 2.2.2.2:5678 进行 SNAT。
  2. 注意到目的地址 2.2.2.2:5678 匹配到的是 B 的入向 NAT 映射,因此接着对这个包执行 DNAT,将目的 IP 改成 B 的私有地址。
  3. 通过 CGNAT 的 internal 接口(而不是 public 接口,对应公网)将包发给 B。

这种 NAT 行为有个专门的术语,叫 hairpinning(直译为发卡,意思 是像发卡一样,沿着一边上去,然后从另一边绕回来),

hairpin-icon.png

大家应该猜到的一个事实是:不是所以 NAT 都支持 hairpin 模式。 实际上,大量 well-​behaved NAT 设备都不支持 hairpin 模式,

  • 因为它们都有 “只有 src_​ip 是私有地址且 dst_​ip 是公网地址的包才会经过我” 之类的假设。
  • 因此对于这种目的地址不是公网、需要让路由器把包再转回内网的包,它们会直接丢弃
  • 这些逻辑甚至是直接实现在路由芯片中的,因此除非升级硬件,否则单靠软件编程无法改变这种行为。

Hairpin 是所有 NAT 设备的特性(支持或不支持),并不是 CGNAT 独有的。

  1. 在大部分情况下,这个特性对我们的 NAT 穿透目的来说都是无所谓的,因为我们期望中 两个 LAN NAT 设备会直接通信,不会再向上绕到它们的默认网关 CGNAT 来解决这个问题

    Hairpin 特性可有可无这件事有点遗憾,这可能也是为什么 hairpin 功能经常 broken 的原因。

  2. 一旦必须涉及到 CGNAT,那 hairpinning 对连接性来说就至关重要了。

    Hairpinning 使内网连接的行为与公网连接的行为完成一致,因此我们无需关心目的 地址类型,也不用知晓自己是否在一台 CGNAT 后面。

如果 hairpinning 和 port mapping protocols 都不可用,那只能降级到中继模式了

7.7 全 IPv6 网络:理想之地,但并非问题全无

行文至此,一些读者可能已经对着屏幕咆哮:不要再用 IPv4 了! 花这么多时间精力解决这些没意义的东西,还不如直接换成 IPv6!

  • 的确,之所以有这些乱七八糟的东西,就是因为 IPv4 地址不够了,我们一直在用越来越复杂的 NAT 来给 IPv4 续命
  • 如果 IP 地址够用,无需 NAT 就能让世界上的每个设备都有一个自己的公网 IP 地址,这些问题不就解决了吗?

简单来说,是的,这也正是 IPv6 能做的事情。但是,也只说对了一半:在理想的全 IPv6 世界中,所有这些东西会变得更加简单,但我们面临的问题并不会完全消失 —— 因为有状态防火墙仍然还是存在的

  • 办公室中的电脑可能有一个公网 IPv6 地址,但你们公司肯定会架设一个防火墙,只允许 你的电脑主动访问公网,而不允许反向主动建连。
  • 其他设备上的防火墙也仍然存在,应用类似的规则。

因此,我们仍然会用到

  1. 本文最开始介绍的防火墙穿透技术,以及
  2. 帮助我们获取自己的公网 ip:port 信息的旁路信道
  3. 仍然需要在某些场景下 fallback 到中继模式,例如 fallback 到最通用的 HTTP 中继 协议,以绕过某些网络禁止 outbound UDP 的问题。

但我们现在可以抛弃 STUN、生日悖论、端口映射协议、hairpin 等等东西了。 这是一个好消息!

全球 IPv4/​IPv6 部署现状

另一个更加严峻的现实问题是:当前并不是一个全 IPv6 世界。目前世界上

  • 大部分还是 IPv4,
  • 大约 33% 是 IPv6,而且分布极度不均匀,因此某些 通信对所在的可能是 100% IPv6,也可能是 0%,或二者之间。

不幸的是,这意味着,IPv6 **还**无法作为我们的解决方案。 就目前来说,它只是我们的工具箱中的一个备选。对于某些 peer 来说,它简直是完美工 具,但对其他 peer 来说,它是用不了的。如果目标是“任何情况下都能穿透(连接) 成功”,那我们就仍然需要 IPv4+NAT 那些东西。

新场景:NAT64/DNS64

IPv4/​IPv6 共存也引出了一个新的场景:NAT64 设备。

nat-ipv6.png

前面介绍的都是 NAT44 设备:它们将一个 IPv4 地址转换成另一 IPv4 地址。 NAT64 从名字可以看出,是将一个内侧 IPv6 地址转换成一个外侧 IPv4 地址。 利用 DNS64 设备,我们能将 IPv4 DNS 应答给 IPv6 网络,这样对终端来说,它看到的就是一个 全 IPv6 网络,而仍然能访问 IPv4 公网。

Incidentally, you can extend this naming scheme indefinitely. There have been some experiments with NAT46; you could deploy NAT66 if you enjoy chaos; and some RFCs use NAT444 for carrier-​grade NAT.

如果需要处理 DNS 问题,那这种方式工作良好。例如,如果连接到 google.com,将这个域名解析成 IP 地址的过程会涉及到 DNS64 设备,它又会进一步 involve NAT64 设备,但后一步对用户来说是无感知的。

对于 NAT 和防火墙穿透来说,我们会关心每个具体的 IP 地址和端口

解决方案:CLAT (Customer-​side transLATor)

如果设备支持 CLAT (Customer-​side translator — from Customer XLAT),那我们就很幸运:

  • CLAT 假装操作系统有直接 IPv4 连接,而背后使用的是 NAT64,以对应用程序无感知。 在有 CLAT 的设备上,我们无需做任何特殊的事情。
  • CLAT 在移动设备上非常常见,但在桌面电脑、笔记本和服务器上非常少见, 因此在后者上,必须自己做 CLAT 做的事情:检测 NAT64+DNS64 的存在,然后正确地使用它们。

解决方案:CLAT 不存在时,手动穿透 NAT64 设备

  1. 首先检测是否存在 NAT64+DNS64。

    方法很简单:向 ipv4only.arpa. 发送一个 DNS 请求。这个域名会解析 到一个已知的、固定的 IPv4 地址,而且是纯 IPv4 地址。如果得到的 是一个 IPv6 地址,就可以判断有 DNS64 服务器做了转换,而它必然会用到 NAT64。这样 就能判断出 NAT64 的前缀是多少。

  2. 此后,要向 IPv4 地址发包时,发送格式为{NAT64 prefix + IPv4 address} 的 IPv6 包。 类似地,收到来源格式为 {NAT64 prefix + IPv4 address} 的包时,就是 IPv4 流量。

  3. 接下来,通过 NAT64 网络与 STUN 通信来获取自己在 NAT64 上的公网 ip:port,接 下来就回到经典的 NAT 穿透问题了 —— 除了需要多做一点点事情。

幸运的是,如今的大部分 v6-​only 网络都是移动运营商网络,而几乎所有手机都支持 CLAT。 运营 v6-​only 网络的 ISPs 会在他们给你的路由器上部署 CLAT,因此最后你其实不需要做什么事情。 但如果想实现 100% 穿透,就需要解决这种边边角角的问题,即必须显式支持从 v6-​only 网络连接 v4-​only 对端。

7.8 将所有解决方式集成到 ICE 协议

针对具体场景,该选择哪种穿透方式?

至此,我们的 NAT 穿透之旅终于快结束了。我们已经覆盖了有状态防火墙、简单和高级 NAT、IPv4 和 IPv6。只要将以上解决方式都实现了,NAT 穿透的目的就达到了!

但是,

  • 对于给定的 peer,如何判断改用哪种方式呢?
  • 如何判断这是一个简单有状态防火墙的场景,还是该用到生日悖论算法,还是需要手动处理 NAT64 呢?
  • 还是通信双方在一个 WiFi 网络下,连防火墙都没有,因此不需要任何操作呢?

早期 NAT 穿透比较简单,能让我们精确判断出 peer 之间的路径特点,然后针对性地采用相应的解决方式。 但后面,网络工程师和 NAT 设备开发工程师引入了一些新理念,给路径判断造成很大困难。因此 我们需要简化客户端侧的思考(判断逻辑)。

这就要提到 Interactive Connectivity Establishment (ICE,交换式连接建立) 协议了。 与 STUN/​TURN 类似,ICE 来自电信领域,因此其 RFC 充满了 SIP、SDP、信令会话、拨号等等电话术语。 但如果忽略这些领域术语,我们会看到它描述了一个极其优雅的判断最佳连接路径的算法

真的?这个算法是:每种方法都试一遍,然后选择最佳的那个方法。就是这个算法,惊喜吗?

来更深入地看一下这个算法。

ICE (Interactive Connectivity Establishment) 算法

这里的讨论不会严格遵循 ICE spec,因此如果是在自己实现一个可互操作的 ICE 客户端,应该通读RFC 8445, 根据它的描述来实现。这里忽略所有电信术语,只关注核心的算法逻辑, 并提供几个在 ICE 规范允许范围的灵活建议。

  1. 为实现和某个 peer 的通信,首先需要确定我们自己用的(客户端侧)这个 socket 的地址, 这是一个列表,至少应该包括:

    1. 我们自己的 IPv6 ip:ports
    2. 我们自己的 IPv4 LAN ip:ports(局域网地址)
    3. 通过 STUN 服务器获取到的我们自己的 IPv4 WAN ip:ports公网地址,可能会经过 NAT64 转换)
    4. 通过端口映射协议获取到的我们自己的 IPv4 WAN ip:port(NAT 设备的端口映射协议分配的公网地址
    5. 运营商提供给我们的 endpoints(例如,静态配置的端口转发
  2. 通过旁路信道与 peer 互换这个列表。两边都拿到对方的列表后,就开始互相探测对方提供的地址。 列表中地址没有优先级,也就是说,如果对方给的了 15 个地址,那我们应该把这 15 个地址都探测一遍。

    这些探测包有两个目的

    1. 打开防火墙,穿透 NAT,也就是本文一直在介绍的内容;
    2. 健康检测。我们在不断交换(最好是已认证的)“ping/pong” 包,来检测某个特定的路径是不是端到端通的。
  3. 最后,一小会儿之后,从可用的备选地址中(根据某些条件)选择“最佳”的那个,任务完成!

这个算法的优美之处在于:只要选择最佳线路(地址)的算法是正确的,那就总能获得最佳路径。

  • ICE 会预先对这些备选地址进行排序(通常:LAN > WAN > WAN+NAT),但用户也可以自己指定这个排序行为。
  • 从 v0.100.0 开始,Tailscale 从原来的 hardcode 优先级切换成了根据 round-​trip latency 的方式,它大部分情况下排序的结果和 LAN > WAN > WAN+NAT 是一致的。 但相比于静态排序,我们是动态计算每条路径应该属于哪个类别。

ICE spec 将协议组织为两个阶段:

  1. 探测阶段
  2. 通信阶段

但不一定要严格遵循这两个步骤的顺序。在 Tailscale,

  • 我们发现更优的路径之后就会自动切换过去,
  • 所有的连接都是先选择 DERP 模式(中继模式)。这意味着连接立即就能建立(优先级最低但 100% 能成功的模式),用户不用任何等待,
  • 然后并行进行路径发现。通常几秒钟之后,我们就能发现一条更优路径,然后将现有连接透明升级(upgrade)过去。

但有一点需要关心:非对称路径。ICE 花了一些精力来保证通信双方选择的是相同的网络 路径,这样才能保证这条路径上有双向流量,能保持防火墙和 NAT 设备的连接一直处于 open 状态。 自己实现的话,其实并不需要花同样大的精力来实现这个保证,但需要确保你所有使用的所有路径上,都有双向流量。 这个目标就很简单了,只需要定期在所有已使用的路径上发 ping/pong 就行了。

健壮性与降级

要实现健壮性,还需要检测当前已选择的路径是否已经失败了(例如,NAT 设备维护清掉了所有状态), 如果失败了就要降级(downgrade)到其他路径。这里有两种方式:

  1. 持续探测所有路径,维护一个降级时会用的备用地址列表;
  2. 直接降级到保底的中继模式,然后再通过路径探测升级到更好的路径。

    考虑到发生降级的概率是非常小的,因此这种方式可能是更经济的。

7.9 安全

最后需要提到安全。

本文的所有内容都假设:我们使用的上层协议已经有了自己的安全机制( 例如 QUIC 协议有 TLS 证书,WireGuard 协议有自己的公钥)。 如果还没有安全机制,那显然是要立即补上的。一旦动态切换路径,基于 IP 的安全机制就是无用的了 (IP 协议最开始就没怎么考虑安全性),至少要有端到端的认证

  • 严格来说,如果上层协议有安全机制,那即使收到是欺骗性的 ping/​pong 流量,问题都不大, 最坏的情况也就是攻击者诱导两端通过他们的系统来中继流量。 而有了端到端安全机制,这并不是一个大问题(取决于你的威胁模型)。
  • 但出于谨慎考虑,最好还是对路径发现的包也做认证和加密。具体如何做可以咨询你们的应用安全工程师。

我们终于完成了 NAT 穿透的目标!

如果实现了以上提到的所有技术,你将得到一个业内领先的 NAT 穿透软件,能在绝大多数场景下实现端到端直连。 如果直连不了,还可以降级到保底的中继模式(对于长尾来说只能靠中继了)。

但这些工作相当复杂!其中一些问题研究起来很有意思,但很难做到完全正确,尤其是那些 非常边边角角的场景,真正出现的概率极小,但解决它们所需花费的经历又极大。 不过,这种工作只需要做一次,一旦解决了,你就具备了某种超级能力: 探索令人激动的、相对还比较崭新的端到端应用(peer-​to-​peer applications)世界。

8.1 跨公网 端到端直连

去中心化软件领域中的许多有趣想法,简化之后其实都变成了 跨过公网(互联网)实现端到端直连 这一问题,开始时可能觉得很简单,但真正做才 发现比想象中难多了。现在知道如何解决这个问题了,动手开做吧!

8.2 结束语之 TL; DR

实现健壮的 NAT 穿透需要下列基础:

  1. 一种基于 UDP 的协议;
  2. 能在程序内直接访问 socket;
  3. 有一个与 peer 通信的旁路信道;
  4. 若干 STUN 服务器;
  5. 一个保底用的中继网络(可选,但强烈推荐)

然后需要:

  1. 遍历所有的 ip:port
  2. 查询 STUN 服务器来获取自己的公网 ip:port 信息,以及判断自己这一侧的 NAT 的“难度”(difficulty);
  3. 使用 port mapping 协议来获取更多的公网 ip:ports
  4. 检查 NAT64,通过它获取自己的公网 ip:port
  5. 将自己的所有公网 ip:ports 信息通过旁路信道与 peer 交换,以及某些加密秘钥来保证通信安全;
  6. 通过保底的中继方式与对方开始通信(可选,这样连接能快速建立)
  7. 如果有必要/想这么做,探测对方的提供的所有 ip:port,以及执行生日攻击(birthday attacks)来穿透 harder NAT;
  8. 发现更优路径之后,透明升级到该路径;
  9. 如果当前路径断了,降级到其他可用的路径;
  10. 确保所有东西都是加密的,并且有端到端认证。

« [译] 写给工程师:关于证书(certificate)和公钥基础设施(PKI)的一切(SmallStep, 2018) [译] [论文] 可虚拟化第三代(计算机)架构的规范化条件(ACM, 1974) »


https://arthurchiao.art/blog/how-nat-traversal-works-zh/

Warp: 一种安全的跨平台文件共享应用


via here.news

在 Linux 和 Windows 之间安全地共享文件的无缝方式?试试这个!

在我们的首次尝试系列文章中,我们发现了一种在 Linux 和 Windows 系统之间传输文件的安全高效方法。

一个名为“Warp”的工具,是 GNOME Circle 的一部分,包含扩展 GNOME 生态系统的应用程序。Warp 通过互联网或本地网络实现文件的无缝传输。

让我们来看看它。

Warp:概述⭐

Picked image

Warp 主要用 Rust 编程语言编写,是一个基于 GTK 的文件传输应用,使用“Magic Wormhole”协议在互联网/本地网络上进行文件传输。

所有文件传输都是加密的,接收者必须使用基于单词的代码来访问文件,以防止任何滥用。

允许我向您展示它是如何工作的。

当您第一次启动应用程序时,您会看到一个欢迎屏幕和一个关于 Warp 的简短介绍。

Picked image

继续后,您将进入“发送”菜单,可以选择要发送的文件或文件夹。

📋您还可以将文件和文件夹拖放到应用程序中。

Warp发送文件页面的屏幕截图

Picked image

处理后,将显示一个屏幕,其中包含文本和二维码形式的传输代码。您必须将其安全发送给接收者,他们可以开始下载文件。

📋由于 Warp 是一个跨平台应用,您可以在 Linux 和 Windows 系统之间发送文件。

Warp传输代码页面的屏幕截图

那么,接收方看起来如何呢?

好吧,他们必须进入“接收”菜单,将传输代码粘贴到文本框中。他们还可以扫描二维码将确切文本复制到其设备上。

Picked image

在粘贴代码并单击“接收文件”后,Warp 将开始连接到发送者的设备。

Picked image

如果成功,您将看到“已连接到对等”状态。如果没有,发送者或接收者的系统/网络出现问题。

Picked image

成功连接后,接收者可以选择“接受”将其保存到系统的“下载”文件夹中,或者选择“另存为”将其保存到自己选择的位置。

Picked image
Picked image

就是这样。当文件传输完成时,接收者将看到以下屏幕:

Picked image

用户还可以进入三条丝带图标下的“首选项”菜单,访问高级设置,如设置 Rendezvous/Transit 服务器 URL 或将代码字数设置为更长的字数以增强安全性。

Picked image

关于 Warp 就是这些;它非常简单,工作得很好。

📥 获取 Warp

Warp 适用于 Linux 和 Windows;您可以在其 GitLab 页面上选择所需的软件包或查看源代码。

对于 Linux 用户,您可以从 Flathub 获取它。

备用翻墙方案:eSIM

 
研究了下esim卡,发现用这玩意翻墙实在太简单了,去play store 下载一个能利用esim相关技术提供上网服务的app,例如eskimo(这软件还送你1.5G流量),直接就相当于获得一个国外手机卡,马上就可以翻墙。难怪国产手机到目前都不支持esim,原来是在这里卡了脖子 
 
 购买pixel手机的理由又加一!

 随后有人评论

提醒下,Pixel系列不要用DJB的eSIM,无法share上网。

更有人相信GFW不会坐视不管

sim卡流量走运营商network,如果看ip会看到分配的是运营商所在地ip,这个洞早晚得堵上

T-Mobile原生手机卡购买激活教程,eSIM激活教程,改3美元套餐,转入手机靓号

via https://itangtalk.com/tmobile/

今天向大家介绍一张全新的美国手机卡,美国T-​Mobile预付费卡。

128hsld.jpg

首先这张卡是T-​Mobile原生手机卡,是预付费卡,不需要去进行实名认证。

这张卡默认的套餐是月租10美元,其中包含1000条短信和1000分钟通话。当然这些短信和通话,只限于和美国手机号码进行通讯使用。可以说这个价格能够有这么多的免费用量还是挺香的。

但是这张卡在国内使用默认的套餐,有个致命的问题,它无法进行国际漫游。所以说这张卡在中国是没有信号的。

那么在中国没有信号,我们要如何去使用这张卡呢?这个时候就不得不提到WiFi通话的功能。所谓的WiFi通话,就是我们在一些比较偏僻的地方或者说地下室,很有可能没有手机信号,但是这个时候的话如果有WiFi,我们就可以把手机连接到WiFi,然后通过WiFi再和手机的基站进行通讯。这样的话就能够达到正常使用的效果了。

目前支持WiFi通话的手机很多,比如说iPhone 5C以上的机型基本上都是支持WiFi通话的,包括中国大陆国行的iPhone,也是支持WiFi通话的。

KZpIBR.png

而安卓手机,像国产的一些大部分手机都是不支持WiFi通话的。小米和华为的个别型号是支持的。

这样的话,即使这张卡在中国没有信号,我们也可以通过连接WiFi,使用WiFi通话来使用这张卡。

如果仅仅是上面我讲的,只能在中国使用10美元的无漫游套餐,那么这张卡的吸引力,其实并没有那么大。促使我开始深入研究这张卡的动力是:

其实这张卡是可以联系客服,手动转成月租三美元的手机卡,甚至有小伙伴分享可以转成零月租的手机卡。

转成三美元的卡之后,首先第一个优点就是它的月租非常低,三美元的月租包含,30条短信和30分钟的美国本地通话。其次,它还是可以在WiFi通话的环境下正常使用,而且转成三美元的套餐之后,它是可以国际漫游的,也就是说在中国是有信号的了。

这个时候其实就和之前介绍的Ultra PayGo卡是一样的了。

这张卡的购买渠道一共有两种。一种是可以通过国内的电商平台,包括我自己的自营商店平台,也有这张实体卡的出售。

第二种渠道是,如果说我们的手机支持eSIM,我们其实可以去下载T-Mobile的官方APP,然后去订购下载eSIM卡,这样就不需要再去额外购买实体的手机卡了。

即使我们已经购买了实体卡,也可以联系客服转换成eSIM

接下来就向大家演示一下,如何去购买T-​Mobile的原生手机卡,包括在官网订购eSIM,如何去激活手机卡,如何去改套餐。希望能够帮到大家。

我们可以先来到T-Mobile的官网,一起了解一下他们家的原生手机卡。

6jiwAM.png

首先,他们家的手机卡主要分为两种。一种是后付费,这种需要人在美国并且有美国的身份才可以申请,需要实名认证。

另外一种就是预付费卡,不需要实名认证,只要购买卡片交钱就可以使用,没有那么多繁琐的流程。我们这次讲的就是预付费卡。

我们直接去点击预付费卡。

hVXqMN.png

然后跳转到这个界面。

lbPpfV.png

在这里,我们可以去输入自己手机的IMEI号码。IMEI相当于手机的一个身份证号,我们可以点击这个感叹号,它提示我们在拨号界面拨打*#06#,就可以看到IMEI。

然后我们在这个地方输入我们的IMEI,然后检查一下我们的手机能否使用T-Mobile的手机卡。我这边显示是可以使用的。

NbP0Rx.png

接下来去确认一下,我们的手机是否有锁,像美国一些合约机可能无法正常使用。我们直接勾选无锁就可以了。

XCCQGM.png

下一步有三个选项:

JklmgD.png

第一个是激活eSIM卡,如果我们的手机支持eSIM,可以通过T-​Mobile的官方APP直接订购eSIM,不需要再购买实体手机卡;

第二种是直接在官网订购实体的SIM卡,它会把这个实体的SIM卡邮寄给我们;

第三种是已经购买过了,卡已在手,直接激活。我们在这里输入SIM卡的卡号进行直接激活。

接下来,我将按照这三种流程一一向大家分解如何操作。

T-​Mobile手机卡eSIM购买

首先我们要在支持eSIM的手机上面去下载,T-Mobile的预付费卡eSIM这个APP。

WoGVcG.png

下载完成后打开APP,它会检测手机是否支持eSIM,如果不支持将无法继续。

如果支持,我们在APP里输入想要绑定的邮箱。

WQX0rA.png

然后输入要激活的手机号码的区号。比如说,我想要一个洛杉矶的手机号码,那么我输入洛杉矶的区号90001,然后点击下一步,再点击确认。

Az0Xih.png

接下来我们选择套餐。建议直接选择最便宜的10美元的套餐,因为前面的60美元和50美元套餐都是无限的短信、语音和流量,但在中国是无法使用流量的。所以10美元的套餐就够了。不过,如果你想要无限的语音和短信,可以选择15美元的套餐。

RKo2qJ.png

选择完成后点击继续,然后同意一下协议。

接下来,我们需要输入银行卡号进行付款。

5K4sEM.png

我用的是美国银行的信用卡来支付,但是网上有小伙伴反馈,使用国内发行的Visa或者万事达卡也是可以的。然后选择美国作为国家,输一下账单的地址。

接下来非常关键的一步,是要创建一个PIN。

DUOqsE.png

这个PIN非常重要,无论是登录账户还是使用手机卡都需要用到。设置完成后点击下一步,然后确认信息没有问题,最后点击完成支付。系统会处理支付。

接下来,我们要跳转到安装eSIM的步骤。

EjKCSG.png

我们点击继续,然后自定义一个标签,我选择“T-Mobile”,然后点击继续。现在我们可以看到已经生成了一个号码,eSIM已经安装成功了。

Y6Pow8.png
9qdhzQ.png

接下来我们点开手机的设置,点击蜂窝网络,可以看到eSIM的卡,但是信号栏是没有信号的。

U5jufs.png
gwEwJy.png

我们需要去激活WiFi通话的功能。我们直接点击WiFi通话,然后选择启用。

UwAZMe.png

接下来会跳出一个窗口,如果你和我一样遇到错误提示的话,是因为我们的网络不好。这时候需要通过开启代理才可以。

BmZf39.png

我们打开了美国代理,我们再次点击,可以看到已经成功跳出来输入地址的界面。在这里,我们随便输入一个美国地址就可以了。

pduCgB.png

输入完成后点击保存。需要注意的是,保存完成后一定要点击右下角的关闭按钮,不要点击左上角的取消按钮。

aPkWli.png

之前我操作了好几次都没有成功,主要原因就是点击了取消,这样会取消设置WiFi通话。只有点击关闭才是确定设置了WiFi通话。

点击完关闭后,我们回到设置的界面,就可以看到WiFi通话已经成功打开了。此时,我们就可以收到T-Mobile发来的账户激活成功的信息了。

k2ekwv.png
Udjpw6.png

以上就是eSIM的购买、下载和安装的教程。

购买eSIM的三个要点

购买eSIM有三个非常关键的点:

  • 首先,手机要支持eSIM。目前所有的国行手机都不支持,需要有美版、港版等其他支持eSIM的海外版手机才可以。
  • 其次,第二个难点是我们在购买时必须要有一个外币信用卡,这样才能够去付款。
  • 第三个是在激活WiFi通话时,必须要有科学上网的环境,这样才能打开输入地址的界面去激活WiFi通话进行使用。

接下来我们继续向大家演示,如果我有实体手机卡,要如何去激活账户进行操作。

好的,现在我们回到T-​Mobile的官网,然后我们先演示如何去订购一张实体的手机卡。如果你有美国的地址,而且不希望从别人那里购买手机卡,想要一张全新的话,那么可以选择直接在官网进行订购。

我们点击这里的选择。

ml8xZN.png

然后选择“我是新用户”。

Q9h76D.png

在这里,我们选择套餐。首先给我们展示的是无限流量和语音的套餐,我们不需要,直接点击“有限流量套餐”,然后选择最便宜的10美元的套餐就可以了。然后点击选择。

MbVAB2.png

在这个界面,我们可以看到一共需要支付20美元,其中10美元是购买卡的费用,另外10美元是第1个月的月租。

yG1Jzn.png

确认完成后,点击“开始结算”。

接下来,在这里去创建一个T-​Mobile的账号。

D45iLD.png

输入自己的邮箱,然后设置一个账号的PIN。接着,输入自己的名字和收货地址。输入完成后,继续付款。T-​Mobile就会把实体的手机卡寄送到我们的地址。

5bHRAb.png

等我们收到这张实体的手机卡后,就可以去进行激活了。

接下来我们演示实体手机卡的激活和使用。

好的,我们还是回到这个页面,在这里输入自己SIM卡的卡号,也就是卡片背面的二维码上面的号码。

7FdjGR.png

输入完成后点击选择,然后页面会跳转到去付款的页面,和之前操作是一样的。关键是付10美元去激活这个卡片。

当我们支付完10美元后,就可以看到我们的手机号码了。我们把卡插到手机里,依然是没有信号的,需要我们开启Wi-​Fi通话,具体流程和eSIM开通是一样的。

接下来我们去注册T-​Mobile的账户。需要注意的是在这个过程中需要全程开启美国代理,如果没有开启,直接用国内IP的话,可能是打不开这个网站。

我们点击注册。

XT59Nr.png

接着输入自己的美国手机号码,点击下一步。

ODNNyn.png

在这里去输入自己的名字,邮箱,电话号码,并设置一个T-Mobile账户的密码。

pWHQH8.png

接下来要进行邮箱验证码的验证。

DCAnVb.png

完成邮箱验证后,我们要进行手机卡的验证。

SyzCPq.png

在这步操作时,我们需要先把手机卡插在手机上,或者打开eSIM,并保持WiFi通话连接的状态,这样才能收到系统发给我们的六位数字验证码。

输入验证码后点击继续,然后再输入之前设置的PIN。

0NfBPf.png

这里会问是否要设置谷歌验证账户,我们可以选择不设置。

DJCmCQ.png

接下来就顺利登录到后台了。

在后台的界面中,我们可以看到我们当前的手机号码套餐还有可用的余额。

xA2SSj.png

在我的账户里,我们可以修改一些信息。

RPy623.png

设置一个安全问题,充值或者打开自动充值,这样就不会因为忘记充话费而导致账户暂停。

HUhk6b.png

这些设置都比较简单,大家可以自己进去操作一下。

转移号码教程

接下来想重点和大家分享的就是如何去转入自己已有的号码,或者说如何去更换号码。

点击左上角的“支持”。

dNTqWI.png

然后选择“账户”。在账户页面中,我们可以选择“更换手机号码”。

WNyStr.png

这个时候要输入我们旧号码的转移信息。

gJxoTJ.png

如果没有旧号码,那么可以选择继续使用T-​Mobile随机分配给我们的号码。或者也可以购买一个号码。

购买美国手机号码现在主要有两种途径:一种是购买Google Voice,然后转移到这张卡,具体方法大家可以看我之前做的视频教程。转移之后Google Voice的运营商就会变成T-Mobile,不再是虚拟号码;

另外一种方式是直接在之前推荐过的,NumberBarn网站上购买号码。也是可以转移成功的。

购买完号码后,我们会得到四个关键的信息:手机号码、账户号码,Google Voice的手机号码和账户号码是一样的,但其他运营商的话大部分是不一样的、PIN,以及之前旧号码的邮编。

全部输入完成后,我们点击继续。如果输入后无法继续的话,可能是因为我们的IP问题,这时候尝试更换美国IP就可以了。然后我们继续输入这些信息后,就可以顺利地将我们之前的号码转移到这张新卡上面了。

下面向大家说明一下如何去更改套餐。

目前有四个渠道可以要求修改套餐:

1.打客服电话,要求人工客服877–464-8646把套餐改成三美元,如果说不行就挂掉,重新联系下一个客服。

2.关注T-​Mobile Twitter,然后发私信,提出相同的要求,如果说不行就退出,过一会儿重新联系下一个客服。

QEbeKG.png
uF7p7R.png

3.关注T-​Mobile Facebook,然后发私信,提出相同的要求,如果说不行就退出,过一会儿重新联系下一个客服。

d6JHTA.png

4.第四种方式是隐藏模式,我们可以来到T-​Mobile官网,在最下面的用户条款中有说明。

iqhxX1.png

如果我们账户内有余额,但是不足以支付10美元的月租,那么在120天之后,我们的卡就会自动转为月租3美元的套餐了。所以我们只需要在激活卡片后,账户充值3–9美元之间的任意金额,然后躺平120天就可以了。

使用这种模式转套餐,需要注意的是,因为从第二个月开始,我们实际上就已经是欠费状态了,所以这个卡我们是无法正常使用的,只能接受系统短信。在120天之后,变成3美元的套餐了,我们就可以正常使用这张卡了。

我自己是通过T-​Mobile Facebook客服更换成功,提出要求后,客服二话没说就给改了,后台马上可以看到套餐下个月生效。

总结起来的话,推荐使用TwitterFacebook私信的方法。电话客服普遍等待时间比较长,英语不会的小伙伴更是不适用。还不如就直接扔条私信给客服,慢慢等回应就可以了。

更改套餐并不困难,需要的是耐心和毅力,就是不停地与客服进行沟通和尝试。如果一个客服无法帮助你,就换下一个客服继续尝试。我打算在一个月的时间内慢慢联系客服去解决这个问题。然而,经过我的实际测试,第二天联系了七八个客服后,终于碰到了一个可以帮我修改套餐的客服。大家可以参考一下我的经验。

最后总结一下,T-​Mobile的三美元套餐,与UltraPayGo的三美元套餐。

  • 最明显的区别是T-​Mobile三美元套餐,只包含30条短信和30分钟通话,而Ultra PayGo有100分钟通话和100条短信。
  • T-​Mobile允许在后台查看所有操作和账单明细,而Ultra PayGo不提供账单明细,只能看到每月扣费总额。
  • T-​Mobile原生卡的充值金额是可选的,从1美元到300美元都可以,而Ultra PayGo只能充值5、10、20这三种面值,相对而言不太灵活。
  • T-​Mobile的三美元套餐是绝版套餐,由T-​Mobile官方提供服务,相对而言客服服务也较好。而Ultra PayGo提供的客服服务质量一般,但是胜在有中文客服电话,适合英语不好的小伙伴。