最近家里又多了一块“电子垃圾”———树莓派 Zero 2 W,如下图所示,用来作为便携、极简、低成本的旁路由网关。 由于 #树莓派 是“无头”设备:即无显示设备、无键盘、无鼠标;甚至无网线接口、无串口,只需要连接一根电源🔌线,所以很难维护(在不外接设备的情况下),除了网络以外几乎别无它法。
但是,网络配置很容易出错,而如果一旦配置出错,几乎就🟰设备已完全失联,只能重装系统。 好在重装系统时所用的官方 Imager 非常友好,可以一键设置需要连接的 WiFi、SSH 用户及证书等, 好处在于可以立马复活,坏处在于数据可能完全丢失。
起因
在经历过一次失联(IP 配置错误)、重装系统的痛苦后,我就想到,既然基于 WiFi 的网络不太“稳定”,能不能基于蓝牙?因为 #蓝牙 可以说是点对点的连接、不需要借助外部设备。 然后我就调研了一下,发现网上有很多人有类似需求,也有一部分“复杂”的实现。复杂在于,他们是尝试用 RFComm 设备连接到 getty 设备(获取默认的屏幕串口设备),实现复杂,且不稳定。
我的想法是:sshd 是绑定在 0.0.0.0 上面的,即便没有 WiFi 连接,通过 localhost:22 也可以访问成功。 为什么不直接通过蓝牙模拟出一条网络连接(可读、可写即可)访问它即可呢?
实现
我用低功耗蓝牙设备(BLE)实现了两个特性(Characteristics):
- 一个可读,用于从设备发送数据到主机
- 一个可写,用于从主机发送数据到设备
主机连接设备后:
- 主机将这两个特性的读、写数据作为 #ssh 的 ProxyCommand
- 设备将这两个特性的读、写数据转发给 sshd
这样一来,ssh 和 sshd 之间的数据就通过蓝牙从空中传递了。
严厉抨击不少评论者认为 ssh 必须依赖低层的基于 TCP 的网络连接。
代码
参考代码:https://github.com/movsb/tcp-over-bt。
代码量很少,主机和设备的代码行数均不到 150 行。无需任何配置。
在设备上运行后,它会开始广播自己。 尝试 ssh 到设备时,代码会尝试主动扫描第一个可用的设备并尝试连接。
具体的操作方式见代码的 README。
后续
虽然本文标题配文“树莓派”以及“SSH”,而实际上,本文所用的技术并不限制于此; 理论上,所有基于“可读可写的连接”均可用此实现:不限制为 SSH 连接、不限制为树莓派。
TODO
- 蓝牙库已经支持了 ConnectHandler 的回调,可以把代码中关于此部分的 Hack 去掉了。
第一次知道 ProxyCommand,真是不錯的發想。感覺搭配 socket 的話可以有很多花樣
毕竟,“网络连接”不过就是拿来“读”和“写”的“文件”而已,管它是 connect 来的、还是 stdio 呢。
之前还把 HTTP 通过 Upgrade 回退到 TCP 连接,然后同样作为 ProxyCommand 实现通过走 HTTP 服务/端口登录到公司的几十个服务器上。解决了防火墙问题,统一了域名登录流程。(参考)
具体指怎样玩呢?
因为我考虑到 ssh 是最重要的协议:一旦打通了 ssh,其它的服务都可以通过
ssh -L
端口转发来实现。所以代码里面直接写死了蓝牙连接成功后即转发给localhost:22
。ssh 好像甚至还支持 X11 窗口转发(不熟悉,没测试过)。
尝试用蓝牙连接后执行:
后 ssh 正常;断开 ssh 重连,仍然一切正常。
居然蓝牙还能用来连接SSH,涨知识啦
救命稻草🌾,也是我之前未曾想过的道路
。
神奇,怎么突然多了这么多 Stars,是不是哪位大佬推荐了一下 Orz.
好会玩啊,竟然还能这样。等会我也试试看
真好玩儿
,痛点-1,你好快!
失联一次就重装系统太难受了(Mac 上也没有简单的 ext4 挂载方案,不易离线修改网络配置)。
我甚至还跑了个 iperf 但是无奈真的有些……不太稳定,没跑完
🥵🥵🥵
不过,听到你如是说,应该是成功连上了!首先得恭喜
。
就我的使用体验来看,ssh 的响应速度不快(稍微有点不跟手)、也不算太慢。iperf 我是真不敢尝试。不过既然你试过了,我也试试…
不知道你说的是“不稳定”是指速度还是连接?
连接质量不稳定,经常会卡住或者连不上😂或者 device自动退出了 不过也就是能用就行
等等⌛️,目前的代码逻辑限定只能在设备开机的几分钟内成功连接一次,有意为之的(使用的库部分功能不完善),如果是这个问题的话,那就是已知的“feature”。
什么,竟然是feature而不是bug😂
问题解决✅(commit)。
已经允许:
有道理,有问题的时候发现走网络行不通、走蓝牙怎么可以行不通!必须随时待命!![狗头 [狗头]](/v3/dynamic/emojis/weixin/doge.png)
![狗头 [狗头]](/v3/dynamic/emojis/weixin/doge.png)
![狗头 [狗头]](/v3/dynamic/emojis/weixin/doge.png)