Windows 下通过 SSH port forwarding 在 USB 连接在另一台设备上的 Android 设备上远程调试 Xamarin 应用

最近开始研究 Android 开发,装了 Visual Studio 2017 的 Xamarin 组件和 Android Studio,暂时只试用了 Xamarin。但是因为用的虚拟机,而且我司 Wi-Fi 奇慢,所以不得不寻找其它的方法进行调试。虽然让本子做热点也是可行的方案,但是还有更优雅、高效的方法,就是端口映射。

  1. 标题真长

  2. 本文希望能提供 step-to-step 指导,完成 Win32-OpenSSH 和 PuTTY 的安装和配置,并尽可能列出我遇到的问题及解决方案

  3. 本文介绍的 不是 adb 的 Wi-Fi 连接

问题

需要远程调试 Android 设备!

  1. 开发使用的是虚拟机,无法将 Android 设备 USB 连接至虚拟机,并且没有 Wi-Fi 或 Wi-Fi 质量过差导致无法通过 Wi-Fi 将设备连接至虚拟机

  2. 远程开发,完全无法物理或空气接触开发机器,无法将 Android 设备连接至开发机。

原理

  1. adb (Android Debug Bridge),负责接收系统内其它程序发送的指令,并对连接的 Android 设备进行操作。

    adb 有一个 server,默认监听在 5037 端口上,adb 子进程通过 socket 连接到 server 传递指令。但是 server 默认只监听 loopback interface,且官网给出的 -a 监听所有 interface 由于 bug 完全无效。

  2. ssh。提供安全的远程 shell 访问,同时有 port forwarding 的功能。通过在 adb 所在的设备上运行 ssh server 并配置 port forwarding,可以让远程设备也可以访问到 loopback 上的 adb server

准备 adb

首先在两台设备上都需要安装 adb,并且需要 相同版本 。adb 以前只和 Android SDK 打包提供,但是现已提供 单独下载,或者从已经安装 Android SDK 的设备上复制一份也没问题。

然后在连接 USB 的设备(下称 Server)上执行 adb start-server 启动 adb server。

1
2
3
PS C:\Users\Simon\adb> .\adb start-server
* daemon not running. starting it now at tcp:5037 *
* daemon started successfully *

安装并启动 Win32-OpenSSH

接下来需要在 Server 上安装 SSH Server,以下选择 Win32-OpenSSH。

Win32-OpenSSH 是 PowerShell 开发组维护的 OpenSSH 的 Win32 移植。最新的 release 可以在 GitHub Releases 页面下载到(Win32 和 Win64 自选,虽然我觉得可能没什么区别)。

