Loading... # SSH 无 Host 头问题分析与解决方案 # 一、事件概述 ## 1. 事件背景 exe.dev 是一个提供虚拟机服务的平台,为每个虚拟机提供标准的 URL,同时支持 HTTPS 和 SSH 访问。用户可以直接使用域名连接虚拟机,就像在浏览器中访问网站一样简单。 ## 2. 问题陈述 exe.dev 采用固定费率订阅模式,为用户提供大量虚拟机。然而,为每台虚拟机分配独立的 IPv4 地址会导致订阅成本大幅上升。同时,仅使用 IPv6 意味着部分用户无法通过 Web 访问虚拟机。 因此,exe.dev 需要在多个虚拟机之间共享 IPv4 地址。对于 Web 访问,这是一个长期存在的解决方案,但 SSH 协议没有 Host header,无法通过域名区分不同的虚拟机。 ## 3. 影响范围 - 所有需要通过 SSH 访问的虚拟机 - 共享 IPv4 地址的场景 - 需要统一域名行为的服务 # 二、问题分析 ## 1. Web 服务的解决方案 Web 服务早已解决了同一 IP 承载多个域名的问题。Web 浏览器在 HTTP 请求中发送访问域名作为 Host header。服务器可以根据这个 header 将请求路由到正确的虚拟机。 ```mermaid graph LR C[客户端] --> P[代理服务器] P -->|Host: vm1.example.com| V1[虚拟机1] P -->|Host: vm2.example.com| V2[虚拟机2] P -->|Host: vm3.example.com| V3[虚拟机3] ```   ## 2. SSH 协议的限制 SSH 协议没有等效于 Host header 的机制。当客户端发起 SSH 连接时,服务器只能看到连接来源的 IP 地址和端口,无法知道客户端使用的是哪个域名。 这意味着如果多个虚拟机共享同一个 IPv4 地址,SSH 代理无法将连接路由到正确的虚拟机。 ## 3. 根本原因 SSH 协议设计之初并未考虑域名虚拟化的需求。它是基于网络层传输的协议,只关注 IP 地址和端口,不关心应用层的域名信息。 # 三、解决方案 ## 1. SSH IP Sharing 架构 exe.dev 采用了一种创新的解决方案:不使用单一 IP 地址承载所有虚拟机,而是维护一个公网 IPv4 地址池。每个虚拟机被分配一个相对于其所有者唯一的地址。 ## 2. DNS 配置方式 传统方式使用 A 记录直接指向 IP 地址,而 exe.dev 使用 CNAME 记录指向子域名: ```bash $ dig undefined-behavior.exe.xyz ; <<>> DiG 9.10.6 <<>> undefined-behavior.exe.xyz ... ;; ANSWER SECTION: undefined-behavior.exe.xyz. 230 IN CNAME s003.exe.xyz. s003.exe.xyz. 230 IN A 16.145.102.7 ``` CNAME 记录 `s003.exe.xyz` 代表一个特定的 IPv4 地址池。虽然这个 IP 地址被多个虚拟机使用,但对于某个特定用户来说,它只被该用户的一台虚拟机使用。 ## 3. 路由机制 SSH 代理使用以下信息来路由连接: - 公钥:识别用户身份 - IP 地址:识别连接来源 `{用户, IP}` 元组唯一标识用户正在连接的虚拟机。 ```mermaid sequenceDiagram participant C as SSH 客户端 participant DNS as DNS 服务器 participant P as SSH 代理 participant V as 目标虚拟机 C->>DNS: 查询 vm.exe.xyz DNS-->>C: 返回 CNAME s003.exe.xyz DNS-->>C: 返回 A 记录 16.145.102.7 C->>P: SSH 连接到 16.145.102.7 P->>P: 获取连接来源 IP C->>P: 发送公钥 P->>P: 根据 {公钥, IP} 元组查找虚拟机 P->>V: 转发连接 V-->>C: SSH 会话建立 ```   ## 4. 系统实现要点 ### A. IP 分配策略 创建虚拟机时,根据拥有虚拟机的用户(或未来的团队)仔细分配 IP 地址。这要求跨系统的通信和协调。 ### B. 连接来源 IP 获取 SSH 代理需要能够确定请求进入的本地 IP 地址。在裸金属环境中这很容易,但在云环境中,公网 IP 通过 NAT 转换为私有 VPC 地址,实现起来更复杂。 ### C. 管理软件 所有这些都需要定制的管理软件,因此 exe.dev 不建议将其作为一般解决方案推荐给希望将虚拟机 SSH 访问复用到单个 IP 的人。 # 四、技术细节 ## 1. 元组查找机制 SSH 代理维护一个查找表,键是 `{用户, IP}` 元组,值是对应的虚拟机。当 SSH 连接到达时: 1. 代理记录连接进入的 IP 地址 2. 读取客户端发送的公钥,识别用户身份 3. 使用 `{用户, IP}` 元组查找目标虚拟机 4. 将连接转发到正确的虚拟机 ## 2. 隔离保证 由于每个 IP 地址相对于用户是唯一的,即使多个虚拟机共享同一个 IP 地址,也不会出现路由冲突: - 用户 A 的虚拟机 1 和虚拟机 2 可能使用同一个 IP 地址(如果他们属于不同用户) - 但用户 A 的所有虚拟机使用不同的 IP 地址 - 这保证了 `{用户, IP}` 元组的唯一性 ## 3. 扩展性考虑 随着用户和虚拟机数量增长,需要合理规划 IP 地址池大小: - 每个用户至少需要足够的唯一 IP 地址来承载其所有虚拟机 - IP 地址池的总大小取决于订阅用户数量和每个用户的虚拟机数量 # 五、经验总结 ## 1. 架构设计启示 - 统一的域名行为对用户体验至关重要 - 当协议本身不支持某些功能时,需要创造性地设计变通方案 - 复杂问题可能需要跨系统的协调和定制化解决方案 ## 2. 实施挑战 - IP 地址池管理需要精心设计 - 云环境中获取真实连接来源 IP 有技术难度 - 定制化管理软件增加了系统复杂度 ## 3. 适用场景 这种方案适用于以下场景: - 提供大量虚拟机服务的平台 - 需要统一域名体验的 SaaS 产品 - 有能力维护定制化基础设施的公司 ## 4. 不适用场景 以下场景不建议采用此方案: - 简单的虚拟机部署 - 缺乏定制化基础设施管理能力 - 不需要统一域名行为的场景 *** ## 参考资料 1. [SSH has no Host header - exe.dev blog](https://blog.exe.dev/ssh-host-header) 最后修改:2026 年 03 月 19 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