2

借助 vxlink 实现跨协议栈访问

 6 months ago
source link: https://hsiaofongw.notion.site/vxlink-cd7edd8f2f234d1583b30655ef3897fe
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
met_emanuel_leutze.jpg
Drag image to reposition

借助 vxlink 实现跨协议栈访问

Created
October 27, 2023 3:56 AM
computer-network
nat46
Description
借助于传输层端口转发实现的 4-to-6, 1-to-1 NAT
Direct
Updated
October 29, 2023 6:55 PM
3 more properties
家庭主机 (NAS) 有公网 IPv6 地址,相对固定。用户希望能够在一个 IPv4-only 的网络环境下远程访问 NAS 提供的服务:例如 ssh, VNC, Samba, RDP 等服务。
由于 IPv4 和 IPv6 本身是不兼容的:一个 IPv6-only 的 host 和一个 IPv4-only 的 host 无法仅通过 IP 地址和对方通信,因为 IPv6 的帧头部和 IPv4 的帧头部格式不一样,因此,我们需要利用一台同时是 IPv4 使能和 IPv6 使能的双栈 (DualStack) 机器来为这两台机器牵线搭桥,具体来说是扮演一个做网络地址转换 (NAT) 的中间人角色。
在本文中,我们探讨这种 NAT 的具体实现方式,以 vxlink 为例。

过程与原理

首先创建一个 vxlink 加速服务,注意要选择支持 IPv6 的节点(列表项中有显眼 IPv6 标识),一个 vxlink 加速服务本质上是一个 1-to-1 NAT, 具体运作方式如下:
你在创建此服务时指定一个目标 IP 地址 (dst ip) 和目标端口 (dst port);
vxlink 成功创建服务后,给你返回一个访问主机地址 (entry host) 和访问端口 (entry port);
在没有白名单的情况下,你发给 entry host: entry port 的 packet 会被 vxlink 转发到 dst ip: dst port, 并且在出站时 vxlink 将 packet SNAT 成 vxlink 它自己的地址;
dst ip 的主机会收到 sender 是 vxlink, target 是它自己的 packet;
dst ip 的主机把 reply packet 发给 vxlink;
vxlink 根据 NAT 记录,把地址换回来,把 dst ip 主机发给它的 packet 发给你(最初的 sender);
vxlink 的加速服务强大的地方在于它是支持跨协议的 1-to-1 NAT 的,entry host 一般而言是 dual stack 使能的,也就是说 entry host 的 DNS 服务器支持解析它的 A 类型的资源记录,也支持解析它的 AAAA 类型的资源记录,同时 vxlink 的主机也是 dual stack 使能的。
https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F8551cdf7-056d-46dc-82ad-7ab1ba2283ed%2F98c01f8f-ee32-4729-b419-35d3209d0822%2F%25E6%2588%25AA%25E5%25B1%258F2023-10-27_%25E4%25B8%258B%25E5%258D%258812.21.31.png?table=block&id=219ff5b5-df09-4d7c-9e79-1a727a638449&spaceId=8551cdf7-056d-46dc-82ad-7ab1ba2283ed&width=1600&userId=&cache=v2
一般而言我们可以将 dst port 选为 NAS 的 ssh 端口,方便后续用 ssh 进行进一步的操作。
服务创建成功后,我们应当可以用 vxlink 的地址 ssh 连接 NAS 了:
Shell
ssh -4 -i <keyfile-path> <user>@<vxlink-host> -p <vxlink-port>
我们发现,即便用了 -4 选项模拟 IPv4-only 环境,我们也能通过这行命令连上家里的 IPv6 主机,这也意味着当前主机到最终目的主机间的 4-to-6 NAT 实现成功了。
接下来我们可以将该条 ssh 连接命令中的参数保存在 ssh 用户配置文件,后续就可以以 ssh 快捷方式的方式使用它:
Shell

# $HOME/.ssh/config 文件

Host <shortcut-name> HostName <vxlink-host> User <user> Port <vxlink-port> IdentityFile <keyfile-path>

