Server搭建过程-5.Frp穿透介绍和P2P

利用Frp来进行内网穿透的方案已经较为成熟,不过这套方案已经流行了几年。
现在更多的新软件会偏向P2P打洞,虚拟组网之类。

说实话只要是内网穿透,无论是传统的服务器中转,还是P2P打洞,穿透都需要通过不同的网络环境,所以还是非常吃NAT类型的。
因此尽量使用公网IPV4,没有公网V4用公网V6。内网穿透只是没有公网V4/V6的下位替代。
另外还有一个可以注意的点:
如果大学中办了校园网套餐,你的设备通过校园网连接,而学校内又有校园网wifi覆盖,那么之间大概率是局域网。
可以好好利用这点来局域网串流。

Tailscale的这篇文章很值得看:
中文译文:[译] NAT 穿透是如何工作的:技术原理及企业级实践(Tailscale, 2020)
原文地址:How NAT traversal works
这篇文章浅显易懂,而又全面的讲解了穿透的整个过程。

简单的说,穿透分为两部分,第一部分是穿透网络上的防火墙:
这部分很简单,因为防火墙的一般策略都是进站有所封锁,而出站全部放行。
也就是说,只要我们内部由外主动发起这个连接要求,防火墙就会放行对应的应答包进来,因为防火墙认为是我们主动需要和外界连接。而对方也这样操作一次,也就构成了这个双向通信链接,打通了防火墙。

总而言之,打通防火墙所需要的,就是两台设备在防火墙后面同时发起对向的通信请求。
我们可以发现,无论在链路上有几堵墙,都可以用这个简单的逻辑来穿透。
但其实这个机制有效的前提是:要提前互相知道对方的公网ip和对应的port,才能发送包给对方,来构成链路。

而这个前提也就引入了穿透的第二部分,NAT。这部分也是穿透中最难的部分。
在IPV4情况下,我们手上的终端设备不会被分配公网ip。都需要NAT转换后,数据包才能以一个公网ip:端口发出去。
这也就是问题所在,我们需要提前互相知道对方的公网IP和端口,但是经过NAT后,我们不知道怎么获取到对方的公网IP和端口。

轮到Stun发挥作用的时候了。Stun服务器可以探测客户端在NAT之后的公网IP和端口。
实际上Stun不仅可以探测公网ip和端口,还可以用来检测NAT类型等信息。
有很多基于此的在线检测工具,手机的NekoBox软件里也自带一个Stun检测,当然,你也可以自建一个Stun服务器用于检测。
Stun服务器我一般白嫖的网址是:stun.miwifi.com

NAT类型分为四类:

完全锥形NAT(Full Cone NAT)(NAT1):
只要内部资源先发起过联系,任何外部地址和端口都可以访问该内部资源。

受限锥形NAT(Restricted Cone NAT)(NAT2):
外部IP地址只有在之前与内部资源有过交互的情况下,才能连接到该内部资源。

端口受限锥形NAT(Port Restricted Cone NAT)(NAT3):
只有当某个外部IP地址和端口,之前通过该端口与内部资源交互过时,它才有权限访问该内部地址和端口。

对称NAT(Symmetric NAT)(NAT4):
当数据包被发送到某个外部IP地址和端口时,回复时会为该外部地址和端口分配不同的映射。也就是说,当两个不同的外部用户尝试使用相同的端口号连接同一个内部地址时,他们会各自得到不同的映射端口号。
这种NAT比较常见于企业级NAT。

普通家庭宽带大多数都是NAT3,而移动数据是NAT4。
这里夸一句,中兴的路由器可以更改NAT类型,算是少见了。
简单地说前三种NAT,Stun都能穿透,而对称NAT,Stun无法奏效。
在这种NAT下,Stun拿到的公网ip和端口,只允许Stun的入向包进入。其他服务无法进入,自然无法穿透。

因此这种情况下只能启用保底的中转服务器,流量全部通过中转服务器转发。这类似于FRP干的事情。
Tailscale的中继服务器Derp默认以TCP流量转发。

总而言之,NAT4基本无法P2P直连,只能通过中转。中转穿透的情况,恐怕只能做一些简单的操作,串流延迟很大建议放弃。
Tailscale的中转服务器都在国外,最近的也在香港。建议是自建中转服务器。
自建中转服务器的好处不仅仅是中转延迟低,也有助于设备之间的p2p建立。
有能力的话还可以自建控制服务器。

有一点和Zerotier不同的是,Tailscale各个平台下都直接支持更换中转和控制服务器。
而Zerotier的IOS修改moon服务器很麻烦。

