【歪门邪道】想办法让朋友连上我家里的Minecraft服务器
最近和朋友联机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了。-全文完-