pub mod dynamic;
pub mod local;
pub mod manager;
pub mod remote;
pub mod spec;
pub mod tunnel;
pub use manager::{ForwardingId, ForwardingManager, ForwardingMessage};
pub use spec::ForwardingSpec;
use anyhow::{Context, Result};
use std::fmt;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ForwardingType {
Local {
bind_addr: IpAddr,
bind_port: u16,
remote_host: String,
remote_port: u16,
},
Remote {
bind_addr: IpAddr,
bind_port: u16,
local_host: String,
local_port: u16,
},
Dynamic {
bind_addr: IpAddr,
bind_port: u16,
socks_version: SocksVersion,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SocksVersion {
V4,
V5,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ForwardingStatus {
Initializing,
Active,
Reconnecting,
Failed(String),
Stopped,
}
#[derive(Debug, Default, Clone)]
pub struct ForwardingStats {
pub active_connections: usize,
pub total_connections: u64,
pub bytes_transferred: u64,
pub failed_connections: u64,
pub last_error: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ForwardingConfig {
pub max_connections: usize,
pub connect_timeout_secs: u64,
pub auto_reconnect: bool,
pub max_reconnect_attempts: u32,
pub reconnect_delay_ms: u64,
pub max_reconnect_delay_ms: u64,
pub buffer_size: usize,
}
impl Default for ForwardingConfig {
fn default() -> Self {
Self {
max_connections: 100,
connect_timeout_secs: 30,
auto_reconnect: true,
max_reconnect_attempts: 10,
reconnect_delay_ms: 1000,
max_reconnect_delay_ms: 30000,
buffer_size: 8192,
}
}
}
impl fmt::Display for ForwardingType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ForwardingType::Local {
bind_addr,
bind_port,
remote_host,
remote_port,
} => {
write!(f, "{bind_addr}:{bind_port}→{remote_host}:{remote_port}")
}
ForwardingType::Remote {
bind_addr,
bind_port,
local_host,
local_port,
} => {
write!(f, "{bind_addr}:{bind_port}←{local_host}:{local_port}")
}
ForwardingType::Dynamic {
bind_addr,
bind_port,
socks_version,
} => {
write!(f, "SOCKS{socks_version:?} proxy on {bind_addr}:{bind_port}")
}
}
}
}
impl fmt::Display for ForwardingStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ForwardingStatus::Initializing => write!(f, "initializing"),
ForwardingStatus::Active => write!(f, "active"),
ForwardingStatus::Reconnecting => write!(f, "reconnecting"),
ForwardingStatus::Failed(err) => write!(f, "failed: {err}"),
ForwardingStatus::Stopped => write!(f, "stopped"),
}
}
}
impl SocksVersion {
pub fn parse(s: &str) -> Result<Self> {
match s {
"4" | "v4" | "socks4" => Ok(SocksVersion::V4),
"5" | "v5" | "socks5" => Ok(SocksVersion::V5),
_ => Err(anyhow::anyhow!(
"Invalid SOCKS version: {s}. Expected 4 or 5"
)),
}
}
}
pub fn parse_bind_spec(spec: &str) -> Result<SocketAddr> {
if let Ok(port) = spec.parse::<u16>() {
return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), port));
}
if let Some(port_str) = spec.strip_prefix("*:") {
let port = port_str
.parse::<u16>()
.with_context(|| format!("Invalid port in bind specification: {spec}"))?;
return Ok(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port));
}
spec.parse::<SocketAddr>()
.with_context(|| format!("Invalid bind specification: {spec}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_bind_spec() {
let addr = parse_bind_spec("8080").unwrap();
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::LOCALHOST));
assert_eq!(addr.port(), 8080);
let addr = parse_bind_spec("*:8080").unwrap();
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::UNSPECIFIED));
assert_eq!(addr.port(), 8080);
let addr = parse_bind_spec("192.168.1.1:8080").unwrap();
assert_eq!(addr.ip(), IpAddr::V4(Ipv4Addr::new(192, 168, 1, 1)));
assert_eq!(addr.port(), 8080);
let addr = parse_bind_spec("[::1]:8080").unwrap();
assert_eq!(addr.port(), 8080);
}
#[test]
fn test_socks_version_parse() {
assert_eq!(SocksVersion::parse("4").unwrap(), SocksVersion::V4);
assert_eq!(SocksVersion::parse("v5").unwrap(), SocksVersion::V5);
assert_eq!(SocksVersion::parse("socks4").unwrap(), SocksVersion::V4);
assert!(SocksVersion::parse("invalid").is_err());
}
#[test]
fn test_forwarding_type_display() {
let local = ForwardingType::Local {
bind_addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
bind_port: 8080,
remote_host: "example.com".to_string(),
remote_port: 80,
};
assert_eq!(format!("{local}"), "127.0.0.1:8080→example.com:80");
let dynamic = ForwardingType::Dynamic {
bind_addr: IpAddr::V4(Ipv4Addr::LOCALHOST),
bind_port: 1080,
socks_version: SocksVersion::V5,
};
assert!(format!("{dynamic}").contains("SOCKS"));
}
}