最近和朋友联机Minecraft,用的Tailscale,但有时对方需要使用蜂窝网络,这个时候Tailscale的穿墙打洞就不太行了。怎么办呢?开个国外的服务器便宜配置高,但是延迟大;开个国内的服务器网络好但是配置贵。我灵机一动:开个服务器做流量转发吧!
因为服务器只有两个人联机,所以不需要24小时在线运行,我这边的策略是在我的NUC上运行Minecraft,然后我来开局域网共享。在Tailscale能穿墙打洞的情况下,我们可以通过家宽的IPv6直连,所以延迟很低。但有时对方需要在单位值班,这个时候就要自己的手机开热点给电脑用,而这种情况下Tailscale无法穿墙打洞,这是因为运营商对于蜂窝网络使用的是一种NAT,这种情况下Tailscale会走国外的中继,这样一来不光延迟大增,有时候还会丢包。为了解决这个问题,我决定租用一台云服务器。
上云?家境贫寒,告辞!
一开始原本打算把服务器搬到云上,但最主要的问题无外乎成本和延迟,后者也可以归进成本。由于我使用国外服务器比较多,因此对国外的价格比较熟悉。像是我常用的云服务供应商Hetzner,他们家一台8核心16GB内存的机器大约是这个价格:
- 共享Intel机型CX42:每月20欧元(不到160元人民币)
- 共享AMD机型CPX41:每月30欧元(不到240元人民币)
- 共享Ampere机型CAX31:每月15欧元(不到120元人民币)
- 独享机型CCX33:8核32GB内存每月60欧元(不到480元人民币)
这些价格还包含了至少为160GB的NVMe硬盘、万兆共享网络接口、20TB上传以及下载不计费。
对应地,阿里云的服务器(北京区,以下价格为1个月包月价格,按量付费会更贵):
- Intel系列最便宜的ecs.e-c1m2.xlarge:每月216元
- AMD系列最便宜的ecs.c8a.xlarge:每月371.71元
- Arm系列最便宜的ecs.c8y.xlarge:每月256.26元
阿里云的价格只是配置费,不包含硬盘和按流量计费的百兆共享网络。
再来看看腾讯云(同样北京区,1个月包月价格):
- Intel系列最便宜的S5.LARGE8:每月288.14元
- AMD系列最便宜的SA3.LARGE8:每月241.6元
- Arm系列最便宜的SR1.LARGE8:每月262.8元
同样,这个价格只有配置费,不包含硬盘和按流量计费的宽带。
我一直以来使用Hetzner的服务器就是因为便宜量大还管饱,但唯一不好的一点就是网络延迟很高。Minecraft作为一个需要操作的游戏,它对于延迟还是很敏感的。一般人的情况下(高ping战士不在此列),ping值在100毫秒以下就算是可以接受的,超过120毫秒就有些难受了,最好还是在70毫秒以下。而Hetzner的服务器呢?刚刚ping了一下,肉眼平均在350毫秒左右。我平时部署的服务都是基于HTTP的,像是什么wikijs这种个人知识库,他们对延迟不敏感,我也没什么体感。但这个延迟肯定是玩不了Minecraft。但是国内服务器这么贵,我真的用不起。
这里其实还可以考虑按时租用,比如说晚上要和朋友玩了,就把服务器打开,不用就关掉。阿里云和腾讯云都能够在关机时做到不收取实例配置费,你只需要支付硬盘的价格就行了。但是这也有一个弊端:你要是一不小心忘了,那就自求多福吧。
当然,你也可以考虑使用各家的产品来组合一套解决方案,例如通过一个HTTP请求或者一封邮件在触发服务器启动,然后在服务器内通过脚本检测游戏人数,如果半小时内玩家数量一直为0则触发自动关机。但这种解决方案通常不具有通用性,所以这里就不多介绍了,因为我确实没有这个需求。等之后有这个需求的,可以考虑研究研究。
其实我有公网IP,只不过是v6
由于v4地址日渐枯竭,我搬到昆明之后,发现这边的电信用的是CGNAT,它是一种无感知的技术,旨在提供一个Full-cone NAT让多个用户共享一个IPv4地址。这也就是为什么基于UDP协议的Tailscale能够穿墙打洞成功,因为它是对称的NAT。当然了,我也有IPv6,实际上Tailscale穿墙打洞之后用的也是IPv6地址直连。但关于IPv6地址有很多系列,比如我现在一个ip addr
下去:
3: wlo1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
link/ether 【我的网卡MAC】 brd ff:ff:ff:ff:ff:ff
altname wlp0s20f3
inet 192.168.X.X/8 brd 192.168.X.255 scope global noprefixroute wlo1
valid_lft forever preferred_lft forever
inet6 【v6前缀】:2040:d278:cadf:b885/64 scope global temporary dynamic
valid_lft 85567sec preferred_lft 13567sec
inet6 【v6前缀】::1002/128 scope global dynamic noprefixroute
valid_lft 84998sec preferred_lft 84998sec
inet6 【v6前缀】:987:474a:409a:32d1/64 scope global temporary deprecated dynamic
valid_lft 85567sec preferred_lft 0sec
inet6 【v6前缀】:b432:6cc4:9984:3432/64 scope global dynamic mngtmpaddr noprefixroute
valid_lft 85567sec preferred_lft 13567sec
inet6 fe80::【v6本地链路】/64 scope link noprefixroute
valid_lft forever preferred_lft forever
除了fe80开头的本地链路地址之外,你可以看到我有4个不同的v6地址,他们都以运营商分配的前缀开头,他们的标签分别是:
global temporary dynamic
global dynamic noprefixroute
global temporary deprecated dynamic
global dynamic mngtmpaddr noprefixroute
首先他们都是global,说明都是全局地址,也就是公网IPv6。其次他们也都是dynamic,说明都是动态分配的,因为我买不起固定ip的商宽。再后面有的有temporary,有的没有。带有temporary的地址主要以出站为主,并在一段时间后会时效,这里是为了缓解访问的网站记录IP地址的问题。理论上你可以用这类地址监听入站,但取决于防火墙、路由表等诸多原因,你可能连不通,同时系统还会轮换这些ip,实在不适合入站使用(废话,毕竟人家说了这个就是出站用的)。第三个ip还多了一个deprecated
,这个表示ip地址不会被新的连接所使用了,但由于目前还有持续的连接,这个ip还不能释放。
还剩下两个,比较复杂。一个是mngtmpaddr
,它对应了IPv6的隐私扩展(RFC3041),是操作系统随机生成出来的ip地址,顾名思义——用来保护隐私的。另一个noprefixroute
,它意味着系统没有为这个ip创建前缀路由。所谓前缀路由,就是通过这个IP地址来访问一段IP,比如192.168.1.0/8 via 10.0.0.1
,这个前缀路由意味着所有发往192.168.1.0/8这个网段的数据包都要发给10.0.0.1。在家用情况下,我们不需要配置前缀路由,因为我们出站的数据包的第一站总是家里的路由器。不信你就看ip -6 route show
:
【v6前缀】::1002 dev wlo1 proto kernel metric 600 pref medium
【v6前缀】::/64 dev wlo1 proto ra metric 600 pref medium
(docker和tailscale)
fe80::/64 dev wlo1 proto kernel metric 1024 pref medium
default via fe80::【路由器的v6本地链路】 dev wlo1 proto ra metric 600 pref medium
这个defaul via ...
就是默认路由。
按理说IPv6普及了那么多年,家宽和蜂窝网络都应该支持了。在关闭了TPLink的IPv6防火墙之后,我用手机流量成功地使用那个/128的地址访问到了电脑。手机是北京移动(在昆明),家宽是昆明电信。我的朋友在江苏,他也能在手机上用流量访问到我的电脑。
Minecraft的协议用的是tcp,所以只要一方能连上,这个链路就是通的。如果其他游戏使用UDP,那么你还得确认运营商流量出口的NAT是不是full cone。之前电脑上测试tailscale穿墙打洞失败,要么是手机热点的问题,要么是运营商NAT的问题,总之就是无法实现UDP直连。
这个方法是今天突然想到的,之后等他再去单位值班的时候试试。在发现这个方法之前,我们使用的是腾讯云的服务器做流量转发。
到底还是需要一个v4公网IP。。。?
之前由于Tailscale穿墙打洞失败,我便认为流量没有v6地址,因为蜂窝网络是运营商自己的一个局域网。于是便很快地将思路转到了公网v4地址。在脑子里过了一遍诸如nginx、frp这些流量转发软件之后,我认为当下最快的解决办法就是用ssh做流量转发,因为我的NUC是Linux系统,所以自带SSH。
因为当时我们都开了游戏,然后发现tailscale不能用。为了尽可能快地玩儿上游戏,虽然我之前在跨国ssh的时候遇到过断流的现象,但我猜测国内ssh流量应该不会有断流,除非有省墙,但目前云南省没有这些妖蛾子,所以应该没有问题。
于是果断地去腾讯云开了一个最便宜的配置,区域选的成都,因为在我们俩之间。这个服务器的唯一目的就是转发流量,所以硬盘拉到最小,网络选择按量付费,把带宽拉满——跑图的时候轻量服务器给的不到10Mbps的带宽根本不够用——流量用不了多少,主要是速度,你也不想朋友玩的时候加载个区块要等半分钟吧。总之就是配置最低,网络最高。这里记得给公网ip,然后打开安全组。奔放一点的就全开,不想奔放就只开mc和ssh的端口号,分别是25565和22。
SSH本身有三种转发方式:
- 动态转发(
ssh -D localPort ...
):通过socks5代理的方式,将发往本地端口的请求转发到服务器上,服务器上的出站端口随机选择(动态),可用于访问局域网资源。例如我通过动态转发在本机开设一个socks5代理,通过tailscale连上家里的服务器,那么我便可以通过这个socks5代理访问家里无法使用tailscale连接的资源,例如家里的路由器等。 - 本地转发(
ssh -L localPort:targetHost:targetPort ...
):将发到本地端口的流量转发到远端指定服务器上。例如我的本地端口是8080,而远端可以设定为192.168.1.1:80(路由器的管理界面),这样当我连上家里的服务器时,只要我访问localhost:8080就可以直接打开路由器的管理界面。注意,远端的地址是对应服务器来说的,毕竟出入站还是在服务器上。 - 远程转发(
ssh -R remotePort:targetHost:targetPort
):将服务器指定端口的入站转发到本地端口上。例如服务器绑定25565端口,本地绑定127.0.0.1:43251(MC分享局域网世界的端口),这样当别人访问服务器的25565端口时,数据将被自动转发到我本地的MC端口上,朋友们就可以通过这个服务器来连接到我家里的MC服务器了。
很明显,远程转发是我们需要的。在开始远程转发之前,请检查/etc/ssh/sshd_config
里面的GatewayPorts
是否为yes
,如果是no
(默认值),那么remotePort只能在localhost上面监听,而我们想要的是在0.0.0.0
和::
上监听。修改配置文件后记得重启或reload服务。
假设Minecraft打开局域网共享之后给出的端口号是12345,而我们想直接转发到服务器上的25565端口上(也就是MC的默认端口号),则需要执行如下命令:
ssh -R 25565:localhost:12345 user@server -N
一般来说user都是root,server就是你的服务器IP。如果你开MC的电脑不是Linux,但你有另一台Linux并希望在上面转发,可以执行这个命令:
ssh -R 25565:192.168.1.2:12345 user@server -N
这里把localhost换成了192.168.1.2,这个要换成你实际运行mc的电脑的ip,并确保linux系统能够访问到这个端口(指检查防火墙之类的东西)。
这里额外加了个-N
,因为我们的目的是端口转发,因此这个flag可以告诉ssh不要打开一个shell,不加也无所谓。
在成功转发之后,可以到服务器上使用lsof -i:25565
看一下有没有正确监听端口,如果监听的端口是在localhost或者127.0.0.1上,那么请检查配置文件,并使用ssh -R 0.0.0.0:25565:192.168.1.2:12345 user@server -N
来明确告诉ssh你要监听在所有端口上。
至此,朋友使用服务器的ip便可以成功连接到我NUC上分享的Minecraft了。
-全文完-
【歪门邪道】想办法让朋友连上我家里的Minecraft服务器 由 天空 Blond 采用 知识共享 署名 - 非商业性使用 - 相同方式共享 4.0 国际 许可协议进行许可。
本许可协议授权之外的使用权限可以从 https://skyblond.info/about.html 处获得。
又学到了知识。
还好我是电信公网IP用户:)
本地PC + DDNS + 一个域名即可让好友愉快体验联机快乐
我这边只有公网v6,我的下一篇文章就是介绍利用cloudflare做ddns的。不过cf只能接入二级域名,所以不得不又买了个域名给cf用(
电信还是蛮爽的( ๑´•ω•) "(ㆆᴗㆆ)
毕竟世界加钱可及(
太厉害了٩(ˊᗜˋ*)و