network-infra-utility 0.2.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/CHANGELOG.md +31 -4
- 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/GeoAPI.md +1 -0
- 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,378 @@
|
|
|
1
|
+
//! 端口转发管理器 — 管理本地/远程端口转发规则。
|
|
2
|
+
//! 对应 Erlang 端的 ssh_port_fwd.erl。
|
|
3
|
+
//!
|
|
4
|
+
//! 核心设计:
|
|
5
|
+
//! - 本地转发:tokio TcpListener 监听本地端口 → SSH channel_open_direct_tcpip → 远程
|
|
6
|
+
//! - 远程转发:handle.tcpip_forward() 请求 SSH 服务器在远端监听 → 推送转发数据
|
|
7
|
+
//! - 每条规则有唯一 rule_id,可动态添加/删除/列举
|
|
8
|
+
//! - 本地转发的 listener task 在规则删除时通过 AbortHandle 取消
|
|
9
|
+
|
|
10
|
+
use std::collections::HashMap;
|
|
11
|
+
use std::sync::Arc;
|
|
12
|
+
|
|
13
|
+
use russh::client;
|
|
14
|
+
use serde_json::{json, Value};
|
|
15
|
+
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
16
|
+
use tokio::net::{TcpListener, TcpStream};
|
|
17
|
+
use tokio::sync::RwLock;
|
|
18
|
+
use tokio::task::AbortHandle;
|
|
19
|
+
use tracing::{debug, info, warn};
|
|
20
|
+
|
|
21
|
+
use crate::codec;
|
|
22
|
+
use crate::conn::ConnManager;
|
|
23
|
+
use crate::handler::SshHandler;
|
|
24
|
+
use crate::proto;
|
|
25
|
+
|
|
26
|
+
/// 端口转发类型
|
|
27
|
+
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
28
|
+
enum FwdType {
|
|
29
|
+
Local,
|
|
30
|
+
Remote,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
impl FwdType {
|
|
34
|
+
fn as_str(&self) -> &'static str {
|
|
35
|
+
match self {
|
|
36
|
+
FwdType::Local => "local",
|
|
37
|
+
FwdType::Remote => "remote",
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn from_str(s: &str) -> Option<Self> {
|
|
42
|
+
match s {
|
|
43
|
+
"local" => Some(FwdType::Local),
|
|
44
|
+
"remote" => Some(FwdType::Remote),
|
|
45
|
+
_ => None,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/// 转发规则
|
|
51
|
+
struct FwdRule {
|
|
52
|
+
rule_id: String,
|
|
53
|
+
conn_id: String,
|
|
54
|
+
fwd_type: FwdType,
|
|
55
|
+
local_port: u16,
|
|
56
|
+
remote_host: String,
|
|
57
|
+
remote_port: u16,
|
|
58
|
+
/// 本地转发:listener task 的 abort handle
|
|
59
|
+
abort_handle: Option<AbortHandle>,
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// 端口转发管理器
|
|
63
|
+
pub struct PortFwdManager {
|
|
64
|
+
/// rule_id → 转发规则
|
|
65
|
+
rules: RwLock<HashMap<String, FwdRule>>,
|
|
66
|
+
/// 连接管理器引用
|
|
67
|
+
conn_mgr: Arc<ConnManager>,
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl PortFwdManager {
|
|
71
|
+
pub fn new(conn_mgr: Arc<ConnManager>) -> Arc<Self> {
|
|
72
|
+
Arc::new(Self {
|
|
73
|
+
rules: RwLock::new(HashMap::new()),
|
|
74
|
+
conn_mgr,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// RPC: portfwd.add — 添加端口转发规则
|
|
79
|
+
pub async fn rpc_add(&self, params: Value) -> Result<Value, String> {
|
|
80
|
+
let conn_id = proto::get_str(¶ms, "conn_id")
|
|
81
|
+
.ok_or("missing conn_id")?
|
|
82
|
+
.to_string();
|
|
83
|
+
let type_str = proto::get_str(¶ms, "type").unwrap_or("local");
|
|
84
|
+
let local_port = proto::get_num(¶ms, "local_port").unwrap_or(0.0) as u16;
|
|
85
|
+
let remote_host = proto::get_str(¶ms, "remote_host")
|
|
86
|
+
.unwrap_or("127.0.0.1")
|
|
87
|
+
.to_string();
|
|
88
|
+
let remote_port = proto::get_num(¶ms, "remote_port").unwrap_or(0.0) as u16;
|
|
89
|
+
|
|
90
|
+
let fwd_type = FwdType::from_str(type_str)
|
|
91
|
+
.ok_or_else(|| format!("invalid_fwd_type: {}", type_str))?;
|
|
92
|
+
|
|
93
|
+
if local_port == 0 {
|
|
94
|
+
return Err("missing_local_port".to_string());
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let rule_id = codec::gen_rule_id(&conn_id);
|
|
98
|
+
|
|
99
|
+
match fwd_type {
|
|
100
|
+
FwdType::Local => {
|
|
101
|
+
self.add_local_forward(&conn_id, &rule_id, local_port, &remote_host, remote_port)
|
|
102
|
+
.await?;
|
|
103
|
+
}
|
|
104
|
+
FwdType::Remote => {
|
|
105
|
+
self.add_remote_forward(&conn_id, &rule_id, local_port, &remote_host, remote_port)
|
|
106
|
+
.await?;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
info!("portfwd.add: conn={} rule={} type={} local_port={} -> {}:{}",
|
|
111
|
+
conn_id, rule_id, fwd_type.as_str(), local_port, remote_host, remote_port);
|
|
112
|
+
|
|
113
|
+
Ok(json!({
|
|
114
|
+
"rule_id": rule_id
|
|
115
|
+
}))
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/// RPC: portfwd.remove — 删除端口转发规则
|
|
119
|
+
pub async fn rpc_remove(&self, params: Value) -> Result<Value, String> {
|
|
120
|
+
let rule_id = proto::get_str(¶ms, "rule_id")
|
|
121
|
+
.ok_or("missing rule_id")?
|
|
122
|
+
.to_string();
|
|
123
|
+
|
|
124
|
+
let mut rules = self.rules.write().await;
|
|
125
|
+
let rule = rules.remove(&rule_id);
|
|
126
|
+
match rule {
|
|
127
|
+
Some(r) => {
|
|
128
|
+
// 本地转发:abort listener task
|
|
129
|
+
if let Some(handle) = r.abort_handle {
|
|
130
|
+
handle.abort();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 远程转发:请求服务器取消转发
|
|
134
|
+
if r.fwd_type == FwdType::Remote {
|
|
135
|
+
if let Some(handle) = self.conn_mgr.get_handle(&r.conn_id).await {
|
|
136
|
+
let _ = handle
|
|
137
|
+
.cancel_tcpip_forward(&r.remote_host, r.local_port.into())
|
|
138
|
+
.await;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
info!("portfwd.remove: rule={}", rule_id);
|
|
143
|
+
Ok(json!({"ok": true}))
|
|
144
|
+
}
|
|
145
|
+
None => Err("rule_not_found".to_string()),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/// RPC: portfwd.list — 列出所有转发规则
|
|
150
|
+
pub async fn rpc_list(&self, params: Value) -> Result<Value, String> {
|
|
151
|
+
let conn_id = proto::get_str(¶ms, "conn_id");
|
|
152
|
+
let rules = self.rules.read().await;
|
|
153
|
+
let items: Vec<Value> = rules
|
|
154
|
+
.iter()
|
|
155
|
+
.filter(|(_, r)| conn_id.is_none() || Some(r.conn_id.as_str()) == conn_id)
|
|
156
|
+
.map(|(id, r)| {
|
|
157
|
+
json!({
|
|
158
|
+
"rule_id": id,
|
|
159
|
+
"conn_id": r.conn_id,
|
|
160
|
+
"type": r.fwd_type.as_str(),
|
|
161
|
+
"local_port": r.local_port,
|
|
162
|
+
"remote_host": r.remote_host,
|
|
163
|
+
"remote_port": r.remote_port,
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
.collect();
|
|
167
|
+
|
|
168
|
+
Ok(json!({"rules": Value::Array(items)}))
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/// 添加本地端口转发
|
|
172
|
+
async fn add_local_forward(
|
|
173
|
+
&self,
|
|
174
|
+
conn_id: &str,
|
|
175
|
+
rule_id: &str,
|
|
176
|
+
local_port: u16,
|
|
177
|
+
remote_host: &str,
|
|
178
|
+
remote_port: u16,
|
|
179
|
+
) -> Result<(), String> {
|
|
180
|
+
// 绑定本地 TCP 监听
|
|
181
|
+
let listener = TcpListener::bind(("127.0.0.1", local_port))
|
|
182
|
+
.await
|
|
183
|
+
.map_err(|e| format!("listen_failed: {}", e))?;
|
|
184
|
+
|
|
185
|
+
let actual_port = listener
|
|
186
|
+
.local_addr()
|
|
187
|
+
.map(|a| a.port())
|
|
188
|
+
.unwrap_or(local_port);
|
|
189
|
+
|
|
190
|
+
let handle = self
|
|
191
|
+
.conn_mgr
|
|
192
|
+
.get_handle(conn_id)
|
|
193
|
+
.await
|
|
194
|
+
.ok_or("conn_not_found")?;
|
|
195
|
+
|
|
196
|
+
let remote_host = remote_host.to_string();
|
|
197
|
+
let conn_id_owned = conn_id.to_string();
|
|
198
|
+
|
|
199
|
+
// 启动 listener task
|
|
200
|
+
let task = tokio::spawn({
|
|
201
|
+
let rh = remote_host.clone();
|
|
202
|
+
async move {
|
|
203
|
+
Self::local_forward_loop(listener, handle, rh, remote_port, conn_id_owned).await;
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
let abort_handle = task.abort_handle();
|
|
208
|
+
|
|
209
|
+
let rule = FwdRule {
|
|
210
|
+
rule_id: rule_id.to_string(),
|
|
211
|
+
conn_id: conn_id.to_string(),
|
|
212
|
+
fwd_type: FwdType::Local,
|
|
213
|
+
local_port: actual_port,
|
|
214
|
+
remote_host: remote_host.to_string(),
|
|
215
|
+
remote_port,
|
|
216
|
+
abort_handle: Some(abort_handle),
|
|
217
|
+
};
|
|
218
|
+
self.rules.write().await.insert(rule_id.to_string(), rule);
|
|
219
|
+
|
|
220
|
+
Ok(())
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// 添加远程端口转发
|
|
224
|
+
async fn add_remote_forward(
|
|
225
|
+
&self,
|
|
226
|
+
conn_id: &str,
|
|
227
|
+
rule_id: &str,
|
|
228
|
+
remote_port: u16,
|
|
229
|
+
local_host: &str,
|
|
230
|
+
local_port: u16,
|
|
231
|
+
) -> Result<(), String> {
|
|
232
|
+
let handle = self
|
|
233
|
+
.conn_mgr
|
|
234
|
+
.get_handle(conn_id)
|
|
235
|
+
.await
|
|
236
|
+
.ok_or("conn_not_found")?;
|
|
237
|
+
|
|
238
|
+
// 请求 SSH 服务器在远端监听
|
|
239
|
+
handle
|
|
240
|
+
.tcpip_forward(local_host, local_port.into())
|
|
241
|
+
.await
|
|
242
|
+
.map_err(|e| format!("tcpip_forward_failed: {}", e))?;
|
|
243
|
+
|
|
244
|
+
let rule = FwdRule {
|
|
245
|
+
rule_id: rule_id.to_string(),
|
|
246
|
+
conn_id: conn_id.to_string(),
|
|
247
|
+
fwd_type: FwdType::Remote,
|
|
248
|
+
local_port: remote_port,
|
|
249
|
+
remote_host: local_host.to_string(),
|
|
250
|
+
remote_port: local_port,
|
|
251
|
+
abort_handle: None,
|
|
252
|
+
};
|
|
253
|
+
self.rules.write().await.insert(rule_id.to_string(), rule);
|
|
254
|
+
|
|
255
|
+
Ok(())
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// 本地转发循环:接受 TCP 连接 → SSH direct_tcpip → 双向数据转发
|
|
259
|
+
async fn local_forward_loop(
|
|
260
|
+
listener: TcpListener,
|
|
261
|
+
handle: Arc<client::Handle<SshHandler>>,
|
|
262
|
+
remote_host: String,
|
|
263
|
+
remote_port: u16,
|
|
264
|
+
conn_id: String,
|
|
265
|
+
) {
|
|
266
|
+
loop {
|
|
267
|
+
match listener.accept().await {
|
|
268
|
+
Ok((client_sock, client_addr)) => {
|
|
269
|
+
debug!("portfwd: accepted connection from {} on conn {}", client_addr, conn_id);
|
|
270
|
+
|
|
271
|
+
let handle = Arc::clone(&handle);
|
|
272
|
+
let rh = remote_host.clone();
|
|
273
|
+
let rp = remote_port;
|
|
274
|
+
|
|
275
|
+
// 每个连接独立 spawn 一个转发任务
|
|
276
|
+
tokio::spawn(async move {
|
|
277
|
+
Self::handle_forward_connection(
|
|
278
|
+
client_sock,
|
|
279
|
+
handle,
|
|
280
|
+
rh,
|
|
281
|
+
rp,
|
|
282
|
+
)
|
|
283
|
+
.await;
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
Err(e) => {
|
|
287
|
+
warn!("portfwd: accept failed on conn {}: {}", conn_id, e);
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// 处理单个本地转发连接
|
|
295
|
+
async fn handle_forward_connection(
|
|
296
|
+
mut client_sock: TcpStream,
|
|
297
|
+
handle: Arc<client::Handle<SshHandler>>,
|
|
298
|
+
remote_host: String,
|
|
299
|
+
remote_port: u16,
|
|
300
|
+
) {
|
|
301
|
+
// 通过 SSH 打开 direct_tcpip 通道
|
|
302
|
+
let channel = match handle
|
|
303
|
+
.channel_open_direct_tcpip(&remote_host, remote_port.into(), "127.0.0.1", 0)
|
|
304
|
+
.await
|
|
305
|
+
{
|
|
306
|
+
Ok(ch) => ch,
|
|
307
|
+
Err(e) => {
|
|
308
|
+
warn!("portfwd: direct_tcpip failed to {}:{}: {}", remote_host, remote_port, e);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
// 获取通道的读写流
|
|
314
|
+
let (mut ch_reader, mut ch_writer) = {
|
|
315
|
+
let stream = channel.into_stream();
|
|
316
|
+
tokio::io::split(stream)
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// 拆分 TCP socket 为读写两半,避免双重借用
|
|
320
|
+
let (mut sock_reader, mut sock_writer) = tokio::io::split(&mut client_sock);
|
|
321
|
+
|
|
322
|
+
// 双向数据转发
|
|
323
|
+
let client_to_remote = async {
|
|
324
|
+
let mut buf = [0u8; 32768];
|
|
325
|
+
loop {
|
|
326
|
+
match sock_reader.read(&mut buf).await {
|
|
327
|
+
Ok(0) => break, // EOF
|
|
328
|
+
Ok(n) => {
|
|
329
|
+
if ch_writer.write_all(&buf[..n]).await.is_err() {
|
|
330
|
+
break;
|
|
331
|
+
}
|
|
332
|
+
let _ = ch_writer.flush().await;
|
|
333
|
+
}
|
|
334
|
+
Err(_) => break,
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
let remote_to_client = async {
|
|
340
|
+
let mut buf = [0u8; 32768];
|
|
341
|
+
loop {
|
|
342
|
+
match ch_reader.read(&mut buf).await {
|
|
343
|
+
Ok(0) => break, // EOF
|
|
344
|
+
Ok(n) => {
|
|
345
|
+
if sock_writer.write_all(&buf[..n]).await.is_err() {
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
348
|
+
let _ = sock_writer.flush().await;
|
|
349
|
+
}
|
|
350
|
+
Err(_) => break,
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
tokio::join!(client_to_remote, remote_to_client);
|
|
356
|
+
|
|
357
|
+
let _ = client_sock.shutdown().await;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
/// 清理指定连接的所有转发规则(连接断开时调用)
|
|
361
|
+
pub async fn cleanup_conn(&self, conn_id: &str) {
|
|
362
|
+
let mut rules = self.rules.write().await;
|
|
363
|
+
let to_remove: Vec<String> = rules
|
|
364
|
+
.iter()
|
|
365
|
+
.filter(|(_, r)| r.conn_id == conn_id)
|
|
366
|
+
.map(|(k, _)| k.clone())
|
|
367
|
+
.collect();
|
|
368
|
+
|
|
369
|
+
for id in &to_remove {
|
|
370
|
+
if let Some(rule) = rules.remove(id) {
|
|
371
|
+
if let Some(handle) = rule.abort_handle {
|
|
372
|
+
handle.abort();
|
|
373
|
+
}
|
|
374
|
+
warn!("portfwd rule {} cleaned up (conn {} disconnected)", id, conn_id);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
//! JSON-RPC 2.0 协议编解码与分帧。
|
|
2
|
+
//! 对应 Erlang 端的 ssh_ipc_proto.erl。
|
|
3
|
+
//!
|
|
4
|
+
//! 协议规定:
|
|
5
|
+
//! - 消息格式:JSON-RPC 2.0,每条消息以 \n 分隔
|
|
6
|
+
//! - 有 id 的消息为 request(Ruby→Rust)或 response(双向)
|
|
7
|
+
//! - 无 id 的消息为 notification(推送,Rust→Ruby)
|
|
8
|
+
//! - 最大消息 2MB
|
|
9
|
+
|
|
10
|
+
use serde_json::{Map, Value};
|
|
11
|
+
|
|
12
|
+
/// 协议版本
|
|
13
|
+
pub const PROTOCOL_VER: &str = "1.0";
|
|
14
|
+
/// 消息分隔符
|
|
15
|
+
pub const MSG_DELIMITER: u8 = b'\n';
|
|
16
|
+
/// 最大消息大小(2MB)
|
|
17
|
+
pub const MAX_MSG_SIZE: usize = 2 * 1024 * 1024;
|
|
18
|
+
|
|
19
|
+
/// 编码 JSON-RPC 请求(带 id 和 method)
|
|
20
|
+
pub fn encode_request(id: i64, method: &str, params: &Value) -> String {
|
|
21
|
+
serde_json::json!({
|
|
22
|
+
"jsonrpc": "2.0",
|
|
23
|
+
"id": id,
|
|
24
|
+
"method": method,
|
|
25
|
+
"params": params
|
|
26
|
+
})
|
|
27
|
+
.to_string()
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/// 编码 JSON-RPC 成功响应
|
|
31
|
+
pub fn encode_response(id: i64, result: &Value) -> String {
|
|
32
|
+
serde_json::json!({
|
|
33
|
+
"jsonrpc": "2.0",
|
|
34
|
+
"id": id,
|
|
35
|
+
"result": result
|
|
36
|
+
})
|
|
37
|
+
.to_string()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// 编码 JSON-RPC 错误响应
|
|
41
|
+
pub fn encode_error(id: i64, code: i64, message: &str) -> String {
|
|
42
|
+
serde_json::json!({
|
|
43
|
+
"jsonrpc": "2.0",
|
|
44
|
+
"id": id,
|
|
45
|
+
"error": {
|
|
46
|
+
"code": code,
|
|
47
|
+
"message": message
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
.to_string()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// 编码推送通知(无 id)
|
|
54
|
+
pub fn encode_push(method: &str, params: &Value) -> String {
|
|
55
|
+
serde_json::json!({
|
|
56
|
+
"jsonrpc": "2.0",
|
|
57
|
+
"method": method,
|
|
58
|
+
"params": params
|
|
59
|
+
})
|
|
60
|
+
.to_string()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/// 给消息追加帧分隔符
|
|
64
|
+
pub fn frame(msg: &str) -> Vec<u8> {
|
|
65
|
+
let mut data = msg.as_bytes().to_vec();
|
|
66
|
+
data.push(MSG_DELIMITER);
|
|
67
|
+
data
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/// 从缓冲区中分割出完整帧。
|
|
71
|
+
/// 返回 (完整帧列表, 剩余不完整数据)
|
|
72
|
+
pub fn unframe(buffer: &[u8]) -> (Vec<Vec<u8>>, Vec<u8>) {
|
|
73
|
+
let mut frames = Vec::new();
|
|
74
|
+
let mut start = 0;
|
|
75
|
+
|
|
76
|
+
for (i, &byte) in buffer.iter().enumerate() {
|
|
77
|
+
if byte == MSG_DELIMITER {
|
|
78
|
+
// 检查前面是否有 \r(处理 \r\n 的情况)
|
|
79
|
+
let end = if i > start && buffer[i - 1] == b'\r' {
|
|
80
|
+
i - 1
|
|
81
|
+
} else {
|
|
82
|
+
i
|
|
83
|
+
};
|
|
84
|
+
if end > start {
|
|
85
|
+
frames.push(buffer[start..end].to_vec());
|
|
86
|
+
}
|
|
87
|
+
start = i + 1;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let rest = if start < buffer.len() {
|
|
92
|
+
buffer[start..].to_vec()
|
|
93
|
+
} else {
|
|
94
|
+
Vec::new()
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
(frames, rest)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// 消息分类
|
|
101
|
+
#[derive(Debug, Clone)]
|
|
102
|
+
pub enum Message {
|
|
103
|
+
/// Ruby→Rust 请求(有 id 和 method)
|
|
104
|
+
Request { id: i64, method: String, params: Value },
|
|
105
|
+
/// 对 Rust→Ruby 反向请求的响应(有 id,无 method)
|
|
106
|
+
Response { id: i64, result: Option<Value>, error: Option<Value> },
|
|
107
|
+
/// 推送通知(无 id,有 method)
|
|
108
|
+
Notification { method: String, params: Value },
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/// 解码一条 JSON-RPC 消息
|
|
112
|
+
pub fn decode(data: &[u8]) -> Result<Message, String> {
|
|
113
|
+
let text = std::str::from_utf8(data).map_err(|e| format!("utf8 error: {}", e))?;
|
|
114
|
+
let val: Value = serde_json::from_str(text).map_err(|e| format!("json error: {}", e))?;
|
|
115
|
+
|
|
116
|
+
let obj = val.as_object().ok_or("not a json object")?;
|
|
117
|
+
|
|
118
|
+
// 校验 jsonrpc 版本
|
|
119
|
+
let ver = obj
|
|
120
|
+
.get("jsonrpc")
|
|
121
|
+
.and_then(|v| v.as_str())
|
|
122
|
+
.ok_or("missing jsonrpc field")?;
|
|
123
|
+
if ver != "2.0" {
|
|
124
|
+
return Err(format!("unsupported jsonrpc version: {}", ver));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let id = obj.get("id");
|
|
128
|
+
|
|
129
|
+
match id {
|
|
130
|
+
Some(id_val) => {
|
|
131
|
+
let id_num = id_val.as_i64().ok_or("id is not an integer")?;
|
|
132
|
+
|
|
133
|
+
if obj.contains_key("method") {
|
|
134
|
+
// 有 id 和 method → Ruby→Rust 请求
|
|
135
|
+
let method = obj
|
|
136
|
+
.get("method")
|
|
137
|
+
.and_then(|v| v.as_str())
|
|
138
|
+
.ok_or("method not a string")?
|
|
139
|
+
.to_string();
|
|
140
|
+
let params = obj.get("params").cloned().unwrap_or(Value::Null);
|
|
141
|
+
Ok(Message::Request {
|
|
142
|
+
id: id_num,
|
|
143
|
+
method,
|
|
144
|
+
params,
|
|
145
|
+
})
|
|
146
|
+
} else {
|
|
147
|
+
// 有 id 无 method → 反向 RPC 的响应
|
|
148
|
+
let result = obj.get("result").cloned();
|
|
149
|
+
let error = obj.get("error").cloned();
|
|
150
|
+
Ok(Message::Response {
|
|
151
|
+
id: id_num,
|
|
152
|
+
result,
|
|
153
|
+
error,
|
|
154
|
+
})
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
None => {
|
|
158
|
+
// 无 id → 推送通知
|
|
159
|
+
let method = obj
|
|
160
|
+
.get("method")
|
|
161
|
+
.and_then(|v| v.as_str())
|
|
162
|
+
.ok_or("notification missing method")?
|
|
163
|
+
.to_string();
|
|
164
|
+
let params = obj.get("params").cloned().unwrap_or(Value::Null);
|
|
165
|
+
Ok(Message::Notification { method, params })
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/// 从 Value 中提取字符串字段(兼容 String key)
|
|
171
|
+
pub fn get_str<'a>(params: &'a Value, key: &str) -> Option<&'a str> {
|
|
172
|
+
params.get(key).and_then(|v| v.as_str())
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/// 从 Value 中提取数值字段
|
|
176
|
+
pub fn get_num(params: &Value, key: &str) -> Option<f64> {
|
|
177
|
+
params.get(key).and_then(|v| v.as_f64())
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// 从 Value 中提取布尔字段
|
|
181
|
+
pub fn get_bool(params: &Value, key: &str) -> Option<bool> {
|
|
182
|
+
params.get(key).and_then(|v| v.as_bool())
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/// 构建成功响应的 params(Erlang 风格 {ok, #{}}
|
|
186
|
+
pub fn ok_result(fields: Map<String, Value>) -> Value {
|
|
187
|
+
Value::Object(fields)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// 构建简单 ok 响应
|
|
191
|
+
pub fn ok_true() -> Value {
|
|
192
|
+
serde_json::json!({"ok": true})
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/// 构建错误对象
|
|
196
|
+
pub fn error_obj(message: &str) -> Value {
|
|
197
|
+
serde_json::json!({"message": message})
|
|
198
|
+
}
|