我们应该如何提高P2P的成功率呢?我们要做的就是减少NAT的层级(这点对于国内用户来说很迫切)和提高NAT的类型。
少嵌套一层NAT(比如路由器拨号光猫桥接),可以减少转发损耗,提高网络通信效率和连通性。
而NAT类型越高,P2P就越容易成功。

当然最明显的提升,就是从NAT各个类型升级到公网的程度,公网也称为NAT0。
最适合国内的目前就是公网IPV6。下发了公网IPV6也就意味着无NAT,不太需要考虑NAT穿透的问题,P2P成功率高很多。
基本只需要考虑防火墙,而防火墙的情况和之前一样,只需要通过两端同时发包就能解决。

可能有人就会问,都有公网IPV6了,直接直连不就好了吗,还打什么洞?
有几方面的优势:
1.就算有公网IPV6,运营商光猫的防火墙会阻拦直连流量。如果想直连必须关闭光猫防火墙,或者改桥接。而Tailscale较友好。
2.公网IPV6分配的地址是不断变化的,如果需要固定还需要用DDNS。
3.公网IPV6暴露在外的端口安全性不佳,而Tailscale端到端加密流量,只有组网内的设备能互相访问。且Tailscale虚拟组网较无感。

关于搭建Tailscale服务器可以看:
Tailscale 基础教程:部署私有 DERP 中继服务器
Tailscale 基础教程:Headscale 的部署方法和使用教程
Tailscale/Headscale ACL 使用教程

Tailscale玩法之内网穿透、异地组网、全隧道模式、纯IP的双栈DERP搭建、Headscale协调服务器搭建,用一期搞定,看一看不亏吧?


虚拟组网其实就是把众多设备组在一个虚拟的,共同的网络内。
该说不说,和以前用游侠对战平台来组虚拟局域网,联机打游戏这点差不多。
ZeroTier还真的可以用来局域网联机。

现在内网穿透比较常见的软件有:

FrpNpsEasyTierZeroTierTailscaleWireGuard

Tailscale实际上是基于Stun和WireGuard的工具。

不过,使用Frp通过传统的服务器中转流量方案,穿透效果还是不佳。
在单纯偏向速率需求的任务中,比如搭建网盘穿透到公网,下载/上传文件等操作,速率是能够跑满上传限制。能够正常使用,Frp没有问题。
但在对延迟要求高的任务中,比如sunshine+moonlight串流,只要动作大点,就卡的不行。
并不是带宽上限太低导致的卡顿丢帧,在把分辨率和帧率都降低后,frp穿透本身在带宽还有余的情况下,依旧如此。

经过调查,卡的原因就是码率。可能是我之前一下子对码率太自信了,调的太高。
后面被代码框框住的内容是之前的错误推测,仅供参考
为什么我能确定这是错误推测呢,因为我后来使用Tailscalep2p打洞成功后,码率和frp一样,调高的话也会出现同样的情况。
一般分辨率720p-1080p,网络不卡的情况可以开60帧,卡顿的话开30帧比较好。
码率10-20mbps之间。

Moonlight中,除了需要根据情况调整分辨率和帧率外,码率也应设置在实际串流过程中网络最低可用带宽之下。
以避免因“木桶效应”导致的卡顿。
比如串流整个过程中,中转服务器速率为200mbps,主机设备上传带宽30mbps,串流设备下行100mbps:
则以30mbps为准,而且Moonlight码率设置应比30mbps还要低一些。

1
2
3
4
5
6
7
8
我估计是穿透网络太难。在和主机设备(主机设备即被链接,被控制的设备)同一房间的情况下:
使用别的运营商的5G移动数据串流,比较卡顿。但是切换到和主机设备同一运营商的网络下串流就明显好了很多。
网络延迟30ms+,但胜在延迟不波动,够稳定,是可以比较正常的控制设备的。
扩展开来的说,在使用Frp进行内网穿透时,如果网络路径较复杂,涉及多个运营商或跨网段传输(特别是异地),就会出现链路质量下降。
有许多的因素会导致网络环境变差,比如NAT类型,对UDP包的限制,通道出口的拥堵情况,QOS策略等,从而造成明显的卡顿。
总而言之,如果只是应急穿透,或者只是需要网盘这种速率主要的,可以使用Frp。
但如果是串流这种对延迟要求很高的,建议是放弃用Frp吧。
就我个人目前来看,哪怕是和主机设备同一城市,使用移动数据来串流,都无法有正常体验,很卡顿,异地就更不用想了。

