network-infra-utility 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -0
  3. data/Gemfile +2 -0
  4. data/Gemfile.lock +70 -0
  5. data/Rakefile +1 -1
  6. data/bin/dns-query +834 -0
  7. data/bin/geo-doc +135 -0
  8. data/bin/geo-get +1 -1
  9. data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
  10. data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
  11. data/document/Geo/345/221/275/344/273/244/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +441 -0
  12. data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
  13. data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
  14. data/document/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/344/275/277/347/224/250/346/226/271/346/263/225.md +764 -0
  15. data/network-infra-utility.gemspec +4 -2
  16. data/network.rb +3 -1
  17. data/service/geodb/geodb.rb +278 -1
  18. data/service/ssh/README.md +942 -0
  19. data/service/ssh/bin/ssh-client +198 -0
  20. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/345/212/237/350/203/275/351/234/200/346/261/202/346/226/207/346/241/243.md +292 -0
  21. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/257/246/347/273/206/350/256/276/350/256/241/346/226/207/346/241/243.md +1521 -0
  22. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/275/257/344/273/266/350/256/276/350/256/241/346/226/207/346/241/243.md +2493 -0
  23. data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
  24. data/service/ssh/ext/ssh_core/config/sys.config +0 -0
  25. data/service/ssh/ext/ssh_core/config/vm.args +0 -0
  26. data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
  27. data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
  28. data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
  29. data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
  30. data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
  31. data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
  32. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
  33. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
  34. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
  35. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
  36. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
  37. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
  38. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
  39. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
  40. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
  41. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
  42. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
  43. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
  44. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
  45. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
  46. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
  47. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
  48. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
  49. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
  50. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
  51. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
  52. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
  53. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
  54. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
  55. data/service/ssh/ext/ssh_core/rebar.config +24 -0
  56. data/service/ssh/ext/ssh_core/rebar.lock +1 -0
  57. data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
  58. data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
  59. data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
  60. data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
  61. data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
  62. data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
  63. data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
  64. data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
  65. data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +104 -0
  66. data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
  67. data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
  68. data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
  69. data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
  70. data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
  71. data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
  72. data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
  73. data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
  74. data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
  75. data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
  76. data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
  77. data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
  78. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
  79. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
  80. data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
  81. data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
  82. data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
  83. data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
  84. data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
  85. data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
  86. data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
  87. data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
  88. data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
  89. data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
  90. data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
  91. data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
  92. data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
  93. data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
  94. data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
  95. data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
  96. data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
  97. data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
  98. data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
  99. data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
  100. data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
  101. data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
  102. data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
  103. data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
  104. data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
  105. data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
  106. data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
  107. data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
  108. data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
  109. data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
  110. data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
  111. data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
  112. data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
  113. data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
  114. data/support/basic/as_num.rb +221 -0
  115. data/support/basic/mac_address.rb +281 -0
  116. data/version.rb +1 -1
  117. metadata +138 -1
