Android SSH 保活客户端
一个轻量级 Android SSH 保活工具,用于维持与远程服务器的 SSH 长连接。核心连接逻辑使用 Rust(russh 纯 Rust SSH 实现)通过 JNI 与 Android 应用层交互,提供高性能、安全的 SSH 连接能力。
专为 CNB (cnb.cool) 工作区保活 场景设计:CNB 容器 10 分钟无活动会被自动回收,本应用通过定时心跳命令保持 SSH 会话活跃,防止容器被回收。
none 认证(CNB)、密码认证、公钥认证从 CNB Releases 下载最新 APK,直接安装即可。
cnb-ak5-xxx.xxx@cnb.spaceuser@host 或 user@host:portnone 认证,无需密码)echo keepalive 即可连接成功后会显示 handle 值和保活状态,通知栏会出现常驻通知。
┌─────────────────────────────────────────┐ │ Android App (Kotlin) │ │ MainActivity ←→ SshService ←→ SshNative│ └───────────────────┬─────────────────────┘ │ JNI (com.ssh.keepalive.SshNative) ┌───────────────────▼─────────────────────┐ │ Rust Library (russh) │ │ jni_bridge.rs → connection.rs │ │ → keepalive.rs │ └──────────────────────────────────────────┘
| 层级 | 技术 | 说明 |
|---|---|---|
| UI 层 | Kotlin + Material 3 | Android 界面 |
| 服务层 | Android Foreground Service | 后台保活 |
| 桥接层 | JNI (jni-rs) | Kotlin ↔ Rust 通信 |
| 连接层 | Rust (russh 0.60) | 纯 Rust SSH2 实现 |
| 异步运行时 | Tokio | Rust async runtime |
ssh/ ├── .cnb.yml # CI/CD 流水线(APK + Docker) ├── Dockerfile # Docker 镜像构建 ├── app/ │ ├── build.gradle.kts # 应用 Gradle 配置 │ ├── proguard-rules.pro # ProGuard 混淆规则 │ └── src/main/ │ ├── AndroidManifest.xml │ ├── java/com/ssh/keepalive/ │ │ ├── MainActivity.kt # 主界面 │ │ ├── SshService.kt # 前台服务 + 连接管理 │ │ └── SshNative.kt # JNI 声明 │ └── res/layout/ │ └── activity_main.xml # 界面布局 ├── rust/ssh-client/ │ ├── Cargo.toml # Rust 依赖(russh 0.60, jni, tokio) │ └── src/ │ ├── lib.rs # 库入口 │ ├── jni_bridge.rs # JNI 导出函数 │ ├── connection.rs # SSH 连接管理 │ └── keepalive.rs # 心跳保活逻辑 └── scripts/ └── build-android.sh # 本地构建脚本
# 完整构建
./scripts/build-android.sh
# 或分步构建
rustup target add aarch64-linux-android armv7-linux-androideabi x86_64-linux-android
cargo install cargo-ndk
cargo ndk -t arm64-v8a -t armeabi-v7a -t x86_64 build --release
./gradlew assembleRelease
推送代码到 main 分支后自动触发:
docker.cnb.cool/87878787.xyz/ssh:latest制品:
docker.cnb.cool/87878787.xyz/ssh:latest| 方式 | 参数 | 适用场景 |
|---|---|---|
none 认证 | 密码留空 | CNB 工作区 |
| 密码认证 | 填入密码 | 常规 SSH 服务器 |
| 公钥认证 | 指定密钥路径 | 高安全场景 |
以下是在开发过程中遇到并解决的关键问题,供参考。
Box::into_raw() 返回的指针在 ARM64 上高位可能为 1,强转 jlong (i64) 后变成负数。Kotlin 端 handle > 0 判断为 false,导致连接成功却显示失败。
修复:Kotlin 端改为 handle != 0L。
R8/ProGuard 会混淆 com.ssh.keepalive.SshNative 类名,导致 JNI FindClass 失败。
修复:精确添加 -keep class com.ssh.keepalive.SshNative { *; } 规则。
JNI 库加载失败抛出 UnsatisfiedLinkError,继承自 Error 而非 Exception,catch (e: Exception) 捕获不到。
修复:改为 catch (e: Throwable)。
russh 默认 features 依赖 aws-lc-rs,Android NDK 交叉编译失败(CMake/汇编兼容性问题)。
修复:default-features = false, features = ["ring", "flate2", "rsa"],用 ring 替代。
russh 的 connect_stream 需要 TcpStream,但 Vec<SocketAddr> 不实现 tokio::net::ToSocketAddrs。
修复:转为 &[SocketAddr] 切片。
CNB CI runner 是 Debian bookworm,Dockerfile 里用了 Ubuntu 的 apt 源导致 404。
修复:基础镜像改为 debian:bookworm,apt 源自动检测 OS。
私有项目,未经授权不得使用。