背景
最近在整理手机上的代理配置,碰到一个比较具体的问题:Android 上想同时使用代理和 Tailscale。
我平时手机上会开 Clash 类客户端,主要用来走代理规则。同时又希望手机能通过 Tailscale 访问家里的 Mac mini,比如用 CC Pocket 连 Mac 上的 bridge。桌面端还好,Tailscale 和 Clash Verge 可以同时跑,通过路由排除就能共存。但 Android 上麻烦一点,因为官方 Tailscale App 和代理 App 都要占用系统 VPN。
后来发现 FlClash 使用的 mihomo 内核已经支持 type: tailscale 出站,于是就尝试把 Tailscale 放进 FlClash 配置里,让它作为一个特殊节点使用。
这篇记录一下配置和排查过程。主要是为了以后自己再换手机时别踩同样的坑。
大概思路
最终思路是:
- FlClash 继续作为 Android 上唯一的 VPN。
- 普通外网流量按原来的代理规则走。
100.64.0.0/10这段 Tailscale 地址走 mihomo 内置的 Tailscale 出站。- 局域网地址,比如
192.168.0.0/16,继续直连。
也就是把 Tailscale 当成一个代理节点来用,而不是再单独开官方 Tailscale App。
我这里用的是 FlClash,内核是 mihomo。测试时版本大概是:
- FlClash:0.8.93
版本以后肯定会变,这里只是记录一下当时的环境。
Tailscale 出站配置
核心配置是一个 type: tailscale 的 proxy:
proxies:
- name: TAILSCALE
type: tailscale
hostname: flclash-android
auth-key: tskey-auth-xxxx
control-url: https://controlplane.tailscale.com
state-dir: ./tailscale
ephemeral: false
udp: true
accept-routes: true
ip-version: ipv4-prefer这里有几个字段比较关键:
hostname:这个设备在 Tailscale 里显示的名字。auth-key:Tailscale 的授权 key,这个不要公开。state-dir:Tailscale 本地状态目录,后面踩坑主要就在这里。accept-routes:是否接受 Tailscale 路由,我这里先开着。
我一开始还试过给 TAILSCALE 配 dialer-proxy,想让它通过代理去连 Tailscale 控制面。结果在 Android 上遇到过类似这样的错误:
magicsock: Rebind IPv4 failed: failed to bind any ports最后还是去掉了 dialer-proxy,让它直接走当前网络。至少我这里这样更稳定。
规则配置
然后需要在规则里把 Tailscale 网段放到前面:
proxy-groups:
- name: Tailscale
type: select
proxies:
- TAILSCALE
- DIRECT
rules:
- IP-CIDR,100.64.0.0/10,Tailscale,no-resolve
- IP-CIDR,100.100.100.100/32,Tailscale,no-resolve
- DOMAIN-SUFFIX,ts.net,Tailscale
- IP-CIDR,192.168.0.0/16,DIRECT,no-resolve
- IP-CIDR,10.0.0.0/8,DIRECT,no-resolve
- IP-CIDR,172.16.0.0/12,DIRECT,no-resolve
- IP-CIDR,169.254.0.0/16,DIRECT,no-resolve
# 其他代理规则
- MATCH,节点选择这里 100.64.0.0/10 是 Tailscale 使用的 CGNAT 网段,100.100.100.100 是 Tailscale DNS。规则要尽量靠前,不然可能先被其他规则截走。
局域网地址我还是直接放行。比如手机在家里访问 192.168.x.x,就没必要绕 Tailscale。
FlClash 的应用白名单
我手机上 FlClash 用的是白名单模式,也就是只有选中的 App 才进入 VPN。
这个地方也容易误判。如果某个 App 没有被选中,它根本不会进入 FlClash,也就不会走内置 Tailscale。
比如我要用 CC Pocket 访问 Mac mini,就要确认 CC Pocket 在 FlClash 的白名单里。否则它访问 100.x.x.x 时,既不会走代理,也不会走 FlClash 里的 Tailscale 出站。
这个可以通过 adb 看一下 VPN 里包含哪些 UID:
adb shell 'dumpsys connectivity | grep -A6 "VPN CONNECTED" | grep "Uids"'
adb shell 'dumpsys package com.k9i.ccpocket | grep appId | head -1'如果 CC Pocket 的 appId 不在 VPN 的 Uids 范围里,那就是白名单没勾上。
怎么验证
调试时我主要用了 mihomo 的本地 API。FlClash 默认会开一个本地控制端口,我这里是 127.0.0.1:9090。
先看 Tailscale 出站有没有被加载:
adb shell 'curl -s http://127.0.0.1:9090/proxies/TAILSCALE'如果能看到类似 type: Tailscale 的信息,说明配置至少被 mihomo 识别了。
然后访问一个 Tailscale IP。因为 adb shell 本身不一定在 FlClash 的 VPN 白名单里,所以不能直接用普通 curl 判断。更稳的方法是显式走 mihomo 的 HTTP 代理口:
adb shell 'curl -v -x http://127.0.0.1:7890 --connect-timeout 8 http://100.x.x.x:8765/health'如果命中规则,日志里应该能看到类似:
match IPCIDR(100.64.0.0/10) using Tailscale[TAILSCALE]也可以抓实时日志:
adb shell 'curl -s "http://127.0.0.1:9090/logs?level=debug" | grep -iE "tailscale|tsnet|magicsock|100\\."'另外在 Mac 上也可以看 Tailscale 状态:
/Applications/Tailscale.app/Contents/MacOS/Tailscale status如果成功,应该能看到 flclash-android 这个节点变成 active。
CC Pocket 的 404
后面还有一个小插曲。
我用 CC Pocket 连 Mac mini,局域网地址 192.168.x.x:8765 正常,但 Tailscale 地址 100.x.x.x:8765 一直看起来不对。一开始怀疑是 Android 版 CC Pocket 或官方 Tailscale App 有问题。
后来才发现是 Mac 上有一个之前测试时留下的 Python HTTP 服务,刚好绑定在 Tailscale IP 的 8765 端口。结果访问 100.x.x.x:8765 时命中的是这个测试服务,而不是 CC Pocket bridge;访问 192.168.x.x:8765 时才命中真正的 bridge。
可以用这个命令查:
lsof -nP -iTCP:8765 -sTCP:LISTEN停掉那个 Python 服务后,两个地址都打到了同一个 bridge。
另一个容易误会的点是,不要只根据浏览器打开 / 的结果判断服务是否正常。我们当时看到过 404,但真正的问题不是 URL 协议写错,而是请求打到了另一个占用端口的测试服务。
所以这类问题最好先确认端口背后到底是谁在监听,再看应用自己的健康检查接口:
curl http://100.x.x.x:8765/health最后
目前这套方案已经能满足我的需求:Android 上只开 FlClash,一个 VPN 同时处理代理和 Tailscale 访问。
整体感觉可用,但排查时要记住几件事:
- Android 上官方 Tailscale App 和 FlClash 不能同时作为系统 VPN。
type: tailscale是否真的登录成功,要看日志和 tailnet 状态。- 白名单模式下,目标 App 必须进 FlClash VPN。
state-dir不要随便改。- 看到 HTTP 404 时先确认访问的是不是正确协议和正确服务。
这次折腾下来,感觉最有用的还是日志。规则看起来都对的时候,不要继续猜,直接看 mihomo logs 里 Tailscale 有没有真的登录成功。