GitHub Wiki 页面上有 安装说明。唯一需要重点说明的是,如果你的 Windows 10 启用了 Developer Mode,那么在 22 端口上已经有一个 SSH Server 在运行了(但是这个 SSH Server 除了部署 UWP 我实在不知道有什么用,连上的 cmd 都是废的)。解决方案是修改 sshd_config 中的 Port(去除行首的 # 取消注释,然后修改 22 到另一端口,比如 23),并且重新设置防火墙。

1
2
3
4
5
6
// Before
# Port 22

// Now
Port 23

最后启动 sshd,在 Administrator PowerShell 中执行 Start-Service sshd

安装并启动 PuTTY

下一步,在 Server 相对的,开发 IDE 运行的设备(下称 Client)上安装 PuTTY。

PuTTY 提供 GUI 配置的 SSH Client,感觉非常方便。可以在之前的链接中下载到,只需要 putty.exeputtygen.exe(如果要配置 public key 登录),下载到任意位置即可。

启动后出现 GUI 配置页面,输入 Server 的 Host Name 或 IP 地址,修改端口(如果不是 22),点击 Open 即可连接,没问题的话会询问用户名的密码,用 Server 的上用户名和密码登录即可(输入密码时不会有任何显示,这是正常现象)。

现在右击 PuTTY 的标题栏,选择 Change Settings... 打开连接设置,在左侧 Connection - SSH - Tunnels 中即可配置 port forwarding。在 Source port 中输入要在 Client 上监听的端口,在 Destination 中输入 forward 的目标 IP 和端口,最后点击 AddApply,就能创建一个 Local tunnel 了。重复操作能添加多个 tunnel,且同时有效。当程序连接到 Client 的 Souce port 时,它就好像在 Server 上连接 Destination,SSH 会将两侧的流量相互转发。

前面已知 adb 位于 5037 端口上,因此最直接的方法就是 Source port 输入 5037,Destination 输入 127.0.0.1:5037,这样 Client 上的 adb 子进程连接 5037 端口,就被 forward 到 Server 的 5037 端口上,对它来说就是连接到了 adb server,可以进行交互了。

为了不用每次重复设置,可以再打开 Change Settings...,在 Sessions 页面输入 session 名称或点击现有的 session,然后点击 Save

(也可以在连接时配置 tunnel 并保存 session,本文为了保持思路清晰才没有这样介绍)

Xamarin

如果一切顺利的话,现在打开 Visual Studio 中的 Xamarin.Android 项目,已经可以看到 Server 上连接的 Android 设备,并且进行 deploy 了。

如果不顺利,有很大的可能是 Client 上已经启动了 adb server,占用了 5037 端口,这样 PuTTY 就不能将 5037 端口配置 port forwarding。这时需要关闭 Visual Studio(因为 Xamarin 会一直试图重启 adb server,手速不够快可能一会又重启了),然后执行 adb kill-server 关闭现有的 adb server,然后重新启动 PuTTY 连接(已经有一个 shell 的话可以通过菜单里的 Duplicate Connection 选项),最后再重新启动 Visual Studio。之后也要尽量保证先启动 PuTTY,再启动 Visual Studio。

但是这时并不能 debug,点击 debug 后会看到 App 启动然后立即退出。查看 Output (Debug) 窗口是这样的

1
2
3
4
Android application is debugging.
Cannot start debugging: Cannot connect to 127.0.0.1:8xxx: No connection could be made because the target machine actively refused it 127.0.0.1:8xxx
No connection could be made because the target machine actively refused it 127.0.0.1:8xxx

经过一番艰苦的反编译,我终于找到了这个错误的原因。Xamarin 连接 Android 上的 Mono 进行 debug 时,需要使用两个额外的端口。正常情况下 Xamarin 会用 adb 的 port forwarding 功能,将本地 8805~9004 这 200 个端口,每次两个,映射到 Android 设备的对应端口上。像之前介绍 port forwarding 时说的,Xamarin 连接 127.0.0.1:8805 端口,就好像在 Android 上连接它的 127.0.0.1:8805。再通过 adb shell setprop xxx 指令告诉 Mono 本次使用的端口,就可以相互连接进行 debug 了。

但是现在 adb server 位于 Server 上,所以 adb 的 port forwarding 也只是将 Server 的端口映射到了 Android 上,Xamarin 在 Client 上请求本地端口,当然是无法连接的。

虽然 Xamarin 每次都要换端口的原因或者想法不得而知,但既然如此那也只能想办法解决。最暴力的方法自然是用 SSH 把这 200 个端口全映射到 Server 上,因为我们不知道 Xamarin 什么时候要用什么端口。而且只是 200 个高位端口,对其它程序正常使用一般也没有影响,所以就这么干!不过 200 个端口,肯定不能通过 GUI 一个一个添加,因此需要寻找更高效的方法。

于是我经过百度 PuTTY 的命令行用法,用 Node.js 写了个脚本来生成连接指令。不过之后我又找到了一个稍好些的方法:修改注册表。PuTTY 的配置保存在注册表的 Computer\HKEY_CURRENT_USER\Software\SimonTatham\PuTTY\Sessions\ 路径下,选择名称之后 PortForwardings 中就是端口映射的信息了。针对这个格式生成配置会稍稍方便一点,以下就是完成的状态:

1
L5037=127.0.0.1:5037,L8805=127.0.0.1:8805,L8806=127.0.0.1:8806,L8807=127.0.0.1:8807,L8808=127.0.0.1:8808,L8809=127.0.0.1:8809,L8810=127.0.0.1:8810,L8811=127.0.0.1:8811,L8812=127.0.0.1:8812,L8813=127.0.0.1:8813,L8814=127.0.0.1:8814,L8815=127.0.0.1:8815,L8816=127.0.0.1:8816,L8817=127.0.0.1:8817,L8818=127.0.0.1:8818,L8819=127.0.0.1:8819,L8820=127.0.0.1:8820,L8821=127.0.0.1:8821,L8822=127.0.0.1:8822,L8823=127.0.0.1:8823,L8824=127.0.0.1:8824,L8825=127.0.0.1:8825,L8826=127.0.0.1:8826,L8827=127.0.0.1:8827,L8828=127.0.0.1:8828,L8829=127.0.0.1:8829,L8830=127.0.0.1:8830,L8831=127.0.0.1:8831,L8832=127.0.0.1:8832,L8833=127.0.0.1:8833,L8834=127.0.0.1:8834,L8835=127.0.0.1:8835,L8836=127.0.0.1:8836,L8837=127.0.0.1:8837,L8838=127.0.0.1:8838,L8839=127.0.0.1:8839,L8840=127.0.0.1:8840,L8841=127.0.0.1:8841,L8842=127.0.0.1:8842,L8843=127.0.0.1:8843,L8844=127.0.0.1:8844,L8845=127.0.0.1:8845,L8846=127.0.0.1:8846,L8847=127.0.0.1:8847,L8848=127.0.0.1:8848,L8849=127.0.0.1:8849,L8850=127.0.0.1:8850,L8851=127.0.0.1:8851,L8852=127.0.0.1:8852,L8853=127.0.0.1:8853,L8854=127.0.0.1:8854,L8855=127.0.0.1:8855,L8856=127.0.0.1:8856,L8857=127.0.0.1:8857,L8858=127.0.0.1:8858,L8859=127.0.0.1:8859,L8860=127.0.0.1:8860,L8861=127.0.0.1:8861,L8862=127.0.0.1:8862,L8863=127.0.0.1:8863,L8864=127.0.0.1:8864,L8865=127.0.0.1:8865,L8866=127.0.0.1:8866,L8867=127.0.0.1:8867,L8868=127.0.0.1:8868,L8869=127.0.0.1:8869,L8870=127.0.0.1:8870,L8871=127.0.0.1:8871,L8872=127.0.0.1:8872,L8873=127.0.0.1:8873,L8874=127.0.0.1:8874,L8875=127.0.0.1:8875,L8876=127.0.0.1:8876,L8877=127.0.0.1:8877,L8878=127.0.0.1:8878,L8879=127.0.0.1:8879,L8880=127.0.0.1:8880,L8881=127.0.0.1:8881,L8882=127.0.0.1:8882,L8883=127.0.0.1:8883,L8884=127.0.0.1:8884,L8885=127.0.0.1:8885,L8886=127.0.0.1:8886,L8887=127.0.0.1:8887,L8888=127.0.0.1:8888,L8889=127.0.0.1:8889,L8890=127.0.0.1:8890,L8891=127.0.0.1:8891,L8892=127.0.0.1:8892,L8893=127.0.0.1:8893,L8894=127.0.0.1:8894,L8895=127.0.0.1:8895,L8896=127.0.0.1:8896,L8897=127.0.0.1:8897,L8898=127.0.0.1:8898,L8899=127.0.0.1:8899,L8900=127.0.0.1:8900,L8901=127.0.0.1:8901,L8902=127.0.0.1:8902,L8903=127.0.0.1:8903,L8904=127.0.0.1:8904,L8905=127.0.0.1:8905,L8906=127.0.0.1:8906,L8907=127.0.0.1:8907,L8908=127.0.0.1:8908,L8909=127.0.0.1:8909,L8910=127.0.0.1:8910,L8911=127.0.0.1:8911,L8912=127.0.0.1:8912,L8913=127.0.0.1:8913,L8914=127.0.0.1:8914,L8915=127.0.0.1:8915,L8916=127.0.0.1:8916,L8917=127.0.0.1:8917,L8918=127.0.0.1:8918,L8919=127.0.0.1:8919,L8920=127.0.0.1:8920,L8921=127.0.0.1:8921,L8922=127.0.0.1:8922,L8923=127.0.0.1:8923,L8924=127.0.0.1:8924,L8925=127.0.0.1:8925,L8926=127.0.0.1:8926,L8927=127.0.0.1:8927,L8928=127.0.0.1:8928,L8929=127.0.0.1:8929,L8930=127.0.0.1:8930,L8931=127.0.0.1:8931,L8932=127.0.0.1:8932,L8933=127.0.0.1:8933,L8934=127.0.0.1:8934,L8935=127.0.0.1:8935,L8936=127.0.0.1:8936,L8937=127.0.0.1:8937,L8938=127.0.0.1:8938,L8939=127.0.0.1:8939,L8940=127.0.0.1:8940,L8941=127.0.0.1:8941,L8942=127.0.0.1:8942,L8943=127.0.0.1:8943,L8944=127.0.0.1:8944,L8945=127.0.0.1:8945,L8946=127.0.0.1:8946,L8947=127.0.0.1:8947,L8948=127.0.0.1:8948,L8949=127.0.0.1:8949,L8950=127.0.0.1:8950,L8951=127.0.0.1:8951,L8952=127.0.0.1:8952,L8953=127.0.0.1:8953,L8954=127.0.0.1:8954,L8955=127.0.0.1:8955,L8956=127.0.0.1:8956,L8957=127.0.0.1:8957,L8958=127.0.0.1:8958,L8959=127.0.0.1:8959,L8960=127.0.0.1:8960,L8961=127.0.0.1:8961,L8962=127.0.0.1:8962,L8963=127.0.0.1:8963,L8964=127.0.0.1:8964,L8965=127.0.0.1:8965,L8966=127.0.0.1:8966,L8967=127.0.0.1:8967,L8968=127.0.0.1:8968,L8969=127.0.0.1:8969,L8970=127.0.0.1:8970,L8971=127.0.0.1:8971,L8972=127.0.0.1:8972,L8973=127.0.0.1:8973,L8974=127.0.0.1:8974,L8975=127.0.0.1:8975,L8976=127.0.0.1:8976,L8977=127.0.0.1:8977,L8978=127.0.0.1:8978,L8979=127.0.0.1:8979,L8980=127.0.0.1:8980,L8981=127.0.0.1:8981,L8982=127.0.0.1:8982,L8983=127.0.0.1:8983,L8984=127.0.0.1:8984,L8985=127.0.0.1:8985,L8986=127.0.0.1:8986,L8987=127.0.0.1:8987,L8988=127.0.0.1:8988,L8989=127.0.0.1:8989,L8990=127.0.0.1:8990,L8991=127.0.0.1:8991,L8992=127.0.0.1:8992,L8993=127.0.0.1:8993,L8994=127.0.0.1:8994,L8995=127.0.0.1:8995,L8996=127.0.0.1:8996,L8997=127.0.0.1:8997,L8998=127.0.0.1:8998,L8999=127.0.0.1:8999,L9000=127.0.0.1:9000,L9001=127.0.0.1:9001,L9002=127.0.0.1:9002,L9003=127.0.0.1:9003,L9004=127.0.0.1:9004

改完注册表,启动 PuTTY,启动连接,启动 Visual Studio,Debug,完美!

附:Public Key 登录

SSH 支持多种登录方式,每次都输入用户名和密码有些麻烦,而且其实安全性也并不高。一种推荐的方式是换用 Public Key 登录。以下简单介绍 OpenSSH 在 Windows 下配置 Public Key 登录的方法。

  1. 生成公钥和私钥。

    可以用 PuTTY 的工具 PuTTYGen 来通过 GUI 生成公钥和私钥。

    打开下载的 PuTTYGen,根据需要修改配置(在编写时点,一般采用 RSA 2048,即默认配置),点击 Generate a public/private key pair 右侧的 Generate 开始生成,根据提示在空白区域反复移动鼠标来生成强随机数,等进度条走完,就完成生成了。

    接下来可以在 Key comment 里输入一个好记的名称;Key passphrase 是打开私钥时需要输入的密码,给你多一重保护,推荐填写但可以不填。

    最后将 Public key for pasting into OpenSSH authorized_keys file 文本框中的文本全部复制,并且点击 Save the generated key 右侧的 Save private key 按钮,将 PuTTY 需要的格式的私钥保存到你想要的地方就可以了。

  2. 配置 OpenSSH 信任公钥。

    回到 Server,将上一步中复制的公钥粘贴到 ~/.ssh/authorized_keys 文件中。如果没有这个文件就自己创建,如果要信任多个私钥就每行一个公钥。需要注意 OpenSSH 默认用 SSHD 用户启动,无法访问你的用户文件夹,一定要调整 authorized_keys 文件的权限,给 Users 组 Read 权限。

  3. 重启 OpenSSH

  4. 配置 PuTTY 使用私钥登录。

    打开 PuTTY,选择你的 session 点击 Load,在左侧 Connection - Data 页面的 Auto-login username 中输入自动登录的用户名,在 Connection - SSH - Auth 中的 Private key file for authentication 选择上面保存的私钥,保存就可以了。连接时如果你给私钥设置了密码需要输入密码,未设置则不需要。这样登录就方便多了!

Have a nice day!