Frp已支持P2P打洞,无需中转服务器的XTCP穿透,可以按照官方文档来添加。
但是P2P已经有ZeroTier等软件了。
因此这里只介绍Frp最为基础的的Tcp和Udp端口转发,传统的中转服务器方案。


BTW,网络上很多Frp教程提供的配置文件都是ini的,但后续frp默认的配置文件是toml,这之间的语法有区别。本文以toml为标准。

程序下载下来里有两份,frpc是Client,客户端的,放在自己需要内网穿透的设备上。frps是Server,服务端的,放在中转服务器上。

frps.toml的参考配置

1
2
bindPort = 5050            #服务器和客户端之间的通信端口
auth.token = "key" #认证密码

后续不添加配置,默认转发所有通过密码认证的流量。
如需自定义,请参考官方文档。

frpc.toml的参考配置

1
2
3
4
5
6
7
8
9
10
serverAddr = "x.x.x.x"    #填写服务器的公网IP地址
serverPort = 5050 #服务器和客户端之间的通信端口,值应和bindPort相同
auth.token = "key" #认证密码

[[proxies]]
name = "port1" #代理端口1,命名可自定义
type = "tcp" #转发的包类型
localIP = "127.0.0.1" #需要转发内网服务的设备位于局域网内的IP地址,本机内网服务需转换即填写127.0.0.1
localPort = 5212 #需转发的服务所在的端口
remotePort = 5344 #转发后在外网上开放的对应端口,可和localPort相同

启动frpc/frps时,默认查找的配置文件是本目录下的ini文件,直接打开会说找不到对应的配置文件。
命令行手动指定配置文件位置:

1
frps -c ./frps.toml
1
frpc -c ./frpc.toml

后续可以设置成sh和bat脚本自动化执行。


串流需要打开的端口可以看这个视频:
串流游戏IPv4端口映射最全列表 | 包含 PC PS和XBOX一次性搞定

Frp对应的配置文件参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
serverAddr = "x.x.x.x"
serverPort = 5050
auth.token = "key"

[[proxies]]
name = "port1"
type = "tcp"
localIP = "127.0.0.1"
localPort = 47984
remotePort = 47984

[[proxies]]
name = "port2"
type = "tcp"
localIP = "127.0.0.1"
localPort = 47989
remotePort = 47989

[[proxies]]
name = "port3"
type = "udp"
localIP = "127.0.0.1"
localPort = 47998
remotePort = 47998

[[proxies]]
name = "port4"
type = "udp"
localIP = "127.0.0.1"
localPort = 47999
remotePort = 47999

[[proxies]]
name = "port5"
type = "udp"
localIP = "127.0.0.1"
localPort = 48000
remotePort = 48000

[[proxies]]
name = "port6"
type = "udp"
localIP = "127.0.0.1"
localPort = 48002
remotePort = 48002

[[proxies]]
name = "port7"
type = "udp"
localIP = "127.0.0.1"
localPort = 5353
remotePort = 5353

[[proxies]]
name = "port8"
type = "udp"
localIP = "127.0.0.1"
localPort = 48010
remotePort = 48010

[[proxies]]
name = "port9"
type = "tcp"
localIP = "127.0.0.1"
localPort = 48010
remotePort = 48010

[[proxies]]
name = "port10"
type = "tcp"
localIP = "127.0.0.1"
localPort = 27031
remotePort = 27031

[[proxies]]
name = "port11"
type = "tcp"
localIP = "127.0.0.1"
localPort = 27036
remotePort = 27036

[[proxies]]
name = "port12"
type = "udp"
localIP = "127.0.0.1"
localPort = 27036
remotePort = 27036

[[proxies]]
name = "port13"
type = "udp"
localIP = "127.0.0.1"
localPort = 27037
remotePort = 27037

新版本的Frp使用的toml语法不支持端口范围转发。
用老的ini格式设置端口范围转发,frp可以识别配置文件,但是无法正确转发多个端口,导致moonlight串流连接失败。
因此下面这个配置文件仅供参考:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[common]
server_addr = "x.x.x.x"
server_port = 5050
token = "key"

[tcpwork]
type = "tcp"
localIP = "127.0.0.1"
localPort = 47984,47989,48010
remotePort = 47984,47989,48010

[udpwork]
type = "udp"
localIP = "127.0.0.1"
localPort = 47998,47999,48000,48002,48010,5353
remotePort = 47998,47999,48000,48002,48010,5353

[tcpwork2]
type = "tcp"
localIP = "127.0.0.1"
localPort = 27031,27036
remotePort = 27031,27036

[udpwork2]
type = "udp"
localIP = "127.0.0.1"
localPort = 27036,27037
remotePort = 27036,27037