不用 root:我用 Tailscale 打通了云手机的 ADB 通道

文章目录

  • [不用 root:我用 Tailscale 打通了云手机的 ADB 通道](#不用 root:我用 Tailscale 打通了云手机的 ADB 通道)
    • TL;DR
    • 一、起因:一台"不听话"的云手机
    • 二、先想清楚:问题到底出在哪
    • [三、第一次尝试:IPv6 直连(失败)](#三、第一次尝试:IPv6 直连(失败))
    • 四、关键转折:需要一个"牵线的人",但不一定是一台服务器
      • [4.1 Tailscale 到底做了什么(大白话版)](#4.1 Tailscale 到底做了什么(大白话版))
      • [4.2 为什么这条路能绕开"没有公网 IP"](#4.2 为什么这条路能绕开"没有公网 IP")
    • [五、另一半拼图:无线调试让 adb 不用 root 也能开门](#五、另一半拼图:无线调试让 adb 不用 root 也能开门)
    • [六、实操:从零到 `adb devices`](#六、实操:从零到 adb devices)
      • [6.1 云手机侧](#6.1 云手机侧)
      • [6.2 电脑侧](#6.2 电脑侧)
      • [6.3 配对与连接](#6.3 配对与连接)
      • [6.4 固化端口为 5555](#6.4 固化端口为 5555)
    • 七、踩坑记录
      • [坑 1:Termux 里 `su` 找不到](#坑 1:Termux 里 su 找不到)
      • [坑 2:Termux 的 curl 符号缺失](#坑 2:Termux 的 curl 符号缺失)
      • [坑 3:无线调试端口会漂移](#坑 3:无线调试端口会漂移)
      • [坑 4:cmd 和 PowerShell 语法不一样](#坑 4:cmd 和 PowerShell 语法不一样)
      • [坑 5:切换端口后出现 `offline` 僵尸设备](#坑 5:切换端口后出现 offline 僵尸设备)
      • [坑 6:配对端口 ≠ 连接端口](#坑 6:配对端口 ≠ 连接端口)
    • [八、意外发现:这台云机的 adbd 居然是 root](#八、意外发现:这台云机的 adbd 居然是 root)
    • [九、能力验证:adb 到底能不能用](#九、能力验证:adb 到底能不能用)
      • [adb 能力边界](#adb 能力边界)
    • 十、这套方案适合谁
    • 十一、日常维护:重启之后怎么办
    • 十二、结语
    • [附录 A:命令速查](#附录 A:命令速查)
    • [附录 B:检查清单](#附录 B:检查清单)
    • [附录 C:本次实测环境](#附录 C:本次实测环境)

不用 root:我用 Tailscale 打通了云手机的 ADB 通道

摘要:云手机没有公网 IP、没有 root、厂商也不提供 ADB 入口,常规教程都要求你租一台云服务器跑 frp。本文记录另一种做法:用 Tailscale 组一个虚拟局域网,配合 Android 11 的「无线调试」,在两个约束(不租服务器、不 root)下拿到完整的 adb 控制能力。包含原理、实操、踩坑和实测的能力边界。

关键词:云手机、ADB、Tailscale、无线调试、Android 自动化、NAT 穿透


TL;DR

  • 云手机和本地电脑都装 Tailscale,登录同一账号
  • 云手机打开 开发者选项 → 无线调试 (Android 11+,不需要 root)
  • 电脑执行 adb connect 100.x.y.z:<端口>
  • 可选:adb tcpip 5555 把随机端口固化成 5555
  • 结果:零服务器、零 frp、零 Termux、零 root,adb 全功能可用

一、起因:一台"不听话"的云手机

我手上有一台桃心云手机(型号 Mate 30pro),想从本地电脑用 adb 控制它,做 UI 自动化。

正常手机插根 USB 就行,云手机不行:它在云厂商的 VPC 里,地址是 172.18.15.x,外面根本路由不到。厂商也没有提供任何 ADB 入口。

搜到的教程几乎都是同一个模板:

复制代码
云手机装 Termux → 装 proot → 修 DNS → 装 frpc
云服务器装 frps → 开放端口 → 配 token
电脑装 frpc visitor → adb connect 127.0.0.1:55556

这套方案本身没问题,我在另一台云机上跑通过。但它有两个硬性前提:

  1. 你必须有一台公网服务器(对应本文的约束一:不想租)
  2. 云手机必须有 root(对应约束二:这台没有)

更麻烦的是,这类教程里的坑极多:Termux 的 curl 版本错配、Go 程序的 DNS 解析失败、Android 13 的 adbd 只听 IPv6、Termux 粘贴长 URL 被输入法污染......我上一台机器全部踩过一遍。

所以这次我想找一个更简单的路。


二、先想清楚:问题到底出在哪

很多人把这件事理解成"需要一条隧道"。但隧道只是手段,真正的问题是:

两台机器互相找不到对方。

看一下双方的地址:

设备 地址 别人能访问吗
云手机 172.18.15.19 ❌ 厂商 VPC 内部地址
我的电脑 192.168.1.100 ❌ 家庭局域网地址
我的电脑 240e:878:...:17a2 ✅ 公网 IPv6

云手机没有公网地址;电脑有一个公网 IPv6。

于是有一个看起来很美妙的思路:

让云手机主动连到我的电脑,走 IPv6,不就完全不需要服务器了吗?

这个思路在逻辑上完全成立------只要云手机也有 IPv6,并且能路由到我家。


三、第一次尝试:IPv6 直连(失败)

先在云手机(Termux)里测一下能不能用 IPv6 出网:

bash 复制代码
curl -6 -sS -o /dev/null -w "HTTP=%{http_code}  IP=%{remote_ip}\n" --max-time 10 https://www.baidu.com

结果:

复制代码
curl: (7) Failed to connect to www.baidu.com:443 after 149 ms: Could not connect to server

加上 -v 看细节:

复制代码
* IPv6: 240e:ff:e020:98c:0:ff:b061:c306, ...
* IPv4: (none)
*   Trying [240e:ff:e020:98c:0:ff:b061:c306]:443...
* Immediate connect fail for 240e:ff:e020:98c:...: Network is unreachable
* connect to 240e:ff:e020:98c:... port 443 from :: port 0 failed: Network is unreachable

注意这一句:

复制代码
from :: port 0

from :: 的意思是内核找不到任何可用的源 IPv6 地址 ------不是被防火墙拦了,也不是 DNS 问题,而是这台云手机压根没有被分配 IPv6 地址。

结论很干脆:

复制代码
云手机:只有私网 IPv4,无 IPv6
电脑:  私网 IPv4 + 公网 IPv6
        ↑
        双方都没有对方能到达的地址

直连这条路,在物理层面就断了。

顺带说一句:如果当时云手机真有公网 IPv6,这条路是可行的------电脑起一个 frps,云机主动反向连过去即可,同样不需要额外的服务器。所以这个检查值得每次换云机时都做一遍。


四、关键转折:需要一个"牵线的人",但不一定是一台服务器

直连不行,那就必须有第三方帮忙牵线。传统做法是自己租一台公网服务器跑 frp,但牵线这件事本身,已经有成熟服务免费做了。

这就是 Tailscale 这类 P2P 组网工具的价值。

4.1 Tailscale 到底做了什么(大白话版)

把 Tailscale 想成一个私人快递网络:

  1. 登记 :两台设备都主动向 Tailscale 的协调服务器发起出站连接(出站不受 NAT 限制)
  2. 发牌 :协调服务器给每台设备分配一个虚拟地址(100.x.y.z)
  3. 牵线 :把双方信息互换,两端尝试直接打洞
    • 打通 → 数据 P2P 直连,速度快
    • 打不通 → 走 DERP 中继,能通但慢一点
  4. 加密:全程 WireGuard 加密,即使走中继,中继也看不到内容

关键在于:协调服务器只负责牵线,不承载业务流量(除非走中继),所以你不需要"租一台服务器并维护它"。

分配完地址后,100.70.52.94 就是云手机在虚拟局域网里的门牌号,你的电脑访问它,就像访问同一局域网里的设备。

复制代码
你的电脑 100.95.223.67  ══►  100.70.52.94 云手机
           (Tailscale 虚拟网络,WireGuard 加密)

4.2 为什么这条路能绕开"没有公网 IP"

因为两端都是主动往外连:

  • 云手机 → Tailscale 协调服务器:出站,NAT 允许
  • 电脑 → Tailscale 协调服务器:出站,NAT 允许

只要云手机能上网(能 apt 装包就说明能),就能被拉进这个网络。这和云手机有没有公网 IP、有没有 IPv6 完全无关。


五、另一半拼图:无线调试让 adb 不用 root 也能开门

网络通了,但云手机里还得有个"接活的管家"------adbd(adb 守护进程)。

平时 adbd 只听 USB。要让它监听网络端口,传统做法是:

bash 复制代码
setprop service.adb.tcp.port 5555
stop adbd
start adbd

这三条命令需要 root。 这就是为什么旧教程都强调"云机必须 root"。

但 Android 11(API 30)之后,系统设置里直接提供了**「无线调试」**开关:

设置 → 开发者选项 → 无线调试 → 打开

打开后,系统自己就把 adbd 启动在 TCP 模式上,监听一个随机高位端口。不需要 root,不需要 setprop,不需要重启 adbd。

打开后云机会显示:

复制代码
IP 地址和端口
172.18.15.19:37085

这个 37085 就是 adbd 的门牌号。

于是整个方案的两个前提就都满足了:

问题 解法
两台机器互相找不到 Tailscale 虚拟局域网
adb 开不了门 Android 11+ 无线调试

服务器和 root,一个都不需要。


六、实操:从零到 adb devices

下面是浓缩版流程。每一步都带验证方法。

6.1 云手机侧

① 打开开发者选项

设置 → 关于手机 → 连点「版本号」7 次。

② 打开无线调试

设置 → 系统 → 开发者选项 → 无线调试 → 打开。

记下屏幕上的端口(后面会变,先记着)。

③ 安装 Tailscale

  • 有 Google Play:搜索 Tailscale,开发者认准 Tailscale Inc.
  • 没有:用云机浏览器打开 https://pkgs.tailscale.com/stable/tailscale-latest.apk 直接装

④ 登录并连接

打开 Tailscale → Sign in → 用 Google / Microsoft / GitHub 登录 → 点开关 → 系统弹出 VPN 授权 → 允许。

连接成功后会显示:

复制代码
mate-30pro.tailXXXX.ts.net
100.70.52.94                ← 记住这个
fd7a:115c:a1e0::xxxx:xxxx

⑤ 关掉电池优化

设置 → 应用 → Tailscale → 电池 → 无限制。

不做这一步,Android 的 Doze 会在后台杀掉 VPN,表现为"用着用着突然断了"。

⑥ 关闭密钥过期(强烈建议)

浏览器打开 https://login.tailscale.com/admin/machines → 找到云机 → ... → Disable key expiry。

云机重新登录很麻烦,关掉省事。

6.2 电脑侧

① 安装 Tailscale

powershell 复制代码
winget install --id Tailscale.Tailscale -e

或用官网安装包。装完登录同一个账号,点 Connect。

验证:

powershell 复制代码
tailscale status

预期能看到云机:

复制代码
100.95.223.67   my-pc        xxx@  windows  -
100.70.52.94    mate-30pro   xxx@  android  -

② 确认 adb 可用

cmd 复制代码
adb version

需要 platform-tools 30.0.0 以上(adb pair 随 Android 11 一同提供)。建议直接用最新版。

6.3 配对与连接

新设备第一次连接需要配对(我的这台之前配对过,所以直接连上了,但新云机大概率要配对)。

云机:无线调试 → 使用配对码配对设备

屏幕会显示:

复制代码
Wi-Fi 配对码
123456

IP 地址和端口
172.18.15.19:41234    ← 注意这个是配对端口

电脑:

cmd 复制代码
adb pair 100.70.52.94:41234

⚠️ 这里有一个非常容易踩的坑 :云机屏幕上显示的 IP 是它的内部地址 (172.18.15.19),你根本连不上。要把 IP 换成 Tailscale 地址 ,端口保持屏幕上的配对端口不变。

然后输入 6 位配对码:

复制代码
Enter pairing code: 123456
Successfully paired to 100.70.52.94:41234

接着连接连接端口(不是配对端口):

cmd 复制代码
adb connect 100.70.52.94:37085

验证:

cmd 复制代码
adb devices -l
复制代码
100.70.52.94:37085   device product:CHUNMEI model:Mate_30pro ...

device 状态就代表完全可用。

再确认 shell:

cmd 复制代码
adb shell echo ok

6.4 固化端口为 5555

无线调试的端口每次重启都会变(我这次就从 37085 漂移到了 33189)。固化一次省心:

cmd 复制代码
adb tcpip 5555

输出:

复制代码
restarting in TCP mode port: 5555

注意:这一瞬间 adbd 会重启,当前连接会断开,属正常现象。重新连:

cmd 复制代码
adb connect 100.70.52.94:5555

之后只要云机没重启,永远用 5555。

边界 :adb tcpip 在云机重启后失效,会回到随机端口模式,需要重新做一遍。


七、踩坑记录

坑 1:Termux 里 su 找不到

复制代码
~ $ su -c id
No su program found on this device. Termux does not supply tools for rooting...

command -v su 返回的是 Termux 自己的包装脚本 /data/data/com.termux/files/usr/bin/su,不是真的 su。

但这不影响本方案------因为根本不需要 root,也不需要 Termux。这是我一开始按旧方案排查时走的弯路。

有意思的是,后来发现 adb shell id 显示 uid=0(root)。原因是 adbd 本身以 root 运行,和 Termux 是两回事:Termux 是普通 APP(uid 10xxx),看不到系统的 su;adbd 是系统服务,本来就有 root。二者上下文不同。

坑 2:Termux 的 curl 符号缺失

复制代码
CANNOT LINK EXECUTABLE "curl": cannot locate symbol
"nghttp2_option_set_no_rfc9113_leading_and_trailing_ws_validation"
referenced by "/data/data/com.termux/files/usr/lib/libcurl.so"

补了 libnghttp2 之后又冒出:

复制代码
cannot locate symbol "libssh2_crypto_engine"

原因是这台云机的 Termux 环境里有一批库停留在老版本,而 libcurl 升级到了 8.23,依赖链对不上。逐个补会一直冒新错,正确做法是一次性升级:

bash 复制代码
pkg upgrade -y

(中途问 openssl.cnf (Y/I/N/O/D/Z) 直接回车选默认)

升级后:

复制代码
curl 8.23.0 ... OpenSSL/3.6.5 libssh2/1.11.1 nghttp2/1.70.0

这个坑只在"用 Termux 验证网络"时遇到,最终方案不用 Termux,所以可以完全跳过。

坑 3:无线调试端口会漂移

同一台云机,先显示 37085,后来变成 33189。每次关开无线调试、云机重启都会变。

所以要么每次回云机屏幕看一眼,要么用 adb tcpip 5555 固化。

坑 4:cmd 和 PowerShell 语法不一样

在 cmd 里执行 PowerShell 写法:

cmd 复制代码
adb exec-out screencap -p > "$env:USERPROFILE\Desktop\a.png"

报:

复制代码
文件名、目录名或卷标语法不正确。

因为 cmd 不认识 $env:,而且 : 在路径里非法。

通用写法(cmd / PowerShell 都能跑):

cmd 复制代码
adb shell screencap -p /sdcard/screen.png
adb pull /sdcard/screen.png D:\adb_test\screen.png

顺便还验证了文件传输能力。

坑 5:切换端口后出现 offline 僵尸设备

adb tcpip 5555 之后,adb devices 里会同时出现:

复制代码
100.70.52.94:37085     offline
100.70.52.94:5555      device

旧的 37085 记录已经是僵尸,清理即可:

cmd 复制代码
adb disconnect 100.70.52.94:37085

如果报 error: no such device,说明已经自动清掉了,忽略。

坑 6:配对端口 ≠ 连接端口

云机无线调试页面上:

  • 「IP 地址和端口」 → 用于 adb connect
  • 「使用配对码配对设备」里的端口 → 用于 adb pair

这是两个不同的端口。用错会直接连不上。


八、意外发现:这台云机的 adbd 居然是 root

连接建立后,我随手跑了一下:

cmd 复制代码
adb root

输出:

复制代码
adbd is already running as root

再查身份:

cmd 复制代码
adb shell id
复制代码
uid=0(root) gid=0(root) groups=0(root),1004(input),1007(log),1011(adb),...
context=u:r:shell:s0

uid=0 但 context=u:r:shell:s0 ------ 这是整份记录里最关键的一行。

它说明:

  • DAC 层面:进程是 root,文件权限位对它不设限
  • MAC 层面 :SELinux 域仍是 shell,不是 su / magisk

继续查构建信息,结果更有意思:

属性 值 标准 AOSP 的含义
ro.build.type user adbd 应以 shell 运行
ro.debuggable 0 不可调试
ro.secure 1 adbd 应降权
getenforce Enforcing SELinux 生效

按标准 AOSP,这三个属性都意味着 adb root 应该失败。但这台成功了 ,而且 ls /data/data 能列出 118 个应用目录。

结论:这是厂商魔改过的镜像 ------在 user 构建上打了补丁,让 adbd 无条件以 root 运行,同时放宽了 shell 域对 /data/data 的读取。

能力定位

复制代码
普通 shell  <  这台云机(uid=0 + SELinux shell 域)  <  Magisk 完全体
  • ✅ 能读更多文件、能 pm grant、能看应用数据目录
  • ⚠️ 写系统分区、改 persist.* 大概率仍被 SELinux 拦
  • ❌ 不是真正的"完全 root"

这个差异因厂商而异。 换一台云机,上面这些结论可能完全不同,建议每台都实测记录。


九、能力验证:adb 到底能不能用

光看到 device 不够,我做了一层能力验证:

验证项 命令 结果
shell 执行 adb shell echo ok ✅ ok
截屏 screencap + pull ✅ 720×1300 真实画面
UI 控件树 uiautomator dump ✅ 43 个节点,text 完整
安装 APK adb install -r ✅ Success
启动应用 monkey -p <pkg> -c LAUNCHER 1 ✅ Events injected: 1
文件传输 adb push / adb pull ✅

对 UI 自动化来说,这几项已经够了:看得见(截屏)、定位得到(控件树)、点得动(input)、装得上(install)。

adb 能力边界

完整可用 :设备管理、应用安装/卸载、文件推拉、截屏录屏、UI 自动化、dumpsys、logcat、am、pm、input......

部分受限:

能力 限制
input text 中文 只支持 ASCII,中文需 ADBKeyboard 或输入法广播
settings put 某些键 Android 12+ 有限制
screenrecord 默认最长 180 秒
adb backup Android 12+ 基本废弃

无 root 不可用 (除非像这台一样厂商魔改):adb root、改系统属性、直接读写 /data/data、刷机。


十、这套方案适合谁

适合

  • 云手机 Android 11+
  • 允许安装第三方 APK
  • 不禁止 VPN(有些厂商会锁 VPNService)
  • 没有 root,或不想折腾 root
  • 不想为了一次 adb 连接去租服务器
  • 本地电脑不在云厂商 VPC 内

不适合 / 需要换路

情况 替代方案
Android < 11 或无线调试被裁 有 root → frp / SSH 反向隧道;无 root → 厂商官方 ADB
云机禁止 VPN frp + 公网服务器;ZeroTier(同样可能被禁)
云机有公网 IPv6 可以不用 Tailscale,电脑起 frps 反向隧道
自建 redroid / 容器 直接 adb connect <容器IP>:5555,什么隧道都不用

还有一个必须提前确认的点

云机是不是"还原型"实例------重启后应用和登录状态是否保留。

如果不保留,每次重启都要重装 Tailscale、重新登录、重新开无线调试。这会让方案的日常成本大幅上升。建议先装个无关紧要的 APP,重启一次看看还在不在。


十一、日常维护:重启之后怎么办

云机重启后,按这个顺序恢复:

复制代码
① 云机:确认 Tailscale 是 Connected(必要时手动连)
② 云机:开发者选项 → 无线调试 → 确认开着 → 记下当前端口
③ 电脑:adb connect 100.70.52.94:<当前端口>
④ 电脑:adb tcpip 5555
⑤ 电脑:adb connect 100.70.52.94:5555

一键脚本(端口已固化为 5555 时):

bat 复制代码
@echo off
set PHONE=100.70.52.94
adb connect %PHONE%:5555
adb devices -l
adb -s %PHONE%:5555 shell echo ok
pause

断线排查顺序:

复制代码
1. tailscale status          → 云机那行是否 online
2. ping 100.70.52.94         → 通不通
3. 云机本地 nc -vz 127.0.0.1 5555  → adbd 在不在
4. 端口对不对
5. 配对是否失效(failed to authenticate)
6. adb kill-server / start-server

十二、结语

这套方案的本质,是把"需要自己维护的基础设施"换成了"已经存在的服务":

旧方案 新方案
租云服务器 + 装 frps Tailscale 账号
root + setprop 开 adbd 系统设置里的无线调试
Termux + proot + frpc Tailscale APP
token + secretKey 账号登录

代价是引入了对 Tailscale 这个第三方服务的依赖,以及"云机必须允许 VPN"这个新约束。但对大多数个人使用场景来说,这笔交换非常划算------从三台机器、三个进程、一堆配置文件,变成两个 APP 和一条命令。

至于那台云机 adb shell 居然是 root 的意外收获,算是个彩蛋。它提醒我一件事:云手机厂商的镜像差异极大,任何"标准结论"都要自己实测一遍。


附录 A:命令速查

cmd 复制代码
:: 连接
adb devices -l
adb connect 100.70.52.94:5555
adb disconnect 100.70.52.94:5555
adb kill-server
adb start-server

:: 配对
adb pair 100.70.52.94:<配对端口>

:: 固定端口
adb tcpip 5555

:: 设备操作
adb shell <命令>
adb push <本地> <设备>
adb pull <设备> <本地>
adb install -r <apk>
adb uninstall <包名>

:: 自动化
adb shell screencap -p /sdcard/a.png
adb shell uiautomator dump /sdcard/ui.xml
adb shell input tap <x> <y>
adb shell input swipe <x1> <y1> <x2> <y2> <ms>
adb shell input keyevent <键值>
adb shell am start -n <包名>/<Activity>
adb shell am force-stop <包名>
powershell 复制代码
# Tailscale
tailscale status
tailscale ping mate-30pro

附录 B:检查清单

云机

  • Android ≥ 11
  • 开发者选项已开
  • 无线调试已开,端口已记录
  • Tailscale 已装、已登录、已 Connected
  • 已记录 100.x 地址
  • 电池优化已关闭
  • key expiry 已关闭

电脑

  • Tailscale 已装、登录同一账号
  • tailscale status 能看到云机
  • adb ≥ 30.0.0(支持 adb pair)
  • adbkey 已备份(换电脑时免配对)

连接

  • 已配对
  • adb connect 成功
  • adb devices 显示 device
  • adb tcpip 5555 已执行
  • adb connect ...:5555 成功

附录 C:本次实测环境

项 值
云机 Mate 30pro(product: CHUNMEI, device: OBXIN)
云机内部 IP 172.18.15.19
云机 Tailscale 100.70.52.94
云机 IPv6 ❌ 无
adb shell 身份 uid=0(root),context=u:r🐚s0
SELinux Enforcing
构建属性 user / debuggable=0 / secure=1(厂商魔改)
电脑 Windows 11 (10.0.22000)
电脑 Tailscale 100.95.223.67
adb 版本 1.0.41 / 34.0.4
初始端口 37085(后漂移为 33189)
固化端口 5555

说明 :文中"配对流程"按 Android 11+ 标准流程编写。实测设备因之前已配对,adb connect 直接成功,未走配对;"把配对界面显示的 IP 换成 Tailscale IP"是基于 adbd 监听所有网卡的推断,新设备首次配对时请以实际结果为准。

相关推荐
张小姐的猫4 小时前
【MySQL】表的约束
android·linux·服务器·数据库·c++·mysql·adb
狂师4 小时前
夯爆了!又一款开源企业级一站式AI智能测试平台!(附平台架构、技术选型)
人工智能·测试·全栈
DogDaoDao1 天前
Windows 双 Shell 全面对比:CMD 与 PowerShell 的技术剖析
windows·shell·测试·powershell·cmd·程序员开发
花间相见1 天前
【计算基础|MySQL11】事务与隔离级别
sql·mysql·adb·架构
Layer1 天前
一些脱离 Mac 依赖的 Agent 操作 iPhone 实现
ios·ai编程·测试
闻缺陷则喜何志丹1 天前
计算24点
c·测试·input·output
百慕大三角1 天前
为了在 Linux CI 里跑小程序 E2E,我把微信官方基础库搬进了浏览器:wxa-runner 开源实践
微信小程序·agent·测试
golang学习记1 天前
IDEA 的 Java 智能,现在跑到 Cursor 和 VS Code 里去了
java·ide·vscode·intellij-idea·claude code