最近遇到一个问题:
服务器A
- 性能更好
- 只有 IPV6公网
- 跑了一个服务,通过1234/udp端口对外提供服务
- 网络较为稳定
- 假设域名为 domainv6.com
服务器B
- 性能差
- 有IPV4公网,有解析IPV6域名的能力
- 因为性能瓶颈,跑不动服务器A的服务。
- 网络较为稳定
- 假设域名为 domainv4.com
且服务器A与服务器B位于不同地理位置。
虽然现在IPV6已经比较普遍了,但是大部分的家庭宽带,企业内部宽带都默认关闭了解析IPV6的能力,直接将服务器A的纯IPV6域名domainv6.com提供给客户,将导致大部分客户因为IPV6解析能力无法连接服务。
不论是给服务器A购买IPV4公网,还是给服务器B增加CPU内存,成本都较高。
考虑解决
内网穿透
最初想的是在服务器A部署一个穿透工具,然后客户端就相当于跟服务器A在局域网了,也就不存在IPV6连不上的问题。
这种类似的东西有很多,例如Zerotie,frp
但是!让客户安装个额外的客户端,还每次用都得去连接VPN,穿透的效果还时好时坏,不稳定,这是不能忍的。
所以很快,这种方案Pass了。
跳板服务器转发
于是我在想,是否有一种技术,可以实现客户端访问服务器B的IPV4,服务器B收到访问后自主去访问服务器A的对应服务,并发送结果给客户端:
客户端 <-> 服务器B <-> 服务器A
这样一来客户端只需要记住服务器B的IPV4域名domainv4.com,基本是个宽带都能连上,而且服务器B充当了桥梁,不会暴露真实提供服务的服务器A,安全性也更好。
可以预见唯二的缺点是:
- 多了一个故障节点,故障率会更大
- 强依赖服务器B与服务器A的网络状况
挑选方案
现成的有好几种成品可以实现“协议桥接”:
socat
在桥梁服务器B上执行:
socat -T60 UDP4-LISTEN:1234,fork,reuseaddr UDP6:[domainv6.com]:1234
优点是配置简单,支持IPV6。
缺点是高并发性能一般。
haproxy
global
maxconn 10000
defaults
mode udp
timeout connect 5s
timeout client 60s
timeout server 60s
frontend udp_in
bind *:1234
default_backend udp_out
backend udp_out
server srv1 domainv6.com:1234
优点是性能好,可以负载均衡。
缺点是配置复杂。
gost
gost 支持 UDP 转发,而且对 IPv6 友好。
gost -L udp://:1234/[domainv6.com]:1234
优点:支持UDP / TCP / QUIC; 性能优于socat;部署较为简单
缺点:几乎没有
确定方案
gost方案最优,但是直接在服务器B跑系统级服务有点不好维护,所以最终确定是跑一个Gost的Docker容器负责服务转发,资源可控,且方便开关。
于是在服务器B配置Docker compose如下:
version: '3.8'
services:
udp-1234-bridge:
image: ginuerzh/gost:latest
container_name: udp-1234-bridge
network_mode: host
restart: unless-stopped
command: >
-L=udp://:1234/[domainv6.com]:1234
-L=tcp://:2345/[domainv6.com]:2345 #如果有更多转发需求,新增-L参数行
# 输出日志,产品阶段关闭
#-D
Docker跑起来之前,应当调整服务器A和服务器B的防火墙,保证端口udp/1234是对外开放的。
总结:
gost很好,套了Docker容器的gost更可控,并且gost服务本身只是网络转发,对服务器B性能几乎不会有任何影响,使用gost节省了硬件和网络成本,如果是简单的服务、不需要身份验证、即时性要求不是毫秒级,这种方案可以说很完美。
值得注意的是,我们目前所实现的协议桥接还是具有一些风险,这些风险无论用上面哪个工具都会有,与gost无关:
- 对于实时性要求高的服务不适用,节点多会有额外的延迟和抖动
- UDP协议没有握手,没有认证,知道服务器B的地址就能连上,安全性较低
- 服务器A看到的所有连接都是来自服务器B,难溯源,难根据IP设置黑白名单
- 抗攻击能力弱,如果有人flood攻击,服务器A的服务会爆掉,两台服务器的带宽打满可能会连服务器B也爆掉
- 多节点故障率比单节点高
- 通信速度依赖两台服务器之间的带宽和连通情况。
注:本文的所有域名、端口和IP都不是真实的。
#gost #协议桥接