rneter 0.1.5

SSH connection manager for network devices with intelligent state machine handling
Documentation
# rneter

[![Crates.io](https://img.shields.io/crates/v/rneter.svg)](https://crates.io/crates/rneter)
[![Documentation](https://docs.rs/rneter/badge.svg)](https://docs.rs/rneter)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)

[English Documentation](README.md)

`rneter` 是一个用于管理网络设备 SSH 连接的 Rust 库,具有智能状态机处理功能。它提供了高级 API 用于连接网络设备(路由器、交换机等)、执行命令以及管理设备状态,并具备自动提示符检测和模式切换功能。

## 特性

- **连接池管理**:自动缓存和重用 SSH 连接以提高性能
- **状态机管理**:智能设备状态跟踪和自动状态转换
- **提示符检测**:自动识别和处理不同设备类型的提示符
- **模式切换**:在设备模式(用户模式、特权模式、配置模式等)之间无缝转换
- **最大兼容性**:支持广泛的 SSH 算法,包括用于旧设备的传统协议
- **异步/等待**:基于 Tokio 构建,提供高性能异步操作
- **错误处理**:全面的错误类型with详细上下文信息

## 安装

在你的 `Cargo.toml` 中添加:

```toml
[dependencies]
rneter = "0.1"
```

## 快速开始

```rust
use rneter::session::{MANAGER, Command, CmdJob};
use rneter::templates;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // 使用预定义的设备模板(例如:Cisco)
    let handler = templates::cisco()?;

    // 从管理器获取一个连接
    let sender = MANAGER.get(
        "admin".to_string(),
        "192.168.1.1".to_string(),
        22,
        "password".to_string(),
        None,
        handler,
    ).await?;

    // 执行命令
    let (tx, rx) = tokio::sync::oneshot::channel();
    let cmd = CmdJob {
        data: Command {
            mode: "Enable".to_string(), // Cisco 模板使用 "Enable" 模式
            command: "show version".to_string(),
            timeout: Some(60),
        },
        sys: None,
        responder: tx,
    };
    
    sender.send(cmd).await?;
    let output = rx.await??;
    
    println!("命令执行成功: {}", output.success);
    println!("输出: {}", output.content);
    Ok(())
}
```

### 安全级别

`rneter` 现在支持安全默认值,并可在连接时自定义 SSH 安全级别:

```rust
use rneter::session::{ConnectionSecurityOptions, MANAGER};
use rneter::templates;

let handler = templates::cisco()?;

// 默认安全模式(known_hosts 校验 + 严格算法)
let _sender = MANAGER.get(
    "admin".to_string(),
    "192.168.1.1".to_string(),
    22,
    "password".to_string(),
    None,
    handler,
).await?;

// 显式指定安全配置
let _sender = MANAGER.get_with_security(
    "admin".to_string(),
    "192.168.1.1".to_string(),
    22,
    "password".to_string(),
    None,
    templates::cisco()?,
    ConnectionSecurityOptions::legacy_compatible(),
).await?;
```

### 会话录制与回放

```rust
use rneter::session::{MANAGER, SessionRecordLevel, SessionReplayer};
use rneter::templates;

let (sender, recorder) = MANAGER.get_with_recording(
    "admin".to_string(),
    "192.168.1.1".to_string(),
    22,
    "password".to_string(),
    None,
    templates::cisco()?,
).await?;

// 或者仅记录关键事件(不记录原始 shell 分块)
let (_sender2, _recorder2) = MANAGER.get_with_recording_level(
    "admin".to_string(),
    "192.168.1.1".to_string(),
    22,
    "password".to_string(),
    None,
    templates::cisco()?,
    SessionRecordLevel::KeyEventsOnly,
).await?;

// ...通过 `sender` 发送 CmdJob...

// 导出为 JSONL
let jsonl = recorder.to_jsonl()?;

// 恢复并离线回放
let restored = rneter::session::SessionRecorder::from_jsonl(&jsonl)?;
let mut replayer = SessionReplayer::from_recorder(&restored);
let replayed_output = replayer.replay_next("show version")?;
println!("回放输出: {}", replayed_output.content);

// 无需真实 SSH 的离线命令流程测试
let script = vec![
    rneter::session::Command {
        mode: "Enable".to_string(),
        command: "terminal length 0".to_string(),
        timeout: None,
    },
    rneter::session::Command {
        mode: "Enable".to_string(),
        command: "show version".to_string(),
        timeout: None,
    },
];
let outputs = replayer.replay_script(&script)?;
assert_eq!(outputs.len(), 2);
```

对于 CI 的离线测试,可以将 JSONL 录制文件放在 `tests/fixtures/` 下,
并在集成测试中回放(参考 `tests/replay_fixtures.rs`)。

将线上录制归一化为稳定 fixture:

```bash
cargo run --example normalize_fixture -- raw_session.jsonl tests/fixtures/session_new.jsonl
```

### 模板与状态机生态

你可以把内置模板当作注册表管理,并直接对状态图做诊断:

```rust
use rneter::templates;

let names = templates::available_templates();
assert!(names.contains(&"cisco"));

let _handler = templates::by_name("juniper")?; // 大小写不敏感

let report = templates::diagnose_template("cisco")?;
println!("是否存在问题: {}", report.has_issues());
println!("死路状态: {:?}", report.dead_end_states);
println!("不可达的必需模式: {:?}", report.unreachable_required_modes);

let catalog = templates::template_catalog();
println!("模板数量: {}", catalog.len());
let cisco_meta = templates::template_metadata("cisco")?;
println!("cisco 必需模式: {:?}", cisco_meta.required_modes);

let all_json = templates::diagnose_all_templates_json()?;
println!("全部诊断 JSON 字节数: {}", all_json.len());
```

`diagnose_template(name)` 现在会基于模板自身 `required_modes` 元数据来判断
“必需模式是否不可达”,不再使用固定的全局模式数组。

如果你是直接构建 handler,也可以手工指定必需模式:

```rust
let handler = templates::cisco()?;
let report = handler.diagnose_state_machine_with_required_modes(&["login", "enable", "config"]);
assert!(report.unreachable_required_modes.is_empty());
```

新增的录制/回放能力:

- Prompt 前后态:每条 `command_output` 都记录 `prompt_before`/`prompt_after`
- 状态机 prompt 前后态:事件可记录 `fsm_prompt_before`/`fsm_prompt_after`
- 返回值带 prompt:命令执行与离线回放的 `Output` 现在包含 `prompt`
- 兼容旧 schema:历史 `connection_established``prompt`/`state` 字段仍可读取
- fixture 测试工作流:`tests/fixtures/` 提供成功流/失败流/状态切换样本,`tests/replay_fixtures.rs` 提供快照与质量校验

`command_output` 事件结构示例:

```json
{
  "kind": "command_output",
  "command": "show version",
  "mode": "Enable",
  "prompt_before": "router#",
  "prompt_after": "router#",
  "fsm_prompt_before": "enable",
  "fsm_prompt_after": "enable",
  "success": true,
  "content": "Version 1.0",
  "all": "show version\nVersion 1.0\nrouter#"
}
```

## 架构

### 连接管理

`SshConnectionManager` 提供了通过 `MANAGER` 常量访问的单例连接池。它可以自动:
- 缓存连接 5 分钟的不活动时间
- 在连接失败时重新连接
- 管理最多 100 个并发连接

### 状态机

`DeviceHandler` 实现了一个有限状态机:
- 使用正则表达式模式跟踪当前设备状态
- 使用 BFS 算法查找状态之间的最优路径
- 处理自动状态转换
- 支持特定系统状态(例如不同的 VRF 或上下文)

#### 设计思路

这个状态机的设计基于网络设备自动化里的两个稳定事实:
1. 相比命令文本,Prompt 更适合判断当前模式。
2. 不同厂商/型号的模式切换路径不同,路径搜索必须数据驱动。

核心设计选择:
- 状态统一小写,并将 prompt 正则匹配结果映射到状态索引,保证快速定位。
- 将 prompt 检测(`read_prompt`)与状态更新(`read`)拆开,保证命令循环行为可预测。
- 将状态转换建模为有向图(`edges`),通过 BFS 找到最短可行切换路径。
- 将动态输入处理(`read_need_write`)与命令逻辑解耦,复用密码/确认类交互处理。
- 同时记录 CLI prompt 文本与 FSM prompt(状态名),便于在线诊断和离线回放断言。

这样设计的好处:
- 可移植性更好:设备差异主要通过配置表达,而不是硬编码分支。
- 稳定性更好:执行依赖 prompt/状态收敛,而不是脆弱的输出格式假设。
- 可测试性更好:可通过 record/replay 离线验证状态切换与 prompt 演化,不依赖真实 SSH。

#### 状态转换模型

```mermaid
flowchart LR
    O["Output"] --> L["Login Prompt"]
    L -->|enable| E["Enable Prompt"]
    E -->|configure terminal| C["Config Prompt"]
    C -->|exit| E
    E -->|exit| L
    E -->|show ...| E
    C -->|show ... / set ...| C
```

#### 命令执行流程(带状态感知)

```mermaid
flowchart TD
    A["接收命令(mode, command, timeout)"] --> B["读取当前 FSM prompt/state"]
    B --> C["BFS 规划切换路径: trans_state_write(target_mode)"]
    C --> D["按顺序执行切换命令"]
    D --> E["执行目标命令"]
    E --> F["读取流式输出 -> handler.read(line) 更新状态"]
    F --> G{"匹配到 prompt?"}
    G -->|否| F
    G -->|是| H["构建 Output(success, content, all, prompt)"]
    H --> I["记录事件: prompt_before/after + fsm_prompt_before/after"]
```

### 命令执行

命令通过基于异步通道的架构执行:
1. 向连接发送器提交一个 `CmdJob`
2. 库会在需要时自动转换到目标状态
3. 执行命令并等待提示符
4. 返回带有成功状态的输出

## 支持的设备类型

该库旨在与任何支持 SSH 的网络设备配合使用。特别适合:

- Cisco IOS/IOS-XE/IOS-XR 设备
- Juniper JunOS 设备
- Arista EOS 设备
- 华为 VRP 设备
- 通过 SSH 访问的通用 Linux/Unix 系统

## 配置

### SSH 算法支持

`rneter` 在 `config` 模块中包含全面的 SSH 算法支持:
- 密钥交换:Curve25519、DH 组、ECDH
- 加密:AES(CTR/CBC/GCM)、ChaCha20-Poly1305
- MAC:HMAC-SHA1/256/512 及 ETM 变体
- 主机密钥:Ed25519、ECDSA、RSA、DSA(用于旧设备)

这确保了与现代和传统网络设备的最大兼容性。

## 错误处理

该库通过 `ConnectError` 提供详细的错误类型:

- `UnreachableState`:无法从当前状态到达目标状态
- `TargetStateNotExistError`:请求的状态在配置中不存在
- `ChannelDisconnectError`:SSH 通道意外断开
- `ExecTimeout`:命令执行超时
- 等等...

## 文档

详细的 API 文档请访问 [docs.rs/rneter](https://docs.rs/rneter)。

## 许可证

本项目采用 MIT 许可证 - 详情请参阅 [LICENSE](LICENSE) 文件。

## 贡献

欢迎贡献!请随时提交 Pull Request。

## 作者

demohiiiii