后续可以只通过 ssh <shortcut-name> 来访问此 ssh 服务,经过了 vxlink 的 dual stack NAT 加持,应该无论你通过 IPv4 还是 IPv6 因特网都可以用。
你还可以通过 ssh 的固定端口转发参数 -L 来访问内网服务,无论它是 IPv6 还是 IPv4 的,只要这台提供 ssh 服务的机器能连接到就行:
Shell
ssh -vNTL 127.0.0.1:15900:192.168.1.101:5900 <shortcut-name> # 转发 VNC 服务到本地 15900 端口(假如本地主机不开启 VNC 服务,也可以让端口一样) ssh -vNTL '127.0.0.1:10022:[xxxx:xxxx:xxxx:xxxx::xxxx]:22' <shortcut-name> # 转发 ssh 服务到本地 10022 端口 ssh -vNTL '127.0.0.1:10445:127.0.0.1:445' <shortcut-name> # 转发 NAS 服务到本地, 假设 NAS 服务刚好就位于 ssh 连接到的那台机器(所以可以用 loopback 地址) ssh -vNTL '127.0.0.1:13389:[xxxx:xxxx:xxxx:xxxx::xxxx]:3389' <shortcut-name> # 转发 RDP 服务到本地,位于 13389 端口
如果应用程式支持 SOCKS 代理协议,可以用 ssh 命令行的 -D 参数来做动态的端口转发,真实的目的地址会被封装在 socks 封包中,属于 SOCKS 协议的范畴:
Shell
ssh -vNTD 17890 <shortcut-name> # 在本地的 17890 端口建立 socks5 服务,socks5 服务收到的流量会被转交给 ssh 连接到的主机进行转发。

ssh 转发

附带着总结一下 ssh 三个可以灵活运用的转发参数 ‘-L’, ‘-D’ 和 ‘-R’.

本地到远端的固定转发

‘-L’ 对应 ssh_config(5) 里边的 LocalForward 参数,它绑定、侦听本机的一个 socket, 将该 socket 受到的 TCP 连接转接到一个目标 socket, 例如,假设服务器有一个 web server 侦听 127.0.0.1:8080, 则我们可以用命令
Shell
ssh -NTL '127.0.0.1:18080:127.0.0.1:8080' <server>
建立 TCP 连接转发,这样就可以在本地 curl localhost:18080 来访问这个 web 服务。
又比如说 server 所在的私有网络有一个 web 服务面向 10.1.2.3:80 提供服务,这个地址 server 可以访问到,但是本机不行(不能之间访问),这时可以用 ssh 来实现 TCP 连接的转发:
Shell
ssh -NTL '127.0.0.1:8080:10.1.2.3:80' <server>
这样就可以通过访问 127.0.0.1:8080 来访问 10.1.2.3:80 了。

本地到远端的动态转发

固定转发 ‘-L’ 参数用于目的端口已知且固定的情形。
有时候目的端口是变化的,应用程序向哪个地址建立 TCP 连接属于运行时才能确定的知识,例如一个应用程序可能向 1.2.3.4:10080 建立 TCP 连接,也可能向 192.168.1.1:80 建立连接,具体是哪一个取决于用户输入。
SOCKS 协议专门用来解决这种动态转发的问题,而我们可以用 ssh 命令行工具轻松地在本地建立一个 socks 服务:
Shell
ssh -NTD '127.0.0.1:7890' <server>
这样建立的 socks 服务接入点在本地,socks 服务承接的 payload 会被交给 server 转发。
就刚才那个例子而言,可以用:
Shell
curl --proxy socks5://127.0.0.1:7890 10.1.2.3
来访问位于专网的 web 服务。

远端到本地的固定转发

同样对应于目标端口已知的情形,但是这次和 ‘-L’ 的情形不同,接入点是在远端而不是本地,这时可以用 ‘-R’ 参数,例如我希望在 server 上侦听 80 端口,把它收到的 TCP 连接转发到本机的 8080 端口:
Shell
ssh -NTR '*:80:127.0.0.1:8080' <server>
其中 ‘*:80’ 表明要求 server 端的 ssh 侦听任意 interface 的 80 端口,要让这个选项生效,需要启用 server 端 /etc/sshd_config 配置文件的 GatewayPorts 选项(设为 yes)。
这个参数适用于做内网穿透,作用有点类似 zerotier 或者 frp, 只不过这两者是专业的。
这样建立了反向的固定转发之后,我们可以通过访问 http://<公网 ip> 的地址来访问本地的 web 服务。