@@ -0,0 +1,389 @@
1
+ //! IPC 网关 — TCP 监听、JSON-RPC 路由、推送、反向 RPC。
2
+ //! 对应 Erlang 端的 ssh_ipc_gateway.erl。
3
+ //!
4
+ //! 核心设计(与 Erlang 端完全对齐):
5
+ //! - 监听 127.0.0.1 随机端口,写入端点文件供 Ruby 读取
6
+ //! - hello 握手认证(auth_token)
7
+ //! - 路由表:method → 处理函数
8
+ //! - 推送:push_event / push_batch(notification,无 id)
9
+ //! - 反向 RPC:synchronous_push(Rust→Ruby 带 id 的请求,阻塞等响应)
10
+
11
+ use std::collections::HashMap;
12
+ use std::path::PathBuf;
13
+ use std::sync::atomic::{AtomicI64, Ordering};
14
+ use std::sync::Arc;
15
+ use std::time::Duration;
16
+
17
+ use serde_json::{json, Value};
18
+ use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
19
+ use tokio::net::{TcpListener, TcpStream};
20
+ use tokio::sync::{oneshot, Mutex, RwLock};
21
+ use tracing::{debug, error, info, warn};
22
+
23
+ use crate::proto;
24
+
25
+ /// RPC 处理函数类型:接收 params,返回 Result<Value, String>
26
+ type RpcHandler = Arc<
27
+ dyn Fn(Value) -> std::pin::Pin<Box<dyn std::future::Future<Output = Result<Value, String>> + Send>>
28
+ + Send
29
+ + Sync,
30
+ >;
31
+
32
+ /// 反向 RPC 待处理条目
33
+ struct ReversePending {
34
+ tx: oneshot::Sender<Result<Value, String>>,
35
+ }
36
+
37
+ /// 客户端连接信息
38
+ struct ClientInfo {
39
+ writer_tx: tokio::sync::mpsc::UnboundedSender<Vec<u8>>,
40
+ authed: bool,
41
+ reader_handle: Option<tokio::task::JoinHandle<()>>,
42
+ }
43
+
44
+ /// 网关共享状态
45
+ pub struct Gateway {
46
+ auth_token: String,
47
+ routes: RwLock<HashMap<String, RpcHandler>>,
48
+ clients: RwLock<Vec<Arc<Mutex<ClientInfo>>>>,
49
+ reverse_pending: Mutex<HashMap<i64, ReversePending>>,
50
+ next_reverse_id: AtomicI64,
51
+ }
52
+
53
+ impl Gateway {
54
+ /// 创建网关实例
55
+ pub fn new(auth_token: String) -> Arc<Self> {
56
+ Arc::new(Self {
57
+ auth_token,
58
+ routes: RwLock::new(HashMap::new()),
59
+ clients: RwLock::new(Vec::new()),
60
+ reverse_pending: Mutex::new(HashMap::new()),
61
+ next_reverse_id: AtomicI64::new(1000000),
62
+ })
63
+ }
64
+
65
+ /// 注册 RPC 路由
66
+ pub async fn register_route<F, Fut>(&self, method: &str, handler: F)
67
+ where
68
+ F: Fn(Value) -> Fut + Send + Sync + 'static,
69
+ Fut: std::future::Future<Output = Result<Value, String>> + Send + 'static,
70
+ {
71
+ let handler_arc: RpcHandler = Arc::new(move |params| {
72
+ let f = handler(params);
73
+ Box::pin(f)
74
+ });
75
+ self.routes.write().await.insert(method.to_string(), handler_arc);
76
+ }
77
+
78
+ /// 启动 TCP 监听并写入端点文件
79
+ pub async fn start(self: Arc<Self>, endpoint_file: PathBuf) -> Result<(), String> {
80
+ // 监听 127.0.0.1:0(OS 分配端口)
81
+ let listener = TcpListener::bind("127.0.0.1:0")
82
+ .await
83
+ .map_err(|e| format!("bind failed: {}", e))?;
84
+
85
+ let port = listener
86
+ .local_addr()
87
+ .map_err(|e| format!("local_addr failed: {}", e))?
88
+ .port();
89
+
90
+ // 写入端点文件:第一行 tcp://127.0.0.1:<port>,第二行 auth_token
91
+ let endpoint_content = format!("tcp://127.0.0.1:{}\n{}", port, self.auth_token);
92
+ std::fs::write(&endpoint_file, &endpoint_content)
93
+ .map_err(|e| format!("write endpoint file failed: {}", e))?;
94
+
95
+ // 设置文件权限(Unix: 600)
96
+ #[cfg(unix)]
97
+ {
98
+ use std::os::unix::fs::PermissionsExt;
99
+ let mut perms = std::fs::metadata(&endpoint_file)
100
+ .map_err(|e| format!("metadata failed: {}", e))?
101
+ .permissions();
102
+ perms.set_mode(0o600);
103
+ let _ = std::fs::set_permissions(&endpoint_file, perms);
104
+ }
105
+
106
+ info!("IPC gateway listening on 127.0.0.1:{}", port);
107
+ info!("endpoint file: {}", endpoint_file.display());
108
+
109
+ // 接受连接循环
110
+ loop {
111
+ match listener.accept().await {
112
+ Ok((stream, addr)) => {
113
+ debug!("new client connection from {}", addr);
114
+ let gw = Arc::clone(&self);
115
+ tokio::spawn(async move {
116
+ gw.handle_client(stream).await;
117
+ });
118
+ }
119
+ Err(e) => {
120
+ error!("accept failed: {}", e);
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ /// 处理单个客户端连接
127
+ async fn handle_client(self: Arc<Self>, stream: TcpStream) {
128
+ let (reader, mut writer) = stream.into_split();
129
+
130
+ // 创建写通道
131
+ let (writer_tx, mut writer_rx) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
132
+
133
+ let client = Arc::new(Mutex::new(ClientInfo {
134
+ writer_tx: writer_tx.clone(),
135
+ authed: false,
136
+ reader_handle: None,
137
+ }));
138
+
139
+ // 注册客户端
140
+ self.clients.write().await.push(Arc::clone(&client));
141
+
142
+ // 启动写任务
143
+ let write_task = tokio::spawn(async move {
144
+ while let Some(data) = writer_rx.recv().await {
145
+ if writer.write_all(&data).await.is_err() {
146
+ break;
147
+ }
148
+ }
149
+ });
150
+
151
+ // 读取循环
152
+ let buf_reader = BufReader::new(reader);
153
+ let mut lines = buf_reader.lines();
154
+ let mut leftover = String::new();
155
+
156
+ loop {
157
+ match lines.next_line().await {
158
+ Ok(Some(line)) => {
159
+ let frame = if leftover.is_empty() {
160
+ line
161
+ } else {
162
+ format!("{}{}", leftover, line)
163
+ };
164
+ leftover.clear();
165
+
166
+ if frame.is_empty() {
167
+ continue;
168
+ }
169
+
170
+ // 检查消息大小
171
+ if frame.len() > proto::MAX_MSG_SIZE {
172
+ warn!("message too large ({} bytes), discarding", frame.len());
173
+ continue;
174
+ }
175
+
176
+ let msg = match proto::decode(frame.as_bytes()) {
177
+ Ok(m) => m,
178
+ Err(e) => {
179
+ warn!("decode error: {}", e);
180
+ continue;
181
+ }
182
+ };
183
+
184
+ self.handle_message(msg, &client).await;
185
+ }
186
+ Ok(None) => {
187
+ // EOF
188
+ debug!("client disconnected");
189
+ break;
190
+ }
191
+ Err(e) => {
192
+ debug!("read error: {}", e);
193
+ break;
194
+ }
195
+ }
196
+ }
197
+
198
+ // 清理:从客户端列表移除
199
+ self.clients.write().await.retain(|c| {
200
+ !Arc::ptr_eq(c, &client)
201
+ });
202
+
203
+ write_task.abort();
204
+ }
205
+
206
+ /// 处理收到的消息
207
+ async fn handle_message(&self, msg: proto::Message, client: &Arc<Mutex<ClientInfo>>) {
208
+ match msg {
209
+ proto::Message::Request { id, method, params } => {
210
+ if method == "hello" {
211
+ // hello 握手认证
212
+ let result = self.handle_hello(&params).await;
213
+ match result {
214
+ Ok(resp) => {
215
+ // 认证成功
216
+ client.lock().await.authed = true;
217
+ self.send_response(id, &resp, client).await;
218
+ }
219
+ Err(e) => {
220
+ self.send_error(id, -32001, &e, client).await;
221
+ }
222
+ }
223
+ return;
224
+ }
225
+
226
+ // 检查是否已认证
227
+ if !client.lock().await.authed {
228
+ self.send_error(id, -32000, "not authenticated", client).await;
229
+ return;
230
+ }
231
+
232
+ // 路由到处理函数
233
+ let handler = self.routes.read().await.get(&method).cloned();
234
+ match handler {
235
+ Some(h) => {
236
+ let client_clone = Arc::clone(client);
237
+ let id_clone = id;
238
+ let self_clone = Arc::new(()); // 不需要 self,直接用闭包
239
+ tokio::spawn(async move {
240
+ let _ = self_clone; // 消除警告
241
+ match h(params).await {
242
+ Ok(result) => {
243
+ client_clone
244
+ .lock()
245
+ .await
246
+ .writer_tx
247
+ .send(proto::frame(&proto::encode_response(id_clone, &result)))
248
+ .ok();
249
+ }
250
+ Err(e) => {
251
+ client_clone
252
+ .lock()
253
+ .await
254
+ .writer_tx
255
+ .send(proto::frame(&proto::encode_error(id_clone, -32603, &e)))
256
+ .ok();
257
+ }
258
+ }
259
+ });
260
+ }
261
+ None => {
262
+ self.send_error(id, -32601, &format!("method not found: {}", method), client)
263
+ .await;
264
+ }
265
+ }
266
+ }
267
+ proto::Message::Response { id, result, error } => {
268
+ // 反向 RPC 的响应
269
+ let mut pending = self.reverse_pending.lock().await;
270
+ if let Some(entry) = pending.remove(&id) {
271
+ if let Some(err) = error {
272
+ let _ = entry.tx.send(Err(err.to_string()));
273
+ } else {
274
+ let _ = entry.tx.send(Ok(result.unwrap_or(Value::Null)));
275
+ }
276
+ }
277
+ }
278
+ proto::Message::Notification { .. } => {
279
+ // Ruby→Rust 的通知,目前忽略
280
+ debug!("received notification from Ruby (ignored)");
281
+ }
282
+ }
283
+ }
284
+
285
+ /// 处理 hello 握手
286
+ async fn handle_hello(&self, params: &Value) -> Result<Value, String> {
287
+ let token = proto::get_str(params, "auth_token").unwrap_or("");
288
+ if token != self.auth_token {
289
+ return Err("auth_failed".to_string());
290
+ }
291
+
292
+ Ok(json!({
293
+ "capabilities": ["coalesce"],
294
+ "ver": proto::PROTOCOL_VER
295
+ }))
296
+ }
297
+
298
+ /// 发送响应给客户端
299
+ async fn send_response(&self, id: i64, result: &Value, client: &Arc<Mutex<ClientInfo>>) {
300
+ let data = proto::frame(&proto::encode_response(id, result));
301
+ let _ = client.lock().await.writer_tx.send(data);
302
+ }
303
+
304
+ /// 发送错误给客户端
305
+ async fn send_error(&self, id: i64, code: i64, message: &str, client: &Arc<Mutex<ClientInfo>>) {
306
+ let data = proto::frame(&proto::encode_error(id, code, message));
307
+ let _ = client.lock().await.writer_tx.send(data);
308
+ }
309
+
310
+ /// 推送通知给所有已认证客户端
311
+ pub async fn push_event(&self, method: &str, params: Value) {
312
+ let data = proto::frame(&proto::encode_push(method, &params));
313
+ let clients = self.clients.read().await;
314
+ for client in clients.iter() {
315
+ let c = client.lock().await;
316
+ if c.authed {
317
+ let _ = c.writer_tx.send(data.clone());
318
+ }
319
+ }
320
+ }
321
+
322
+ /// 推送批量通知(channel.data.batch)
323
+ pub async fn push_batch(&self, params: Value) {
324
+ self.push_event("channel.data.batch", params).await;
325
+ }
326
+
327
+ /// 同步反向 RPC(Rust→Ruby):发送带 id 的请求,阻塞等响应。
328
+ /// 用于 hostkey.resolve。
329
+ pub async fn synchronous_push(
330
+ &self,
331
+ method: &str,
332
+ params: Value,
333
+ timeout_ms: u64,
334
+ ) -> Result<Value, String> {
335
+ let id = self.next_reverse_id.fetch_add(1, Ordering::SeqCst);
336
+
337
+ let (tx, rx) = oneshot::channel();
338
+ self.reverse_pending
339
+ .lock()
340
+ .await
341
+ .insert(id, ReversePending { tx });
342
+
343
+ // 构造反向 RPC 请求消息(有 id 和 method)
344
+ let msg = json!({
345
+ "jsonrpc": "2.0",
346
+ "id": id,
347
+ "method": method,
348
+ "params": params
349
+ });
350
+ let data = proto::frame(&msg.to_string());
351
+
352
+ // 发送给所有已认证客户端
353
+ let sent = {
354
+ let clients = self.clients.read().await;
355
+ let mut sent = false;
356
+ for client in clients.iter() {
357
+ let c = client.lock().await;
358
+ if c.authed {
359
+ let _ = c.writer_tx.send(data.clone());
360
+ sent = true;
361
+ }
362
+ }
363
+ sent
364
+ };
365
+
366
+ if !sent {
367
+ self.reverse_pending.lock().await.remove(&id);
368
+ return Err("no_clients".to_string());
369
+ }
370
+
371
+ // 等待响应或超时
372
+ match tokio::time::timeout(Duration::from_millis(timeout_ms), rx).await {
373
+ Ok(Ok(result)) => result,
374
+ Ok(Err(_)) => {
375
+ self.reverse_pending.lock().await.remove(&id);
376
+ Err("channel closed".to_string())
377
+ }
378
+ Err(_) => {
379
+ self.reverse_pending.lock().await.remove(&id);
380
+ Err("timeout".to_string())
381
+ }
382
+ }
383
+ }
384
+
385
+ /// 获取已连接客户端数
386
+ pub async fn client_count(&self) -> usize {
387
+ self.clients.read().await.len()
388
+ }
389
+ }
@@ -0,0 +1,293 @@
1
+ //! russh Handler 实现 — SSH 协议事件回调,桥接到 IPC 推送。
2
+ //! 对应 dssh/src/handler.rs + Erlang 端 channel_stm 的数据推送逻辑。
3
+ //!
4
+ //! 架构说明:
5
+ //! russh 0.61.2 的 Handler trait 使用 `&mut self`(引用),方法返回
6
+ //! `Result<(), Self::Error>`。handler 持有 Arc<ConnShared>,通过共享状态
7
+ //! 将通道数据推送到 channel.rs 的接收循环,将连接事件推送到 conn.rs 的事件循环。
8
+
9
+ use std::collections::HashMap;
10
+ use std::future::Future;
11
+ use std::sync::Arc;
12
+
13
+ use russh::client;
14
+ use russh::keys::HashAlg;
15
+ use russh::keys::PublicKey;
16
+ use russh::ChannelId;
17
+ use serde_json::json;
18
+ use tokio::sync::{mpsc, RwLock};
19
+ use tracing::{debug, warn};
20
+
21
+ use crate::gateway::Gateway;
22
+
23
+ /// 连接级事件(从 handler 转发到连接管理器的事件循环)
24
+ #[derive(Debug)]
25
+ pub enum ConnEvent {
26
+ /// 连接被远端关闭
27
+ Disconnected,
28
+ }
29
+
30
+ /// 单个通道的数据接收器。
31
+ /// channel.rs 在 open 时通过 register_channel_tx 注册,
32
+ /// handler 的 data/extended_data 回调通过此 tx 推送数据到 channel.rs 的接收循环。
33
+ pub type ChannelDataTx = mpsc::UnboundedSender<ChannelData>;
34
+
35
+ /// 来自 SSH 通道的数据消息
36
+ #[derive(Debug)]
37
+ pub enum ChannelData {
38
+ /// 标准输出数据
39
+ Stdout { data: Vec<u8> },
40
+ /// 扩展数据(stderr 等)
41
+ Extended { data: Vec<u8>, ext: u32 },
42
+ /// 通道 EOF/关闭
43
+ Eof { reason: String },
44
+ }
45
+
46
+ /// 连接级共享状态——由 handler 持有 Arc,在 &mut self 回调中共享
47
+ pub struct ConnShared {
48
+ /// conn_id(用于推送和日志)
49
+ pub conn_id: String,
50
+ /// 主机地址(用于 hostkey.resolve)
51
+ pub host: String,
52
+ /// 端口(用于 hostkey.resolve)
53
+ pub port: u16,
54
+ /// IPC 网关(用于 check_server_key 反向 RPC)
55
+ pub gateway: Arc<Gateway>,
56
+ /// 通道数据发送器映射:russh ChannelId → channel.rs 的接收端
57
+ channel_txs: RwLock<HashMap<ChannelId, ChannelDataTx>>,
58
+ /// 连接级事件发送器(连接关闭等)
59
+ conn_event_tx: mpsc::UnboundedSender<ConnEvent>,
60
+ /// 服务器主机密钥指纹(check_server_key 回调中写入,authenticate 中读取)
61
+ server_fingerprint: RwLock<Option<String>>,
62
+ }
63
+
64
+ impl ConnShared {
65
+ pub fn new(
66
+ conn_id: String,
67
+ host: String,
68
+ port: u16,
69
+ gateway: Arc<Gateway>,
70
+ conn_event_tx: mpsc::UnboundedSender<ConnEvent>,
71
+ ) -> Arc<Self> {
72
+ Arc::new(Self {
73
+ conn_id,
74
+ host,
75
+ port,
76
+ gateway,
77
+ channel_txs: RwLock::new(HashMap::new()),
78
+ conn_event_tx,
79
+ server_fingerprint: RwLock::new(None),
80
+ })
81
+ }
82
+
83
+ /// 注册通道数据发送器(channel.rs 在 channel.open 时调用)
84
+ pub async fn register_channel_tx(&self, russh_ch_id: ChannelId, tx: ChannelDataTx) {
85
+ self.channel_txs.write().await.insert(russh_ch_id, tx);
86
+ }
87
+
88
+ /// 注销通道数据发送器(channel.rs 在 channel.close 时调用)
89
+ pub async fn unregister_channel_tx(&self, russh_ch_id: ChannelId) {
90
+ self.channel_txs.write().await.remove(&russh_ch_id);
91
+ }
92
+
93
+ /// 向指定通道推送数据
94
+ async fn send_channel_data(&self, channel: ChannelId, msg: ChannelData) {
95
+ let txs = self.channel_txs.read().await;
96
+ if let Some(tx) = txs.get(&channel) {
97
+ if tx.send(msg).is_err() {
98
+ debug!("conn {} channel {}: data rx dropped", self.conn_id, channel);
99
+ }
100
+ }
101
+ }
102
+
103
+ /// 获取服务器指纹(authenticate 完成后可读取)
104
+ pub async fn get_server_fingerprint(&self) -> Option<String> {
105
+ self.server_fingerprint.read().await.clone()
106
+ }
107
+ }
108
+
109
+ /// russh Handler 实现
110
+ pub struct SshHandler {
111
+ /// 共享状态 Arc——在 &mut self 回调中共享
112
+ pub shared: Arc<ConnShared>,
113
+ }
114
+
115
+ impl SshHandler {
116
+ pub fn new(shared: Arc<ConnShared>) -> Self {
117
+ Self { shared }
118
+ }
119
+ }
120
+
121
+ /// server key 验证超时(毫秒)
122
+ const HOSTKEY_RESOLVE_TIMEOUT_MS: u64 = 30_000;
123
+
124
+ /// russh 0.61.2 Handler trait 使用原生 AFIT(`fn -> impl Future` 返回类型)。
125
+ /// 不启用 async-trait 特性,方法用 `fn -> impl Future { async { ... } }` 风格实现。
126
+ impl client::Handler for SshHandler {
127
+ type Error = russh::Error;
128
+
129
+ fn check_server_key(
130
+ &mut self,
131
+ server_public_key: &PublicKey,
132
+ ) -> impl Future<Output = Result<bool, Self::Error>> + Send {
133
+ async {
134
+ let fingerprint = server_public_key
135
+ .fingerprint(HashAlg::Sha256)
136
+ .to_string();
137
+ debug!(
138
+ "conn {}: server key fingerprint: {}",
139
+ self.shared.conn_id, fingerprint
140
+ );
141
+
142
+ // 存储指纹到共享状态(authenticate 完成后读取返回给 Ruby)
143
+ *self.shared.server_fingerprint.write().await = Some(fingerprint.clone());
144
+
145
+ // 通过反向 RPC 询问 Ruby 是否接受此主机密钥
146
+ let resp = self
147
+ .shared
148
+ .gateway
149
+ .synchronous_push(
150
+ "hostkey.resolve",
151
+ json!({
152
+ "host": self.shared.host,
153
+ "port": self.shared.port,
154
+ "fingerprint": fingerprint,
155
+ }),
156
+ HOSTKEY_RESOLVE_TIMEOUT_MS,
157
+ )
158
+ .await;
159
+
160
+ let accepted = match resp {
161
+ Ok(result) => {
162
+ let action = result
163
+ .get("action")
164
+ .and_then(|v| v.as_str())
165
+ .unwrap_or("reject");
166
+ match action {
167
+ "accept" => true,
168
+ "once" => true,
169
+ "reject" => false,
170
+ _ => {
171
+ warn!(
172
+ "conn {}: hostkey.resolve returned unknown action: {}, reject",
173
+ self.shared.conn_id, action
174
+ );
175
+ false
176
+ }
177
+ }
178
+ }
179
+ Err(e) => {
180
+ warn!(
181
+ "conn {}: hostkey.resolve failed: {}, reject (safe default)",
182
+ self.shared.conn_id, e
183
+ );
184
+ false
185
+ }
186
+ };
187
+
188
+ if !accepted {
189
+ warn!(
190
+ "conn {}: server key rejected by Ruby",
191
+ self.shared.conn_id
192
+ );
193
+ }
194
+ Ok(accepted)
195
+ }
196
+ }
197
+
198
+ fn data(
199
+ &mut self,
200
+ channel: ChannelId,
201
+ data: &[u8],
202
+ _session: &mut client::Session,
203
+ ) -> impl Future<Output = Result<(), Self::Error>> + Send {
204
+ async move {
205
+ tracing::trace!(
206
+ "conn {} channel {}: {} bytes data",
207
+ self.shared.conn_id,
208
+ channel,
209
+ data.len()
210
+ );
211
+ self.shared
212
+ .send_channel_data(
213
+ channel,
214
+ ChannelData::Stdout { data: data.to_vec() },
215
+ )
216
+ .await;
217
+ Ok(())
218
+ }
219
+ }
220
+
221
+ fn extended_data(
222
+ &mut self,
223
+ channel: ChannelId,
224
+ ext: u32,
225
+ data: &[u8],
226
+ _session: &mut client::Session,
227
+ ) -> impl Future<Output = Result<(), Self::Error>> + Send {
228
+ async move {
229
+ tracing::trace!(
230
+ "conn {} channel {}: {} bytes extended data (ext={})",
231
+ self.shared.conn_id,
232
+ channel,
233
+ data.len(),
234
+ ext
235
+ );
236
+ self.shared
237
+ .send_channel_data(
238
+ channel,
239
+ ChannelData::Extended { data: data.to_vec(), ext },
240
+ )
241
+ .await;
242
+ Ok(())
243
+ }
244
+ }
245
+
246
+ fn channel_close(
247
+ &mut self,
248
+ channel: ChannelId,
249
+ _session: &mut client::Session,
250
+ ) -> impl Future<Output = Result<(), Self::Error>> + Send {
251
+ async move {
252
+ debug!("conn {} channel {}: close", self.shared.conn_id, channel);
253
+ self.shared
254
+ .send_channel_data(
255
+ channel,
256
+ ChannelData::Eof { reason: "channel_closed".to_string() },
257
+ )
258
+ .await;
259
+ Ok(())
260
+ }
261
+ }
262
+
263
+ fn channel_eof(
264
+ &mut self,
265
+ channel: ChannelId,
266
+ _session: &mut client::Session,
267
+ ) -> impl Future<Output = Result<(), Self::Error>> + Send {
268
+ async move {
269
+ debug!("conn {} channel {}: eof", self.shared.conn_id, channel);
270
+ self.shared
271
+ .send_channel_data(
272
+ channel,
273
+ ChannelData::Eof { reason: "eof".to_string() },
274
+ )
275
+ .await;
276
+ Ok(())
277
+ }
278
+ }
279
+
280
+ fn disconnected(
281
+ &mut self,
282
+ reason: russh::client::DisconnectReason<Self::Error>,
283
+ ) -> impl Future<Output = Result<(), Self::Error>> + Send {
284
+ async move {
285
+ warn!("conn {} disconnected by remote: {:?}", self.shared.conn_id, reason);
286
+ let _ = self.shared.conn_event_tx.send(ConnEvent::Disconnected);
287
+ match reason {
288
+ russh::client::DisconnectReason::ReceivedDisconnect(_) => Ok(()),
289
+ russh::client::DisconnectReason::Error(e) => Err(e),
290
+ }
291
+ }
292
+ }
293
+ }