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.
- checksums.yaml +4 -4
- data/.gitignore +21 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +70 -0
- data/Rakefile +1 -1
- data/bin/dns-query +834 -0
- data/bin/geo-doc +135 -0
- data/bin/geo-get +1 -1
- data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
- data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
- 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
- data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
- data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
- 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
- data/network-infra-utility.gemspec +4 -2
- data/network.rb +3 -1
- data/service/geodb/geodb.rb +278 -1
- data/service/ssh/README.md +942 -0
- data/service/ssh/bin/ssh-client +198 -0
- 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
- 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
- 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
- data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
- data/service/ssh/ext/ssh_core/config/sys.config +0 -0
- data/service/ssh/ext/ssh_core/config/vm.args +0 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
- data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
- data/service/ssh/ext/ssh_core/rebar.config +24 -0
- data/service/ssh/ext/ssh_core/rebar.lock +1 -0
- data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
- data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
- data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
- data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
- data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
- data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
- data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
- data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
- data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +104 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
- data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
- data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
- data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
- data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
- data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
- data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
- data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
- data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
- data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
- data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
- data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
- data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
- data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
- data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
- data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
- data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
- data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
- data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
- data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
- data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
- data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
- data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
- data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
- data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
- data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
- data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
- data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
- data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
- data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
- data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
- data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
- data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
- data/support/basic/as_num.rb +221 -0
- data/support/basic/mac_address.rb +281 -0
- data/version.rb +1 -1
- 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(¶ms).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, ¶ms));
|
|
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
|
+
}
|