远端到本地的动态转发

对于使用了 ‘-R’,而目的地址又不知道(不确定)的情形,ssh 会为你在 server 端建立一个 SOCKS 服务,SOCKS 服务收到的 payload 会放到本地来转发:
Shell
ssh -NTR '127.0.0.1:17890' <server>
例如本地有一台树莓派 192.168.1.102 在 8080 端口提供 web 服务,我们用以上这行命令在 server 上建立 SOCKS 服务后,可以在 server 上使用:
Shell
curl --proxy socks://127.0.0.1:17890 192.168.1.102:8080
来访问本机所在内网 192.168.1.102 的树莓派提供的 web 服务。
要让 127.0.0.1 地址生效,需要确保 GatewayPorts 参数值是 ‘no’. 这也是建议的行为(出于安全考虑)。
假如我们现在不在家中,这时可以结合 ‘-L’ 参数,使得我们可以通过 server 的中转(反向转发),访问树莓派的服务(前提是刚才那行 ssh 命令是由内网的一台机器发出):
Shell
ssh -NTL '127.0.0.1:17890:127.0.0.1:17890' <server>
这时如果再用刚才的 curl 来访问,则流量相当于是从本地到达 server, 再从 server 到达树莓派所在内网。

连接的可靠性

该小节探讨如何建立可靠的内网穿透。

KeepAlive 检测以及 WatchDog 机制

在建立反向代理时,建议让 ssh 命令作为服务运行,在 macOS 上,可以通过 LaunchAgents 机制实现,在常见的 Linux distro 上,可以通过 systemd 实现。具体怎么做需要读者去查阅 LaunchAgents 或 systemd 的资料。
原因是 ssh 的 TCPKeepAlive 参数(见 ssh_config(5))是默认启用的,当本地的 ssh 没有收到 TCP keepalive 回包 (reply packet) 时,会自动中断回话,并且在 stderr 上打出诸如 ‘Broken Pipe’ 之类的提示,LaunchAgents 或者类似的 daemon 管理工具会侦测到 ssh 进程的退出,进而会去尝试重启之。
另外建议也启用 ServerAliveInterval 参数,它使 ssh 在 TCPKeepAlive 的基础上,再额外通过建立的 ssh 隧道向 server 端发送 keep alive 测试包,这属于应用层的 liveness 检测,你可以自定义每次 ServerAlive 检测的间隔以及多少次失败关闭会话。

动态端口以及多个连接

你还可以通过连续启动多个
Shell
ssh -vNTR '127.0.0.1:0' <server>
服务来增加 ‘-R’ 反向动态转发服务的可靠性。这里将 server 端的侦听端口置为 0 是为了让 server 端的 sshd 去动态分配它。
在 server 端,我们可以通过执行
Shell
lsof -i -P -n -s TCP:LISTEN | grep sshd
来查看 sshd 都分别侦听了哪些端口,其中就包括 ‘-R’ 参数相关的。

动态 DNS 以及专业的内网穿透工具

例如 frp, ZeroTier 这些属于专门的内网穿透工具。
若内网主机支持 IPv6 并且被分配了公网 IPv6(哪怕是动态的),也可以使用 DDNS 服务来将这些 IPv6 地址和固定的域名绑定起来,后续可以通过域名来访问内部服务。
若路由器(或者光猫终端)有被分配到了公网 IP 地址,可以在上面启用端口转发服务,例如把光猫终端 12345 端口收到的外网流量转到内网特定机器的特定端口。
然而,严格意义上来说当一个主机有了公网 IPv6 地址,并且互联网上的主机能 ping 通这个地址,那么这台主机就不算是内网设备了,因为从网络逻辑拓扑上看,它和互联网上的其它主机是点对点连接的(网状拓扑)。

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK