ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

libwebsockets lws-ssh-base 插件详解:以 ops 结构体驱动的可嵌入 SSH 服务器框架

libwebsockets lws-ssh-base 插件详解:以 ops 结构体驱动的可嵌入 SSH 服务器框架 人工智能AI Agent多模态语音AI 应用【免费下载链接】ten-frameworkOpen-source framework for conversational voice AI agents项目地址https://gitcode.com/TEN-framework/ten-framework点击查看免费下载本文基于 third_party/libwebsockets/READMEs/README.plugin-sshd-base.md系统讲解 libwebsockets 内置的lws-ssh-base协议插件它如何以极小的代码与内存占用实现一个完整的 SSH 服务端状态机如何通过struct lws_ssh_ops把认证、密钥存储、IO 与 shell 派生等全部行为交给上层应用定制以及如何借助 CMake 构建、静态包含、pvo 配置在自研应用中快速集成。读完本文你将掌握从零嵌入一个可定制 SSH 服务端的完整技术路线。一、lws-ssh-base 是什么lws-ssh-base源码位于 third_party/libwebsockets/plugins/ssh-base/是 libwebsockets 的一个协议插件实现了一个通用、抽象、可嵌入的 SSH 服务端。它不是一个开箱即用的sshd替代品而更像一个抽象基类加密、协议序列化、通道状态机等 SSH 内核全部内置但与业务相关的所有行为——认证、密钥读写、IO 转发、PTY、shell 派生——都通过一个ops结构体由调用方代码提供。其设计定位可以从 README 中归纳为以下几点占用极小代码与内存足迹都很小可运行在 ESP32 这类资源受限的嵌入式平台上。安全导向README 明确声明其经过 valgrind 与 Coverity 检查无问题无泄漏、无缺陷告警。绑定 vhost插件绑定到一个或多个 vhost由 vhost 控制监听端口。全部 IO 抽象化所有 IO 与设置通过单一的ops结构体由用户代码提供。每个实例独立 ops同一 vhost 上的每个插件实例拥有独立的ops结构体可定义各自的服务器密钥、认证方法与 IO 实现。无内置危险行为插件不会像系统 sshd 那样自动检查~/.ssh/authorized_keys、把认证用户名当作系统用户名、或派生用户的 shell。这些行为全部留给用户的 ops 回调决定——lws-ssh-base只有在你的回调中实现了这些逻辑时才会表现类似 sshd。加密栈自动适配根据 libwebsockets 编译时所用的 TLS 后端mbedTLS 或 OpenSSL自动使用相应的哈希与 RSA 函数。从 plugins/CMakeLists.txt 可以看到该插件在LWS_WITH_SSL开启时才被编译这印证了它依赖 libwebsockets 的 TLS/密码学基础设施。二、内嵌的密码学选型为了保持体积精简插件为 SSH 各环节各实现了一种最优单一选择不提供多个算法协商的冗余选项功能采用的算法密钥交换 KEXcurve25519-sha256libssh.org服务器主机密钥ssh-rsa4096 位对称加密chacha20-poly1305openssh.com压缩无None这些实现可以在 third_party/libwebsockets/plugins/ssh-base/crypto/ 下找到对应源码chacha.cChaCha20、poly1305.cPoly1305 MAC、ed25519.c/ge25519.c/sc25519.c/smult_curve25519_ref.c等Ed25519 与 Curve25519 的参考实现以及 kex-25519.c 中基于这些曲线实现的密钥交换逻辑。2.1 许可证说明lws-ssh-base是自由软件整体遵循 libwebsockets 的MIT 许可证。其加密部分源码虽然在其他地方也有 BSD 许可的版本但为简洁起见整个插件统一按 MIT 发布。三、生成自己的服务器密钥插件需要一套 RSA 服务器密钥用于身份标识。README 给出的生成命令为$ ssh-keygen -t rsa -b 4096 -f mykeys该命令会提示输入 passphrase并生成mykeys私钥mykeys.pub公钥如果你已有合适的 RSA 密钥例如日常 SSH 使用的密钥也可以直接复用不必重新生成。此外libwebsockets 安装时会向/usr[/local]/share/libwebsockets-test-server目录安装一套测试密钥对测试程序会接受这套密钥。注意这些测试密钥仅用于开发验证正如 test-apps/test-sshd.c 的注释所强调的那样切勿在生产环境使用否则任何拥有 libwebsockets 源码的人都能用配套私钥连入你的服务。四、两个开箱即用的示例4.1 独立测试程序 libwebsockets-test-sshdtest-apps/test-sshd.c 是一个完整可运行的示例它通过#include lws-plugin-sshd-static-build-includes.h以静态包含方式把整个 ssh-base 插件引入程序见该文件第 37-38 行。认证成功后它会在运行程序的用户凭据下派生一个 bash shell——远端 ssh 使用的用户名本身没有任何含义纯粹是占位符。它接受安装到/usr[/local]/share/libwebsockets-test-server的 lws 测试密钥。启动服务端因为服务器密钥存放在/etc下需要 root 权限$ sudo libwebsockets-test-sshd使用测试私钥连接$ ssh -p 2200 -i /usr/local/share/libwebsockets-test-server/lws-ssh-test-keys anyuser127.0.0.1程序默认监听2200 端口服务器密钥持久化在/etc/lws-test-sshd-server-key见 test-apps/test-sshd.c。值得注意的实现细节是程序在LWS_CALLBACK_PROTOCOL_INIT阶段、尚未丢弃权限之前就打开服务器密钥文件描述符privileged_fd此后所有密钥读写都通过这个预先打开的 fd 完成从而避免在事件循环运行期间保留不必要的系统权限test-apps/test-sshd.c。get_server_key返回空则说明密钥尚不存在此时插件会自动生成一把随机密钥并调用set_server_key持久化供后续使用。4.2 协议插件示例 lws-sshd-demoprotocol_lws_sshd_demo.c 是一个子类化抽象lws-ssh-base的示例插件它实现了ssh_ops但并不派生真实 shell而是进入一个简单的按键交互状态机SSH_TEST_GREET → SSH_TEST_PRESSED → SSH_TEST_DONE用于演示基础 SSH 通道能力。它的 ops 结构中set_env、pty_req、exec、child_process_io、child_process_terminated均置为NULLprotocol_lws_sshd_demo.c展示了不需要的能力留空即可的用法。在 lwsws 中配置一个监听 2222 端口、提供 lws-sshd-demo SSH 服务的 vhost对应配置如下{ name: sshd, port: 2222, onlyraw: 1, ws-protocols: [{ lws-ssh-base: { status: ok, ops-from: lws-sshd-demo }, lws-sshd-demo: { status: ok, raw: 1 } }] }这里的关键配置语义onlyraw: 1该 vhost 只服务于 RAW 连接不走 HTTP。lws-ssh-base条目中的ops-from: lws-sshd-demo指示lws-ssh-base从同 vhost 上启用的lws-sshd-demo协议处获取 ops 结构体。lws-sshd-demo条目中的raw: 1标记真正为 RAW 连接提供服务的协议是lws-sshd-demo而非lws-ssh-base本身。五、集成到自研应用应用侧步骤 0构建并安装 libwebsockets编译libwebsockets-test-sshd示例需要开启 CMake 选项LWS_WITH_CGI因为它使用 lws 的 CGI 辅助函数来派生 shell。lws-ssh-base插件本身不要求开启 CGI 支持。从 third_party/libwebsockets/CMakeLists.txt 可以看到该选项的默认值为 OFF需要在构建时显式打开。另外插件依赖 TLS 后端编译需要满足LWS_WITH_SSL默认 ON具体使用 OpenSSL 还是 mbedTLS 由LWS_WITH_MBEDTLS决定third_party/libwebsockets/CMakeLists.txt。步骤 1把代码引入你的应用将lws-plugin-ssh-base纳入应用有两种方式运行时动态加载作为 libwebsockets 的动态插件加载。静态包含使用 lws 的静态插件包含机制。若想一步把整个 ssh-base 插件静态引入应用只需 include#include plugins/ssh-base/include/lws-plugin-sshd-static-build-includes.h./test-apps/test-sshd.c中就有完整示例test-apps/test-sshd.c。步骤 2定义你的 struct lws_ssh_opsplugins/ssh-base/include/lws-plugin-ssh.h 定义了struct lws_ssh_ops它是所有定制与集成的唯一入口每个 vhost 一份。README 给出的完整初始化示例static const struct lws_ssh_ops ssh_ops { .channel_create ssh_ops_channel_create, .channel_destroy ssh_ops_channel_destroy, .tx_waiting ssh_ops_tx_waiting, .tx ssh_ops_tx, .rx ssh_ops_rx, .get_server_key ssh_ops_get_server_key, .set_server_key ssh_ops_set_server_key, .set_env ssh_ops_set_env, .pty_req ssh_ops_pty_req, .child_process_io ssh_ops_child_process_io, .child_process_terminated ssh_ops_child_process_terminated, .exec ssh_ops_exec, .shell ssh_ops_shell, .is_pubkey_authorized ssh_ops_is_pubkey_authorized, .banner ssh_ops_banner, .disconnect_reason ssh_ops_disconnect_reason, .server_string SSH-2.0-Libwebsockets, .api_version 1, };其中ssh_ops_...()函数都是你按需实现的回调。各字段的职责依据头文件中的文档注释归纳如下回调字段职责channel_create/channel_destroy通道生命周期管理priv指向你可以分配并挂到通道上的私有数据建议在其中保存wsi调用lws_callback_on_writable()即可触发后续.tx_waiting()询问rx收到对端 payload 时被调用buf/len给出数据tx_waiting返回LWS_STDOUT/LWS_STDERR的位掩码表示对应流是否有待发送数据返回 -1 将关闭到客户端的 TCP 连接tx把最多len字节的待发数据拷入buf返回实际拷贝字节数stdch区分LWS_STDOUT/LWS_STDERRget_server_key/set_server_key服务器密钥的读取与持久化get_server_key返回 0 表示无密钥插件会生成随机密钥并回调set_server_key存储set_env客户端请求设置环境变量返回非 0 表示失败exec客户端请求执行命令派生进程并把 stdin/out/err 接入 SSH 通道返回非 0 表示失败shell为相应用户派生合适的 shell返回 0 表示成功pty_req按struct lws_ssh_pty描述创建伪终端返回非 0 表示失败。lws_ssh_pty携带term、modes、宽高字符与像素等字段child_process_io子进程产生 IO 时的通知如 stdout/stderr 数据到达child_process_terminated子进程终止的通知is_pubkey_authorized确认客户端持有对应私钥后判定该公钥是否被授权给username返回 0 表示通过。这与 OpenSSH 查~/.ssh/authorized_keys的流程类似但逻辑完全由你实现banner连接后、认证前返回给客户端的横幅文本UTF-8并附 RFC3066 语言描述符如en/US不需要则置 NULLdisconnect_reason可选的断连原因通知reason取SSH_DISCONNECT_系列常量server_stringSSH 版本字符串按惯例形如SSH-2.0-Libwebsockets必填api_version填你编写代码时对应的 API 版本号若与插件当前LWS_SSH_OPS_VERSION不一致运行时将报错头文件中LWS_SSH_OPS_VERSION当前定义为 2lws-plugin-ssh.hREADME 示例里api_version 1是历史版本写法以你所用 lws 版本对应的宏值为准。另外lws-plugin-ssh.h还定义了大量SSHMO_*终端模式常量SSHMO_ECHO、SSHMO_ICRNL、SSHMO_ONLCR等供pty_req解析 PTY modes 时使用test-apps/test-sshd.c 演示了如何遍历这些 modes 并据此调整回显与换行转换行为。步骤 3把 lws-ssh-base 挂到 vhost 并通过 pvo 配置struct lws_ssh_ops的指针通过per-vhost optionspvo传给 vhost 上的协议实例static const struct lws_protocol_vhost_options pvo_ssh_ops { NULL, NULL, ops, (void *)ssh_ops }; static const struct lws_protocol_vhost_options pvo_ssh { NULL, pvo_ssh_ops, lws-sshd-base, /* ignored, just matches the protocol name above */ }; ... info.port 22; info.options LWS_SERVER_OPTION_ONLY_RAW; info.vhost_name sshd; info.protocols protocols_sshd; info.pvo pvo_ssh; vh_sshd lws_create_vhost(context, info);支持两种 pvoops如上所示直接在 pvo 的 value 中传入 ops 结构体指针。ops-from值为同一 vhost 上另一个协议的名字该协议的lws_protocols结构体.user成员指向的 ops 结构体即为lws-ssh-base所用。这样其他协议可以自行向lws-ssh-base提供 ops。六、集成到其他插件插件侧lws-sshd-demo演示了从一个插件子类化抽象lws-ssh-base的完整套路关键点如下通过.user成员传递 ops插件在自身的lws_protocols结构体user成员中放入 ops 结构体指针见 protocol_lws_sshd_demo.c。vhost 配置用ops-from指明协议名让lws-ssh-base从该协议取 opslws-ssh-base: { status: ok, ops-from: lws-sshd-demo }vhost 只服务 RAW无 HTTP{ name: sshd, port: 2222, onlyraw: 1, ... }标记真正服务 RAW 连接的协议是使用lws-ssh-base的协议而不是lws-ssh-base本身lws-sshd-demo: { status: ok, raw: 1 ... }从回调实现的角度看protocol_lws_sshd_demo.c 的callback_lws_sshd_demo在LWS_CALLBACK_PROTOCOL_INIT阶段分配 vhost 私有数据并提前打开服务器密钥 fd其余回调则通过lws_vhost_name_to_protocol()找到lws-ssh-base协议并把事件转发给其 callback——这正是子类复用父类状态机的落地方式。七、运行注意事项vhost 可以监听非标准端口。SSH 客户端可用-p参数指定端口连接$ ssh -p portnum userhostname非 22 端口的连接不需要 root 权限即可运行服务端而libwebsockets-test-sshd之所以要sudo仅因为其测试服务器密钥存放在/etc目录test-apps/test-sshd.c。在你的应用中把密钥放到普通用户可读写的位置即可免去 root。八、总结lws-ssh-base把 SSH 服务端最复杂、最易出错的部分——协议状态机、密钥交换、加密通道——固化在插件内部同时把哪些用户被允许登录、登录后能做什么这类策略问题完整地交给struct lws_ssh_ops回调。这种内核内置、行为外置的设计使它既可以充当 ESP32 等嵌入式设备上的迷你 SSH 服务端也可以作为自研应用内嵌管理接口的基础。建议进阶阅读插件头文件与完整回调语义lws-plugin-ssh.h插件核心实现plugins/ssh-base/sshd.c约 2600 行的 SSH 状态机密钥交换实现plugins/ssh-base/kex-25519.c应用侧完整示例test-apps/test-sshd.c插件侧完整示例plugins/protocol_lws_sshd_demo.c赞分享人工智能AI Agent多模态语音AI 应用【免费下载链接】ten-frameworkOpen-source framework for conversational voice AI agents项目地址https://gitcode.com/TEN-framework/ten-framework点击查看免费下载相关推荐探索Glider Labs SSH构建高效SSH服务器的利器探索Glider Labs SSH构建高效SSH服务器的利器 项目介绍 gliderlabs/ssh 是一个基于Go语言的高级SSH服务器包旨在简化SSH服网络通信认证鉴权NoneBot 机器人构成详解框架主体、驱动器、适配器与插件NoneBot 机器人构成详解框架主体、驱动器、适配器与插件 导读本文以 NoneBot 官方教程「机器人的构成」为骨架深入拆解一个基于 NoneBot后端即时通讯curl/libcurl 的 CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256以 SHA256 指纹钉扎 SSH 服务器公钥curl/libcurl 的 CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256以 SHA256 指纹钉扎 SSH 服务器公钥 导读 CURCLI网络通信上一篇webtrees数据库迁移指南从MySQL到PostgreSQL的无缝切换下一篇PhotoMovie实战案例从零开始制作专业级照片幻灯片视频创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表