前情提要
我的需求是从外面方便地访问家里的另一台电脑(能 ssh 上就行),并且我不喜欢多装太多服务在工作电脑上。
家里的电脑是一台 kali,是我们预备连接的 server。外面这台我经常带着跑的是 windows 系统,是 client。为了简单起见,我后面直接叫它们 kali 和 windows。
嘉宾介绍
首先是一个符合国情的需求:两台电脑都要有梯子。我在两边都装了 Clash Verge Rev,并且开了 TUN 模式,按照规则转发。梯子常年开着,这意味着两边的虚拟网卡都被 Clash 接管。
kali 的环境
租房处提供的路由器,普通的 DHCP。经过测试,kali 在这里没有独立的 IPv4 地址,是通过 NAT 出去的,一家人穿一条裤子出门。但是!好消息是它有独立的 IPv6 地址 。这意味着我有机会不经过 NAT 转换,直接点对点连上这台电脑。
windows 的环境
这个就比较复杂了。分类讨论下来场景有三类:
在家的时候,和 kali 在一个局域网下,同一局域网下很容易 ssh 上。
在学校的时候,连接的是学校内网。
为了不在学校而又能模拟学校内网连接的情况,我在家进行测试的时候会让 windows 连手机流量热点(中国联通),并打开 EasyConnect VPN(SangFor)。
排查 - IPv6
问题描述
在学校工位,通过 IPv6 地址,连不上 kali。
在家使用手机流量热点,并打开学校 VPN,同样连不上 kali。
以上描述的都是 ssh 的行为(实际上 ping 也一致)。
Test-Connection 测试
下面开始测试。
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 PS C:\windows\system32> PS C:\windows\system32> Test-NetConnection -ComputerName "2409:8a1e:7a56:e440:dbb:887:564d:7218" -Port 443 警告: TCP connect to (2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 : 443 ) failed 警告: Ping to 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 failed with status: TimedOut ComputerName : 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 RemoteAddress : 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 RemotePort : 443 InterfaceAlias : WLAN SourceAddress : 2408 :840 d:7930 :1055 :8 c2b:4624 :8 acc:f56d PingSucceeded : False PingReplyDetails (RTT) : 0 ms TcpTestSucceeded : False PS C:\windows\system32> PS C:\windows\system32> Test-NetConnection -ComputerName "2409:8a1e:7a56:e440:dbb:887:564d:7218" -Port 443 ComputerName : 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 RemoteAddress : 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 RemotePort : 443 InterfaceAlias : Meta SourceAddress : fdfe:dcba:9876 ::1 TcpTestSucceeded : True PS C:\windows\system32> PS C:\windows\system32> Test-NetConnection -ComputerName "2409:8a1e:7a56:e440:dbb:887:564d:7218" -Port 443 ComputerName : 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 RemoteAddress : 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 RemotePort : 443 InterfaceAlias : Meta SourceAddress : fdfe:dcba:9876 ::1 TcpTestSucceeded : True
总结下来是这样的:
网络环境
联通
联通+Clash
联通+Clash+SangFor
TestConnection
×
√
√
这并没有解决我 ssh 的问题,按道理讲如果是通的,那么打开了 Clash 我就该 ssh 上。而且,直连 IPv6 本来就应该可以连上,为什么现在居然连不上呢??
所以接下来我们一步一步排查。
联通直连 IPv6
首先要尝试回答的问题是,为什么不开 Clash 的时候连不上这个 IPv6 地址?
根据 gemini 的建议,使用 tracert 跟踪只在联通网络下连接 kali 的路径,检查是哪里断开了。在这个时候我才发现,原来 kali 所在的网络(家庭路由器)是移动的,而我的手机流量是联通的。
可能的情况有四种:
第 1-2 跳就断开了,说明是本地路由器的拦截或错误配置(发送端)
第 3-5 跳断开,说明联通内部路由表丢弃了发往移动 IP 段的包,是运营商路由故障(联通内部)
IP 地址前缀从 2408(联通) 变为 2409(移动)的时候断开了,说明跨运营商互联有问题(联通->移动)
追踪到了目标附近,最后一跳断开,说明目标服务器屏蔽了访问(接收端)。
我的试验结果是这样的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 PS C:\windows\system32> tracert -6 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 通过最多 30 个跃点跟踪到 2409 :8 a1e:7 a56:e440:dbb:887 :564 d:7218 的路由 1 8 ms 9 ms 7 ms 2408 :840 d:7500 :3 dfa::bb 2 * * * 请求超时。 3 49 ms 73 ms * fc00:1000 ::61 4 * * * 请求超时。 5 42 ms * * 2408 :8000 :9000 :20 e6::52 6 * 23 ms 32 ms 2408 :8000 :9000 :20 e6::b4 7 * * * 请求超时。 8 * * 89 ms 2409 :8080 :0 :3 :2 e2:281 :: 9 * * * 请求超时。 10 29 ms 28 ms 30 ms 2409 :8080 :0 :2 :206 :275 :0 :1 11 41 ms 23 ms 20 ms 2409 :801 e:f0:1 ::3 c5 12 23 ms 21 ms 31 ms 2409 :8 a1e:7 a05:6 de2:56 b2:9 dff:fe15:b6b8 13 * * * 请求超时。 14 * * * 请求超时。 15 * * * 请求超时。 16 * * * 请求超时。 17 * * * 请求超时。 18 * * * 请求超时。
可见是最后一跳出问题 ,也即服务端拦截 。
那么,究竟是移动的路由器拦截了,还是 kali 自己的防火墙拦截了呢?
再次进行测试:在 kali 上使用 tcpdump 监听 443 端口的包。此时,有两种情况:
如果观察到收到了包但是拒绝了,那就是 kali 的防火墙拦截
如果屏幕一片死寂,那就是路由器拦截
实际情况是二。
联通+Clash 连 IPv6
观察到之前使用 Clash 连接的时候是成功的,那么我想要验证它成功的方式。
还是沿用刚才的检测法,在 Clash 进行 Test-NetConnection 的时候,在 kali 这边用 tcpdump 看有没有包。
好耶,成功了!kali 呢?
——没有包!
此刻才基本确定,并非 Clash 有神力连上了我的 kali,而是它报!喜!不!报!忧!我们可以连接一个完全不存在(没开放)的端口进行测试,结果如下:
孩子呢,你一天到晚不好好学习,就知道哄你个大大哄你个妈妈。
这完全解释了为啥 ssh 连不上,因为事实上就是没连上。Clash 开启了 TUN 模式之后,在 Windows 发出 TCP SYN 包之后,Clash 直接在本地给 Windows 回了一个 SYN-ACK,故 Windows 视角下,误认为连接已经成功了。但实际上,此时真实的流量可能正在代理服务器上重试,或者因为规则不对直接被丢弃了,压根没出路由器。
排查 - wireguard
ok,那现在咋办呢?
我们尝试使用 wireguard,它会尝试创建一个 VPN。原理是这样的:kali和windows身份发生了翻转,kali开始向windows发出请求,由于是kali先发出请求的,联通路由器认为可以放行。接下来,windows 顺着这个连接的回包就将被认为是可信的。
只要这个隧道维持着(通过 PersistentKeepalive 定期发个小包),这扇门就为 windows 开着。
wireguard 使用 UDP 包装流量;如果采用 TCP,可能导致“TCP 重传风暴”,也就是丢包的时候内部 TCP 疯狂重传,外部 TCP 也疯狂重传,两层协议的拥塞控制算法叠加起来,网络带宽不堪重负。
再次测试连通性。windows 端也安装了一个 wireguard,首先确保其连接在内网基础上可通,证明 wg 并没有配错。然后撤掉内网,改成联通网络上测试。
windows 一直在尝试发握手请求。中间,wireguard 甚至显示连接已经建立了。但事实上,kali 依旧不为所动,一个包也没有。
这一通操作下来,发现 VPN 也并不能绕过路由器防火墙。wireguard 连 kali 的想法算是泡汤了,不在 windows 上多装一个软件的想法也泡汤了(不然可以直接用 clash verge rev 对 wireguard 的协议连)。
至此打道回府,老老实实装一个 tailscale 了事了。
tailscale 配置
tailscale 实际上也是基于 wireguard 实现的,但是它的打洞能力非常强,可以在 kali 发了出站包之后马上让 windows 的撞进来,从而建立连接。就算 P2P 打洞不成功,它能强制流量从最近的中转服务器那里绕一圈,就算是以延迟高一点为代价,也终归能连上。
注意在配置之前关掉 wireguard,关掉 TUN(否则网卡会打架),然后双方验证一下,就能连上了。我实际测了一下,叠一个学校 VPN 也没问题。
补配一下 Clash verge rev 规则。windows 这边我用的是:
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 function main (config ) { const tailscaleNet = "100.64.0.0/10" ; if (!config.dns ) config.dns = { enable : true }; config.dns ["fake-ip-filter" ] = [ ...(config.dns ["fake-ip-filter" ] || []), tailscaleNet, "*.tailscale.net" , "kali" , ]; if (config.tun ) { config.tun ["skip-proxy" ] = [ ...(config.tun ["skip-proxy" ] || []), tailscaleNet, ]; } const tailscaleRule = `IP-CIDR,${tailscaleNet} ,DIRECT,no-resolve` ; if (!config.rules ) config.rules = []; config.rules .unshift (tailscaleRule); return config; }
kali 这边是:
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 function main (config ) { const tailscaleNet = "100.64.0.0/10" ; const tailscaleDNS = "*.tailscale.net" ; if (!config.dns ) config.dns = {}; config.dns ["fake-ip-filter" ] = [ ...(config.dns ["fake-ip-filter" ] || []), tailscaleNet, tailscaleDNS, "kali" , "windows" , ]; if (!config.tun ) config.tun = {}; config.tun ["skip-proxy" ] = [ ...(config.tun ["skip-proxy" ] || []), tailscaleNet, ]; const tailscaleRule = `IP-CIDR,${tailscaleNet} ,DIRECT,no-resolve` ; if (!config.rules ) config.rules = []; config.rules .unshift (tailscaleRule); return config; }
后话
我的排查就这么虎头蛇尾地结束了,实际踩的坑要多得多得多,比如冤枉学校 VPN 没有 IPv6 或者封禁端口或者封禁 UDP(可能确实封禁了吧,但是还没到这一关就死了啊),然后费劲换端口啊啥的。
事实证明不能盲信 LLM,还得自己多动脑啊。
附录:wireguard 配置
事实上,与全文的逻辑顺序不同,真实的时间线是我最开始先配了 wireguard,再开始测试 IPv6 连接。
在 kali 侧,只需设置流量转发时从 10.66.66.1 出去的流量直接走 DIRECT 即可(相当于它们不经过 clash 转发,而是直接发给 wg 服务)。
作为和 kali 的 wireguard 的对应,在 windows 上面进行配置的时候除了多装一个 wg 之外,另一个可选项是直接配置 clash verge rev 的规则,因为它内置了对于 wireguard 协议的支持。那么 windows 作为 client,虚拟节点可以配成 10.66.66.2。
这边也贴一下 windows 侧 wireguard 配置:
clash verge rev 有个很坑的一点,就是在左边的 Merge 里面写 rule 的话,只能完全覆盖,不能追加。问大模型所得到的答案是完全牛头不对马嘴的,这个信息是在 clash verge rev 的更新日志中提供的。
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 function main (config ) { const wg_node = { name : "kali-direct" , type : "wireguard" , server : "2409:8a1e:7a56:e440:dbb:887:564d:7218" , port : 51820 , ip : "10.66.66.2" , "private-key" : "<redacted>" , "public-key" : "z9DytF559yk2eEuYKXogolr/I7qD2wSwKAbEF0T4iEA=" , udp : true , mtu : 1280 , "allowed-ips" : ["0.0.0.0/0" ] }; if (!config.proxies ) config.proxies = []; config.proxies = config.proxies .filter (p => p.name !== "kali-direct" ); config.proxies .unshift (wg_node); if (!config.rules ) config.rules = []; config.rules .unshift ("IP-CIDR,10.66.66.0/24,kali-direct,no-resolve" ); if (config["proxy-groups" ]) { config["proxy-groups" ].forEach (group => { if (group.proxies && !group.proxies .includes ("kali-direct" )) { group.proxies .unshift ("kali-direct" ); } }); } return config; }