1pub mod dynamic;
24pub mod local;
25pub mod manager;
26pub mod remote;
27pub mod spec;
28pub mod tunnel;
29
30pub use manager::{ForwardingId, ForwardingManager, ForwardingMessage};
32pub use spec::ForwardingSpec;
33
34use crate::ssh::tokio_client::AddressFamily;
35use anyhow::{Context, Result};
36use std::fmt;
37use std::net::{IpAddr, SocketAddr};
38
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum ForwardingType {
42 Local {
45 bind_addr: IpAddr,
46 bind_port: u16,
47 remote_host: String,
48 remote_port: u16,
49 },
50 Remote {
53 bind_addr: IpAddr,
54 bind_port: u16,
55 local_host: String,
56 local_port: u16,
57 },
58 Dynamic {
61 bind_addr: IpAddr,
62 bind_port: u16,
63 socks_version: SocksVersion,
65 },
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub enum SocksVersion {
71 V4,
72 V5,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
77pub enum ForwardingStatus {
78 Initializing,
80 Active,
82 Reconnecting,
84 Failed(String),
86 Stopped,
88}
89
90#[derive(Debug, Default, Clone)]
92pub struct ForwardingStats {
93 pub active_connections: usize,
95 pub total_connections: u64,
97 pub bytes_transferred: u64,
99 pub failed_connections: u64,
101 pub last_error: Option<String>,
103}
104
105#[derive(Debug, Clone)]
107pub struct ForwardingConfig {
108 pub max_connections: usize,
110 pub connect_timeout_secs: u64,
112 pub auto_reconnect: bool,
114 pub max_reconnect_attempts: u32,
116 pub reconnect_delay_ms: u64,
118 pub max_reconnect_delay_ms: u64,
120 pub buffer_size: usize,
122 pub address_family: AddressFamily,
130}
131
132impl Default for ForwardingConfig {
133 fn default() -> Self {
134 Self {
135 max_connections: 100,
136 connect_timeout_secs: 30,
137 auto_reconnect: true,
138 max_reconnect_attempts: 10,
139 reconnect_delay_ms: 1000,
140 max_reconnect_delay_ms: 30000,
141 buffer_size: 8192,
142 address_family: AddressFamily::Any,
143 }
144 }
145}
146
147impl fmt::Display for ForwardingType {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 match self {
153 ForwardingType::Local {
154 bind_addr,
155 bind_port,
156 remote_host,
157 remote_port,
158 } => {
159 write!(
160 f,
161 "{}→{remote_host}:{remote_port}",
162 SocketAddr::new(*bind_addr, *bind_port)
163 )
164 }
165 ForwardingType::Remote {
166 bind_addr,
167 bind_port,
168 local_host,
169 local_port,
170 } => {
171 write!(
172 f,
173 "{}←{local_host}:{local_port}",
174 SocketAddr::new(*bind_addr, *bind_port)
175 )
176 }
177 ForwardingType::Dynamic {
178 bind_addr,
179 bind_port,
180 socks_version,
181 } => {
182 write!(
183 f,
184 "SOCKS{socks_version:?} proxy on {}",
185 SocketAddr::new(*bind_addr, *bind_port)
186 )
187 }
188 }
189 }
190}
191
192impl fmt::Display for ForwardingStatus {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 ForwardingStatus::Initializing => write!(f, "initializing"),
196 ForwardingStatus::Active => write!(f, "active"),
197 ForwardingStatus::Reconnecting => write!(f, "reconnecting"),
198 ForwardingStatus::Failed(err) => write!(f, "failed: {err}"),
199 ForwardingStatus::Stopped => write!(f, "stopped"),
200 }
201 }
202}
203
204impl SocksVersion {
205 pub fn parse(s: &str) -> Result<Self> {
207 match s {
208 "4" | "v4" | "socks4" => Ok(SocksVersion::V4),
209 "5" | "v5" | "socks5" => Ok(SocksVersion::V5),
210 _ => Err(anyhow::anyhow!(
211 "Invalid SOCKS version: {s}. Expected 4 or 5"
212 )),
213 }
214 }
215}
216
217pub fn parse_bind_spec(spec: &str) -> Result<SocketAddr> {
221 parse_bind_spec_with_family(spec, AddressFamily::Any)
222}
223
224pub fn parse_bind_spec_with_family(
232 spec: &str,
233 address_family: AddressFamily,
234) -> Result<SocketAddr> {
235 if let Ok(port) = spec.parse::<u16>() {
237 return Ok(SocketAddr::new(address_family.loopback(), port));
239 }
240
241 if let Some(port_str) = spec.strip_prefix("*:") {
243 let port = port_str
244 .parse::<u16>()
245 .with_context(|| format!("Invalid port in bind specification: {spec}"))?;
246 return Ok(SocketAddr::new(address_family.unspecified(), port));
247 }
248
249 spec.parse::<SocketAddr>()
252 .with_context(|| format!("Invalid bind specification: {spec}"))
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use std::net::{Ipv4Addr, Ipv6Addr};
259
260 #[test]
261 fn test_parse_bind_spec_with_forced_family() {
262 let addr = parse_bind_spec_with_family("8080", AddressFamily::V6).unwrap();
264 assert_eq!(addr.ip(), IpAddr::V6(Ipv6Addr::LOCALHOST));
265 assert_eq!(addr.port(), 8080);
266
267 let addr = parse_bind_spec_with_family("*:8080", AddressFamily::V6).unwrap();
269 assert_eq!(addr.ip(), IpAddr::V6(Ipv6Addr::UNSPECIFIED));
270
271 let addr = parse_bind_spec_with_family("8080", AddressFamily::V4).unwrap();
273 assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::LOCALHOST));
274 let addr = parse_bind_spec_with_family("*:8080", AddressFamily::V4).unwrap();
275 assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::UNSPECIFIED));
276
277 let addr = parse_bind_spec_with_family("192.168.1.1:8080", AddressFamily::V6).unwrap();
279 assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
280 let addr = parse_bind_spec_with_family("[::1]:8080", AddressFamily::V4).unwrap();
281 assert_eq!(addr.ip(), IpAddr::V6(Ipv6Addr::LOCALHOST));
282 }
283
284 #[test]
285 fn test_parse_bind_spec() {
286 let addr = parse_bind_spec("8080").unwrap();
288 assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::LOCALHOST));
289 assert_eq!(addr.port(), 8080);
290
291 let addr = parse_bind_spec("*:8080").unwrap();
293 assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::UNSPECIFIED));
294 assert_eq!(addr.port(), 8080);
295
296 let addr = parse_bind_spec("192.168.1.1:8080").unwrap();
298 assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
299 assert_eq!(addr.port(), 8080);
300
301 let addr = parse_bind_spec("[::1]:8080").unwrap();
303 assert_eq!(addr.port(), 8080);
304 }
305
306 #[test]
307 fn test_socks_version_parse() {
308 assert_eq!(SocksVersion::parse("4").unwrap(), SocksVersion::V4);
309 assert_eq!(SocksVersion::parse("v5").unwrap(), SocksVersion::V5);
310 assert_eq!(SocksVersion::parse("socks4").unwrap(), SocksVersion::V4);
311 assert!(SocksVersion::parse("invalid").is_err());
312 }
313
314 #[test]
315 fn test_forwarding_type_display() {
316 let local = ForwardingType::Local {
317 bind_addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
318 bind_port: 8080,
319 remote_host: "example.com".to_string(),
320 remote_port: 80,
321 };
322 assert_eq!(format!("{local}"), "127.0.0.1:8080→example.com:80");
323
324 let dynamic = ForwardingType::Dynamic {
325 bind_addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
326 bind_port: 1080,
327 socks_version: SocksVersion::V5,
328 };
329 assert!(format!("{dynamic}").contains("SOCKS"));
330 }
331
332 #[test]
333 fn test_forwarding_type_display_brackets_ipv6() {
334 let local_v4 = ForwardingType::Local {
336 bind_addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
337 bind_port: 8080,
338 remote_host: "example.com".to_string(),
339 remote_port: 80,
340 };
341 assert_eq!(format!("{local_v4}"), "127.0.0.1:8080→example.com:80");
342
343 let local_v6 = ForwardingType::Local {
345 bind_addr: IpAddr::V6(Ipv6Addr::LOCALHOST),
346 bind_port: 8080,
347 remote_host: "example.com".to_string(),
348 remote_port: 80,
349 };
350 assert_eq!(format!("{local_v6}"), "[::1]:8080→example.com:80");
351
352 let remote_v6 = ForwardingType::Remote {
353 bind_addr: IpAddr::V6(Ipv6Addr::LOCALHOST),
354 bind_port: 9090,
355 local_host: "localhost".to_string(),
356 local_port: 22,
357 };
358 assert_eq!(format!("{remote_v6}"), "[::1]:9090←localhost:22");
359
360 let dynamic_v6 = ForwardingType::Dynamic {
361 bind_addr: IpAddr::V6(Ipv6Addr::LOCALHOST),
362 bind_port: 1080,
363 socks_version: SocksVersion::V5,
364 };
365 assert_eq!(format!("{dynamic_v6}"), "SOCKSV5 proxy on [::1]:1080");
366 }
367}