Feature: Add host management and connection management MCP tools
Expose the online-list TLS column through list_online_hosts, search_hosts and get_host_detail. The value comes from the flag the client reports at login, so old clients always read false. Add list_network_connections and kill_network_connection on top of the connection manager sub-connection protocol. The kill path matches the original 110-byte record instead of rebuilding addresses, supports a dry run, refuses UDP/IPv6 targets, and verifies that the quadruple really disappeared instead of assuming success. Add a host_* family backed by the machine manager protocol: ten read tools covering every tab of the host management dialog, and four recoverable write tools for process freeze/thaw, window restore/hide/ maximize/minimize, service start/stop/pause/continue/restart, and scheduled task run/stop. Every write re-reads the snapshot before sending and re-checks afterwards, reporting -32009 for stale targets rather than acting blindly. The family is gated by McpMachine, the risky subset by McpMachineDangerous, and writes additionally require read-only mode to be off. Route managed sub-connections from MessageHandle and OfflineProc, keep the single-in-flight rule by sharing the pending table with a request kind, and move the shared JSON/lock/encoding helpers into McpInternal so both tool families use one implementation. Settings dialog, design docs and en_US/zh_TW/de_DE translations are updated accordingly. Co-Authored-By: deepseek-flash
This commit is contained in:
1 parent
6c8336b497
commit
38cd207f5d
17 files changed
+3615
-184
No files matched your search
@@ -0,0 +1,273 @@
|
||||
# MCP P8 设计:在线主机 TLS 字段 / 连接管理 / 主机管理
|
||||
|
||||
> 延续 `Mcp_Design.md` → `Mcp_Phase2_Design.md`(P2)→ P3~P6 → `Mcp_Manage_Design.md`(P7)。
|
||||
> P7 复用的是「系统/窗口/服务」对话框使用的 `COMMAND_SYSTEM`、`COMMAND_WSLIST`、
|
||||
> `COMMAND_SERVICECONFIG` 链路;本轮要覆盖的「连接管理」与「主机管理」走的是**另一套**
|
||||
> 命令(`COMMAND_CONNECTION` / `COMMAND_MACHINE_MANAGE`),必须在同一子连接上收发多步,
|
||||
> 因此新增一层会话抽象,并为工具族增加独立开关。
|
||||
>
|
||||
> 安全门结论(已确认):新增独立 `host_*` 工具族;写入范围限定「只读 + 可恢复操作」;
|
||||
> 开关分两层——`McpMachine`(工具族总开关)与 `McpMachineDangerous`(高风险子集),
|
||||
> 均默认关闭,并与 `McpReadonly` 叠加。现有 27 个工具(含 P7)一律不改行为。
|
||||
|
||||
## 1. 范围
|
||||
|
||||
| 交付 | 内容 |
|
||||
|---|---|
|
||||
| TLS 字段 | `list_online_hosts` / `search_hosts` / `get_host_detail` 增加 `tls` |
|
||||
| 连接管理 | `list_network_connections`、`kill_network_connection` |
|
||||
| 主机管理读 | 10 个标签页各一个:`host_list_processes` … `host_get_hosts_file` |
|
||||
| 主机管理写 | `host_control_process` / `host_control_window` / `host_control_service` / `host_control_task` |
|
||||
| 配置 | 设置对话框两个复选框 + 启动接线(`McpMachine` / `McpMachineDangerous`) |
|
||||
|
||||
不做的(明确排除):kill 进程、关闭窗口、删除服务/任务、卸载软件、写 hosts、
|
||||
创建任务、强制删除文件、代码注入。
|
||||
|
||||
## 2. TLS 字段
|
||||
|
||||
- `HostJson.cpp` 的 `BuildHostJson()` 末尾追加
|
||||
`device["tls"] = (ctx->GetClientData(ONLINELIST_TLS) == "Y");`。
|
||||
数据源是登录时写入的 `ONLINELIST_TLS`(`2015RemoteDlg.cpp` 收到 `RES_TLS=="1"` 时置 `"Y"`)。
|
||||
- 语义:**登录凭据**,不是实时探测。老客户端不上报该值 → `false`。
|
||||
- `BuildHostJson` 同时被 WebService 的设备列表复用,Web 端也会多出该字段(向后兼容)。
|
||||
- `get_host_detail` 复用 `BuildHostItemSchema()`,自动带上。
|
||||
|
||||
## 3. 会话架构
|
||||
|
||||
两条新链路都需要「主连接发一条命令 → 客户端新建子连接 → 子连接回就绪包 →
|
||||
在同一子连接上多次收发」,现有 `m_Pending` 只能等主连接上的单包。做法是给
|
||||
`PendingRequest` 增加 `kind`,**复用同一张表** `m_Pending`(从而天然保证「单设备单飞行」),
|
||||
而不是另开一套状态。
|
||||
|
||||
```cpp
|
||||
enum class PendingKind { Generic, FollowUp, Machine, ConnMgr };
|
||||
```
|
||||
|
||||
| 成员 | 用途 |
|
||||
|---|---|
|
||||
| `m_SubCtxToDevice` | 已绑定的子连接 → device;绑定的子连接上所有包都由 MCP 消费 |
|
||||
| `m_AbsorbedSubCtx` | `CancelIO` 后仍可能到达的包:静默丢弃 |
|
||||
| `m_OrphanByDevice` / `m_OrphanByPeer` | 5 秒墓碑,防止迟到的就绪包弹出 MFC 对话框 |
|
||||
|
||||
### 3.1 时序(工具线程)
|
||||
|
||||
```
|
||||
BeginMachineSession(dev, tool, mainCtx) // 登记单飞行;忙 → -32003
|
||||
→ 主连接 Send2Client(COMMAND_MACHINE_MANAGE)
|
||||
→ 客户端开 CMachineManager 子连接,回 TOKEN_SYSINFOLIST(61)
|
||||
→ OnManagedSubPacket 绑定子连接、保存首包、notify_all
|
||||
WaitManagedPacket(dev, 61, …, 5000) // 就绪
|
||||
|
||||
每次读:
|
||||
ArmManagedResponse(dev, 期望 token)
|
||||
→ 子连接 Send2Client([tab 0..9]) / WaitSendIdle
|
||||
→ WaitManagedPacket(dev, 期望 token, …, 12000)
|
||||
|
||||
EndManagedSession(dev, cancelIo=true) // 关闭子连接,不留长连接
|
||||
```
|
||||
|
||||
整体 60 秒会话截止;每一步取 `min(step, deadline - now)`。所有超时返回 `-32001`。
|
||||
|
||||
### 3.2 子连接路由(`OnManagedSubPacket`)
|
||||
|
||||
在 `3.1` 的模型下,UI 线程收到的每个包先经过钩子
|
||||
(`MessageHandle` 中终端拦截块之后、`switch (cmd)` 之前):
|
||||
|
||||
1. 已绑定 → 只保留 `expectedToken` 匹配的包;`TOKEN_MACHINE_MSG(42)` 存为
|
||||
`clientError`(CP936→UTF-8);其余丢弃。已绑定的子连接上的一切包都归 MCP。
|
||||
2. `m_AbsorbedSubCtx` 命中 → 丢弃。
|
||||
3. 未绑定且 token 不是 `61` / `255` → 交回主 switch(MFC 行为不变)。
|
||||
4. 未绑定且是 `61` / `255`:
|
||||
- 该 context 就是主连接(`FindMainContext(parent, cid)`)→ 交回主 switch;
|
||||
- 否则在同 device 的未绑定会话中按 `clientID` 精确绑定;
|
||||
- 老客户端子连接 `clientID` 可能为 0 → 按 `peer` 兜底,**仅当同 peer 唯一**时才绑定;
|
||||
- 都不命中:墓碑未过期 → 吸收 + `CancelIO` + 消费;否则交回主 switch
|
||||
(用户手动打开对话框)。
|
||||
|
||||
### 3.3 必须同步调整的现有语义
|
||||
|
||||
- `IsPending()` 排除 `Machine` / `ConnMgr`:否则会话在飞时用户点「进程管理」,
|
||||
`case TOKEN_PSLIST` 会把界面包抢走。
|
||||
- `TakeMainResponse()` 顶部对 managed kind 直接返回。
|
||||
- `OfflineProc` 在 `OnManageSubClosed` 之后、`RemoveFromHostList` **之前**调用
|
||||
`OnManagedContextClosed`(否则连接池复用同一 context 指针后,新连接的包会被误吞)。
|
||||
- 锁序:`parent->m_cs` → `m_PendingMutex`;持锁期间禁止 `Send2Client` / `CancelIO` /
|
||||
`WaitSendIdle`。
|
||||
|
||||
### 3.4 健壮性
|
||||
|
||||
- UI 线程只做「有界拷贝 + 保存 + 唤醒」:单包上限 8 MiB,超限置 `clientError` 并 `CancelIO`。
|
||||
- 解析全部在 MCP 工作线程上做;字符串一律用 `BoundedStrlen` / 显式偏移,
|
||||
未对齐的多字节整数用 `memcpy`,禁止指针强转。
|
||||
- 客户端部分列表用 `Send(buf, LocalSize(buf))` 发送,长度可能带出少量尾随 0 填充:
|
||||
记录解析循环遇到「剩余全 0」即结束,定长记录列表忽略不足一条记录的尾部。
|
||||
|
||||
## 4. 连接管理
|
||||
|
||||
### 4.1 协议
|
||||
|
||||
- 主连接:`COMMAND_CONNECTION(253)`;客户端建 `CConnectionsManager` 子连接。
|
||||
- 子连接回 `[255][u16 LE count][ConnectionInfo × count]`。
|
||||
- `ConnectionInfo`(`common/commands.h`,`#pragma pack(1)`,110 字节,已 `static_assert`):
|
||||
`proto(1)` / `af(1)` / `pid(4)` / `localAddr[16]` / `localPort(2,主机序)` /
|
||||
`remoteAddr[16]` / `remotePort(2,主机序)` / `state(4)` / `procName[64](UTF-8)`。
|
||||
地址为网络序;IPv4 取前 4 字节,`af == AF_INET(2)`,IPv6 为 `AF_INET6(23)`。
|
||||
- 强制断开:子连接发 `[254][原始 110 字节记录]`;客户端 `SetTcpEntry(DELETE_TCB)`
|
||||
后自动重发一次 255。
|
||||
|
||||
### 4.2 工具
|
||||
|
||||
| 工具 | 门控 | 说明 |
|
||||
|---|---|---|
|
||||
| `list_network_connections` | `McpMachine` | 过滤 `protocol/family/pid/process/local_port/remote_port/remote_address/state` + 分页;输出 `killable = (proto==1 && af==AF_INET)` |
|
||||
| `kill_network_connection` | `McpMachine && McpManage && !McpReadonly` | 必填四元组;可选 `pid` 消歧、`dry_run` |
|
||||
|
||||
`kill_network_connection` 的流程刻意避免「重新拼地址」:
|
||||
|
||||
1. 取最新列表,**按原始 110 字节记录精确匹配**四元组(可选同时校验 PID);
|
||||
2. 匹配 0 条 → `-32009`(调用方应刷新);匹配多条 → `-32602`(要求给 `pid`);
|
||||
3. 目标不是 IPv4 TCP → `-32005`,不发包;
|
||||
4. `dry_run` → 直接返回目标;
|
||||
5. `Arm(255)` → 发 `[254][原记录]` → 等刷新列表 → 复核该四元组是否消失。
|
||||
|
||||
客户端 `SetTcpEntry` 的返回值被忽略且需要管理员权限,所以「断开后仍在」返回
|
||||
`structuredContent.ok=false, reason="still_present"`,**绝不谎报成功**。
|
||||
每次调用写审计:`tcp a:p->b:q pid=N result=…`。
|
||||
|
||||
## 5. 主机管理
|
||||
|
||||
### 5.1 协议
|
||||
|
||||
- 主连接:`COMMAND_MACHINE_MANAGE(162)` → 客户端 `CMachineManager` 子连接回
|
||||
`TOKEN_SYSINFOLIST(61)`。
|
||||
- 之后在同一子连接上发 **1 字节 tab**(0–9),响应 token 31–42。
|
||||
- 动作 10–30;多数动作客户端会自动回一份刷新后的列表(服务 = 40,任务 = 41),
|
||||
进程冻结/解冻(16/17)与窗口操作(11)**不回包**。
|
||||
|
||||
`MachineManager` 枚举(`common/commands.h`):请求 0 PROCESS / 1 WINDOWS / 2 NETSTATE /
|
||||
3 SOFTWARE / 4 HTML / 5 FAVORITES / 6 WIN32SERVICE / 7 DRIVERSERVICE / 8 TASK / 9 HOSTS;
|
||||
响应 31 PROCESS / 32 WINDOWS / 33 NETSTATE / 34 SOFTWARE / 35 HTML / 36 FAVORITES /
|
||||
37 WIN32SERVICE / 38 DRIVERSERVICE / 39 HOSTS / 40 SERVICE_LIST / 41 TASKLIST / 42 MACHINE_MSG。
|
||||
|
||||
### 5.2 读工具
|
||||
|
||||
| 工具 | tab | 响应 | 主要过滤 |
|
||||
|---|---|---|---|
|
||||
| `host_list_processes` | 0 | 31 | `pid` / `name` / `arch` |
|
||||
| `host_list_windows` | 1 | 32 | `pid` / `title` |
|
||||
| `host_list_network_state` | 2 | 33 | `pid` / `process` / `protocol` / `local_port` / `remote_port` / `state` |
|
||||
| `host_list_software` | 3 | 34 | `name` / `publisher` |
|
||||
| `host_list_browser_history` | 4 | 35 | `title` / `url` |
|
||||
| `host_list_favorites` | 5 | 36 | `name` / `url` |
|
||||
| `host_list_win32_services` | 6 | 40 | `name` / `status` / `start_type` |
|
||||
| `host_list_driver_services` | 7 | 40 | 同上 |
|
||||
| `host_list_scheduled_tasks` | 8 | 41 | `name` / `folder` / `status` |
|
||||
| `host_get_hosts_file` | 9 | 39 | `include_content` / `max_bytes` |
|
||||
|
||||
公共分页 `offset`(默认 0)/ `limit`(默认 100,上限 1000);输出
|
||||
`total`(过滤后)/ `totalAll` / `offset` / `limit` / `hasMore`。
|
||||
|
||||
### 5.3 记录格式(与 `CMachineDlg::Show*` 逐字段对齐)
|
||||
|
||||
| 列表 | 格式 |
|
||||
|---|---|
|
||||
| 进程 31 | `DWORD pid`、`bool is64`、`title[260]`、`exe`+pad、`path`+pad、`DWORD priClass`、`DWORD threads`、`user`+pad、`DWORD workingSetKb`、`DWORD fileSizeKb`;CP936 |
|
||||
| 窗口 32 | `WINDOWSINFO` 定长 1044 字节;**只含可见窗口**;空列表 = 1 字节 |
|
||||
| 网络状态 33 | 进程名、`DWORD pid`、`[TCP]/[UDP]`、`ip:port`、远端、状态,各串 +2;UDP 远端 `*.*.*.*:*`、状态为空;按最后一个 `:` 拆地址 |
|
||||
| 软件 34 | 5 个串各 +2:显示名、发布者、版本、安装日期、卸载命令 |
|
||||
| 浏览记录 35 | 定长 2148:`time[100]` + `title[1024]` + `url[1024]` |
|
||||
| 收藏夹 36 | 2 个串各 +2 |
|
||||
| 服务 40 | 8 个串各 +1 NUL:显示名、描述、状态、启动类型、登录身份、交互式、**服务名**、可执行路径 |
|
||||
| 计划任务 41 | `name/path/exe` 为 UTF-8、`status` 为 GBK,各 +1 NUL;两个 `DATE`;两个 0 字节结束;`folder` 由 `path` 去掉末尾 `"\<name>"` 派生 |
|
||||
| hosts 39 | `[39][文件原文][0]`;有 BOM → UTF-8,否则合法 UTF-8 → UTF-8,否则 CP936 |
|
||||
|
||||
### 5.4 写工具
|
||||
|
||||
统一模式:**先取列表校验目标存在 → 发动作 → 再取列表复核**。目标不在最新快照里
|
||||
返回 `-32009`,绝不盲发。
|
||||
|
||||
| 工具 | action | 额外门控 | 动作包 | 复核 |
|
||||
|---|---|---|---|---|
|
||||
| `host_control_process` | `freeze` / `thaw` | `McpMachineDangerous` | `[16/17][DWORD pid]`(无回包) | 重新取 tab 0:PID 仍在 |
|
||||
| `host_control_window` | `restore/hide/maximize/minimize` | — | `[11][DWORD hwnd][DWORD SW_*]` + 零填充到 20 字节(客户端只读前 9 字节) | 重新取 tab 1:hide 后 hwnd 应消失,其余应仍在 |
|
||||
| `host_control_service` | `start`/`pause`/`continue` | — | `[22/24/25][服务名 CP936][NUL]` | 客户端自动回 40 |
|
||||
| `host_control_service` | `stop`/`restart` | `McpMachineDangerous` | stop = `[23]`;restart = stop → 等 40 → start | 等 40(12 s),状态在期望集合内 |
|
||||
| `host_control_task` | `run` / `stop` | — | `[29/28][folder CP936][NUL][name CP936][NUL]` | 客户端自动回 41:任务仍在 + 当前状态 |
|
||||
|
||||
补充规则:
|
||||
|
||||
- 服务动作前先取一次服务列表(prime),既校验目标,也把客户端 `m_dwServiceType`
|
||||
钉到正确类型(Win32 / 驱动)。
|
||||
- 已是目标状态时不下发动作,返回 `ok=true, changed=false, reason=already_*`。
|
||||
- 冻结/解冻、窗口动作、任务动作**没有回执**:`verified` 只表示「已用动作后的快照复核」,
|
||||
不表示「动作一定生效」。冻结不改变进程存在性,因此只能确认目标仍在。
|
||||
- 写工具输入串(服务名、任务 folder/name)必须能转成 CP936,否则 `-32602`,不发包。
|
||||
- 审计格式:`host <id> <tool>: action=… target=… result=…`。
|
||||
|
||||
## 6. 门控矩阵
|
||||
|
||||
| 配置 | 可见工具 | 直接调用被隐藏的工具 |
|
||||
|---|---|---|
|
||||
| `McpMachine=0`(默认) | 0 个 | `-32006` |
|
||||
| `McpMachine=1, McpReadonly=1` | 11 个读工具(`list_network_connections` + 10 个 `host_list_*` / `host_get_hosts_file`) | 5 个写工具 → `-32006` |
|
||||
| `McpMachine=1, McpReadonly=0, McpManage=1` | 16 个(含 5 个写) | — |
|
||||
| `McpMachine=1, McpReadonly=0, McpManage=0` | 15 个(无 `kill_network_connection`) | `kill_network_connection` → `-32006` |
|
||||
| `McpMachineDangerous=0` | 工具仍可见 | `host_control_process`、`host_control_service{stop,restart}` → `-32006` |
|
||||
|
||||
设置对话框勾选「危险主机操作」但未勾「主机管理」时,保存会清除危险开关并提示。
|
||||
|
||||
## 7. 错误码
|
||||
|
||||
沿用 P1–P7 的约定,新增 `-32009`:
|
||||
|
||||
| 码 | 含义 |
|
||||
|---|---|
|
||||
| `-32602` | 参数非法(含:目标多条匹配、字符无法用 CP936 表示) |
|
||||
| `-32000` | 解析失败 / 客户端返回错误文本(`TOKEN_MACHINE_MSG`) |
|
||||
| `-32001` | 超时(就绪 5 s / 列表 12 s / 会话 60 s) |
|
||||
| `-32002` | 主机离线 |
|
||||
| `-32003` | 设备忙(已有 MCP 会话在飞:单设备单飞行) |
|
||||
| `-32004` | 发送失败 |
|
||||
| `-32005` | 不支持(非 Windows 客户端;UDP/IPv6 断连) |
|
||||
| `-32006` | 开关未开启 |
|
||||
| `-32009` | **目标在最新快照中不存在**(调用方应刷新后重试) |
|
||||
|
||||
## 8. 已知限制
|
||||
|
||||
1. **token 数值碰撞**:机器响应 31–42 与 `COMMAND_KEYBOARD/AUDIO/SYSTEM/SHELL/…` 同值。
|
||||
靠「先按子连接身份路由」解决——未绑定的子连接不会按 token 猜测。
|
||||
2. **UI 争抢就绪包**:用户恰好在 MCP 请求期间点开同一对话框时,首包可能被 MCP 绑定,
|
||||
界面对话框这次不弹(与 P7 的取舍一致),重试即可。
|
||||
3. **5 s 墓碑**:MCP 异常结束后 5 s 内手动打开同一台主机的对话框可能被吞,重试即可。
|
||||
4. **老客户端子连接 `clientID=0`**:靠 peer 兜底,仅在同 peer 唯一会话时绑定。
|
||||
5. **权限**:`SetTcpEntry`、冻结/解冻、服务控制需要客户端管理员;失败按「未生效」如实返回。
|
||||
6. **编码**:机器协议以 CP936 为主;任务 `name/path/exe` 为 UTF-8、`status` 为 GBK;
|
||||
写回输入统一转 CP936。GBK 不可表示的字符明确报 `-32602`。
|
||||
7. **`host_list_network_state` 只有 IPv4**:数据来自客户端自身的 `GetTcpTable` 视图,
|
||||
与「连接管理」(`ConnectionInfo`,含 IPv6)不是同一份数据。
|
||||
8. **`McpMachineDangerous` 只覆盖本轮高风险动作**;后续加入 kill/删除/卸载/注入时,
|
||||
必须按 `McpMachine && McpMachineDangerous && !McpReadonly` 三层校验。
|
||||
9. **`host_control_window {action:"hide"}` 是单向的**:客户端只枚举可见窗口,隐藏后该 hwnd
|
||||
从快照中消失,再发 `restore` 会在发包前被 `-32009` 拦下(MFC 对话框同样无法再操作它)。
|
||||
实测确认:`hide` 复核为 `state:"hidden"`,随后 `restore` 返回 `-32009`。
|
||||
10. **`host_control_service {action:"pause"}` 依赖服务自身支持 PAUSE**:实测虚拟机上的
|
||||
`Spooler` 下发 PAUSE 后状态仍为 `Running`,工具如实返回 `ok:false, reason:"unexpected_state"`
|
||||
(不谎报成功)。`start/stop/continue/restart` 在同一台机器上均通过。
|
||||
11. **开关只在进程启动时读取**(Release 版配置来自注册表 `HKCU\Software\YAMA\settings`,
|
||||
不是 `Bin\settings.ini`);在「MCP设置」里改动 `McpMachine` / `McpMachineDangerous` /
|
||||
`McpReadonly` 等必须重启服务端才生效。未重启时 `tools/list` 里看不到新工具,
|
||||
直接调用返回 `-32006`。
|
||||
|
||||
## 9. 验证要点
|
||||
|
||||
- TLS:`RES_TLS=1` 的主机三个工具都返回 `tls:true`;老客户端 `false`。
|
||||
- 路由:MCP 关闭时手动打开「主机管理」「连接管理」照常工作;
|
||||
`host_list_favorites`(token 36,与键盘等命令同值)在飞时手动打开「进程管理」,
|
||||
两边都不串包。
|
||||
- 迟到包:强制就绪超时后,61/255 到达应被吸收且不弹对话框。
|
||||
- 单飞行:同主机并发两个 host/connection 工具,第二个 `-32003`;两台主机并发都成功。
|
||||
- 连接管理:与对话框逐条对比;断开后复核消失;UDP/IPv6 → `-32005`;过期四元组 → `-32009`。
|
||||
- 10 个读工具:与 MFC 标签页对比行数、首末行、中文编码;空 tab 返回 `total:0`。
|
||||
- 4 个写工具:freeze→thaw、窗口四动作、服务五动作、任务 run/stop;过期目标发包前 `-32009`。
|
||||
- 回归:现有 27 个工具、终端、远程控制、文件传输、10 个 MFC 标签页行为不变。
|
||||
- 资源:连续调用 20 次 host 工具,服务端无残留子连接。
|
||||
Reference in new issue
Block a user