use crate::{Error, Result};
use socks5_impl::protocol::UserKey;
use tproxy_config::IpCidr;
#[cfg(target_os = "linux")]
use std::ffi::OsString;
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
use std::str::FromStr;
#[macro_export]
macro_rules! version_info {
() => {
concat!(env!("CARGO_PKG_VERSION"), " (", env!("GIT_HASH"), " ", env!("BUILD_TIME"), ")")
};
}
fn about_info() -> &'static str {
concat!("Tunnel interface to proxy.\nVersion: ", version_info!())
}
#[derive(Debug, Clone, clap::Parser, serde::Serialize, serde::Deserialize)]
#[command(author, version = version_info!(), about = about_info(), long_about = None)]
pub struct Args {
#[arg(short, long, value_parser = |s: &str| ArgProxy::try_from(s), value_name = "URL")]
pub proxy: ArgProxy,
#[arg(short, long, value_name = "name", value_parser = validate_tun)]
#[cfg_attr(unix, arg(conflicts_with = "tun_fd"))]
#[serde(skip_serializing_if = "Option::is_none")]
pub tun: Option<String>,
#[cfg(unix)]
#[arg(long, value_name = "fd", conflicts_with = "tun")]
#[serde(skip_serializing_if = "Option::is_none")]
pub tun_fd: Option<i32>,
#[cfg(unix)]
#[arg(long, value_name = "true or false", conflicts_with = "tun", requires = "tun_fd")]
#[serde(skip_serializing_if = "Option::is_none")]
pub close_fd_on_drop: Option<bool>,
#[cfg(target_os = "linux")]
#[arg(long)]
pub unshare: bool,
#[cfg(target_os = "linux")]
#[arg(long)]
#[serde(skip_serializing_if = "Option::is_none")]
pub unshare_pidfile: Option<String>,
#[cfg(target_os = "linux")]
#[arg(long, value_name = "fd", hide(true))]
#[serde(skip_serializing_if = "Option::is_none")]
pub socket_transfer_fd: Option<i32>,
#[cfg(target_os = "linux")]
#[arg(requires = "unshare")]
pub admin_command: Vec<OsString>,
#[arg(short = '6', long)]
pub ipv6_enabled: bool,
#[arg(short, long)]
pub setup: bool,
#[arg(short, long, value_name = "strategy", value_enum, default_value = "direct")]
pub dns: ArgDns,
#[arg(long, value_name = "IP", default_value = "8.8.8.8")]
pub dns_addr: IpAddr,
#[arg(long, value_name = "CIDR", default_value = "198.18.0.0/15")]
pub virtual_dns_pool: IpCidr,
#[arg(short, long, value_name = "IP/CIDR")]
pub bypass: Vec<IpCidr>,
#[arg(long, value_name = "seconds", default_value = "600")]
pub tcp_timeout: u64,
#[arg(long, value_name = "seconds", default_value = "10")]
pub udp_timeout: u64,
#[arg(short, long, value_name = "level", value_enum, default_value = "info")]
pub verbosity: ArgVerbosity,
#[arg(long)]
pub daemonize: bool,
#[arg(long)]
pub exit_on_fatal_error: bool,
#[arg(long, value_name = "number", default_value = "200")]
pub max_sessions: usize,
#[cfg(feature = "udpgw")]
#[arg(long, value_name = "IP:PORT")]
#[serde(skip_serializing_if = "Option::is_none")]
pub udpgw_server: Option<SocketAddr>,
#[cfg(feature = "udpgw")]
#[arg(long, value_name = "number", requires = "udpgw_server")]
#[serde(skip_serializing_if = "Option::is_none")]
pub udpgw_connections: Option<usize>,
#[cfg(feature = "udpgw")]
#[arg(long, value_name = "seconds", requires = "udpgw_server")]
#[serde(skip_serializing_if = "Option::is_none")]
pub udpgw_keepalive: Option<u64>,
}
fn validate_tun(p: &str) -> Result<String> {
#[cfg(target_os = "macos")]
if p.len() <= 4 || &p[..4] != "utun" {
return Err(Error::from("Invalid tun interface name, please use utunX"));
}
Ok(p.to_string())
}
impl Default for Args {
fn default() -> Self {
#[cfg(target_os = "linux")]
let setup = false;
#[cfg(not(target_os = "linux"))]
let setup = true;
Args {
proxy: ArgProxy::default(),
tun: None,
#[cfg(unix)]
tun_fd: None,
#[cfg(unix)]
close_fd_on_drop: None,
#[cfg(target_os = "linux")]
unshare: false,
#[cfg(target_os = "linux")]
unshare_pidfile: None,
#[cfg(target_os = "linux")]
socket_transfer_fd: None,
#[cfg(target_os = "linux")]
admin_command: Vec::new(),
ipv6_enabled: false,
setup,
dns: ArgDns::default(),
dns_addr: "8.8.8.8".parse().unwrap(),
bypass: vec![],
tcp_timeout: 600,
udp_timeout: 10,
verbosity: ArgVerbosity::Info,
virtual_dns_pool: IpCidr::from_str("198.18.0.0/15").unwrap(),
daemonize: false,
exit_on_fatal_error: false,
max_sessions: 200,
#[cfg(feature = "udpgw")]
udpgw_server: None,
#[cfg(feature = "udpgw")]
udpgw_connections: None,
#[cfg(feature = "udpgw")]
udpgw_keepalive: None,
}
}
}
impl Args {
#[allow(clippy::let_and_return)]
pub fn parse_args() -> Self {
let args = <Self as ::clap::Parser>::parse();
#[cfg(target_os = "linux")]
if !args.setup && args.tun.is_none() {
eprintln!("Missing required argument, '--tun' must present when '--setup' is not used.");
std::process::exit(-1);
}
args
}
pub fn proxy(&mut self, proxy: ArgProxy) -> &mut Self {
self.proxy = proxy;
self
}
pub fn dns(&mut self, dns: ArgDns) -> &mut Self {
self.dns = dns;
self
}
#[cfg(feature = "udpgw")]
pub fn udpgw_server(&mut self, udpgw: SocketAddr) -> &mut Self {
self.udpgw_server = Some(udpgw);
self
}
#[cfg(feature = "udpgw")]
pub fn udpgw_connections(&mut self, udpgw_connections: usize) -> &mut Self {
self.udpgw_connections = Some(udpgw_connections);
self
}
#[cfg(unix)]
pub fn tun_fd(&mut self, tun_fd: Option<i32>) -> &mut Self {
self.tun_fd = tun_fd;
self
}
#[cfg(unix)]
pub fn close_fd_on_drop(&mut self, close_fd_on_drop: bool) -> &mut Self {
self.close_fd_on_drop = Some(close_fd_on_drop);
self
}
pub fn verbosity(&mut self, verbosity: ArgVerbosity) -> &mut Self {
self.verbosity = verbosity;
self
}
pub fn tun(&mut self, tun: String) -> &mut Self {
self.tun = Some(tun);
self
}
pub fn dns_addr(&mut self, dns_addr: IpAddr) -> &mut Self {
self.dns_addr = dns_addr;
self
}
pub fn bypass(&mut self, bypass: IpCidr) -> &mut Self {
self.bypass.push(bypass);
self
}
pub fn ipv6_enabled(&mut self, ipv6_enabled: bool) -> &mut Self {
self.ipv6_enabled = ipv6_enabled;
self
}
pub fn setup(&mut self, setup: bool) -> &mut Self {
self.setup = setup;
self
}
}
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
pub enum ArgVerbosity {
Off = 0,
Error,
Warn,
#[default]
Info,
Debug,
Trace,
}
#[cfg(target_os = "android")]
impl TryFrom<jni::sys::jint> for ArgVerbosity {
type Error = Error;
fn try_from(value: jni::sys::jint) -> Result<Self> {
match value {
0 => Ok(ArgVerbosity::Off),
1 => Ok(ArgVerbosity::Error),
2 => Ok(ArgVerbosity::Warn),
3 => Ok(ArgVerbosity::Info),
4 => Ok(ArgVerbosity::Debug),
5 => Ok(ArgVerbosity::Trace),
_ => Err(Error::from("Invalid verbosity level")),
}
}
}
impl From<ArgVerbosity> for log::LevelFilter {
fn from(verbosity: ArgVerbosity) -> Self {
match verbosity {
ArgVerbosity::Off => log::LevelFilter::Off,
ArgVerbosity::Error => log::LevelFilter::Error,
ArgVerbosity::Warn => log::LevelFilter::Warn,
ArgVerbosity::Info => log::LevelFilter::Info,
ArgVerbosity::Debug => log::LevelFilter::Debug,
ArgVerbosity::Trace => log::LevelFilter::Trace,
}
}
}
impl From<log::Level> for ArgVerbosity {
fn from(level: log::Level) -> Self {
match level {
log::Level::Error => ArgVerbosity::Error,
log::Level::Warn => ArgVerbosity::Warn,
log::Level::Info => ArgVerbosity::Info,
log::Level::Debug => ArgVerbosity::Debug,
log::Level::Trace => ArgVerbosity::Trace,
}
}
}
impl std::fmt::Display for ArgVerbosity {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
ArgVerbosity::Off => write!(f, "off"),
ArgVerbosity::Error => write!(f, "error"),
ArgVerbosity::Warn => write!(f, "warn"),
ArgVerbosity::Info => write!(f, "info"),
ArgVerbosity::Debug => write!(f, "debug"),
ArgVerbosity::Trace => write!(f, "trace"),
}
}
}
#[repr(C)]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum, serde::Serialize, serde::Deserialize)]
pub enum ArgDns {
Virtual = 0,
OverTcp,
#[default]
Direct,
}
#[cfg(target_os = "android")]
impl TryFrom<jni::sys::jint> for ArgDns {
type Error = Error;
fn try_from(value: jni::sys::jint) -> Result<Self> {
match value {
0 => Ok(ArgDns::Virtual),
1 => Ok(ArgDns::OverTcp),
2 => Ok(ArgDns::Direct),
_ => Err(Error::from("Invalid DNS strategy")),
}
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct ArgProxy {
pub proxy_type: ProxyType,
pub addr: SocketAddr,
#[serde(skip_serializing_if = "Option::is_none")]
pub credentials: Option<UserKey>,
}
impl Default for ArgProxy {
fn default() -> Self {
ArgProxy {
proxy_type: ProxyType::Socks5,
addr: "127.0.0.1:1080".parse().unwrap(),
credentials: None,
}
}
}
impl std::fmt::Display for ArgProxy {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let auth = match &self.credentials {
Some(creds) => format!("{creds}"),
None => "".to_owned(),
};
if auth.is_empty() {
write!(f, "{}://{}", &self.proxy_type, &self.addr)
} else {
write!(f, "{}://{}@{}", &self.proxy_type, auth, &self.addr)
}
}
}
impl TryFrom<&str> for ArgProxy {
type Error = Error;
fn try_from(s: &str) -> Result<Self> {
if s == "none" {
return Ok(ArgProxy {
proxy_type: ProxyType::None,
addr: "0.0.0.0:0".parse().unwrap(),
credentials: None,
});
}
let e = format!("`{s}` is not a valid proxy URL");
let url = url::Url::parse(s).map_err(|_| Error::from(&e))?;
let e = format!("`{s}` does not contain a host");
let host = url.host_str().ok_or(Error::from(e))?;
let e = format!("`{s}` does not contain a port");
let port = url.port_or_known_default().ok_or(Error::from(&e))?;
let e2 = format!("`{host}` does not resolve to a usable IP address");
let addr = (host, port).to_socket_addrs()?.next().ok_or(Error::from(&e2))?;
let credentials = if url.username() == "" && url.password().is_none() {
None
} else {
use percent_encoding::percent_decode;
let username = percent_decode(url.username().as_bytes()).decode_utf8()?;
let password = percent_decode(url.password().unwrap_or("").as_bytes()).decode_utf8()?;
Some(UserKey::new(username, password))
};
let proxy_type = url.scheme().to_ascii_lowercase().as_str().try_into()?;
Ok(ArgProxy {
proxy_type,
addr,
credentials,
})
}
}
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default, serde::Serialize, serde::Deserialize)]
pub enum ProxyType {
Http = 0,
Socks4,
#[default]
Socks5,
None,
}
impl TryFrom<&str> for ProxyType {
type Error = Error;
fn try_from(value: &str) -> Result<Self> {
match value {
"http" => Ok(ProxyType::Http),
"socks4" => Ok(ProxyType::Socks4),
"socks5" => Ok(ProxyType::Socks5),
"none" => Ok(ProxyType::None),
scheme => Err(Error::from(&format!("`{scheme}` is an invalid proxy type"))),
}
}
}
impl std::fmt::Display for ProxyType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ProxyType::Socks4 => write!(f, "socks4"),
ProxyType::Socks5 => write!(f, "socks5"),
ProxyType::Http => write!(f, "http"),
ProxyType::None => write!(f, "none"),
}
}
}