2024-02-01 19:15:32 +08:00
|
|
|
use crate::{Error, Result};
|
|
|
|
use socks5_impl::protocol::UserKey;
|
2024-04-07 21:12:20 +02:00
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
use std::ffi::OsString;
|
|
|
|
|
|
|
|
use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
|
2024-02-01 19:15:32 +08:00
|
|
|
|
|
|
|
#[derive(Debug, Clone, clap::Parser)]
|
2024-02-10 14:45:44 +08:00
|
|
|
#[command(author, version, about = "Tunnel interface to proxy.", long_about = None)]
|
2024-02-01 19:15:32 +08:00
|
|
|
pub struct Args {
|
|
|
|
/// Proxy URL in the form proto://[username[:password]@]host:port,
|
2024-04-08 19:23:13 +08:00
|
|
|
/// where proto is one of socks4, socks5, http.
|
|
|
|
/// Username and password are encoded in percent encoding. For example:
|
2024-02-01 19:15:32 +08:00
|
|
|
/// socks5://myname:password@127.0.0.1:1080
|
|
|
|
#[arg(short, long, value_parser = ArgProxy::from_url, value_name = "URL")]
|
|
|
|
pub proxy: ArgProxy,
|
|
|
|
|
2024-02-25 16:59:18 +08:00
|
|
|
/// Name of the tun interface, such as tun0, utun4, etc.
|
|
|
|
/// If this option is not provided, the OS will generate a random one.
|
2024-04-06 23:21:50 +08:00
|
|
|
#[arg(short, long, value_name = "name", conflicts_with = "tun_fd", value_parser = validate_tun)]
|
2024-02-25 16:59:18 +08:00
|
|
|
pub tun: Option<String>,
|
2024-02-01 19:15:32 +08:00
|
|
|
|
|
|
|
/// File descriptor of the tun interface
|
|
|
|
#[arg(long, value_name = "fd", conflicts_with = "tun")]
|
|
|
|
pub tun_fd: Option<i32>,
|
|
|
|
|
2024-04-03 14:26:46 +00:00
|
|
|
/// Create a tun interface in a newly created unprivileged namespace
|
|
|
|
/// while maintaining proxy connectivity via the global network namespace.
|
2024-04-07 21:12:20 +02:00
|
|
|
#[cfg(target_os = "linux")]
|
2024-04-03 14:26:46 +00:00
|
|
|
#[arg(long)]
|
|
|
|
pub unshare: bool,
|
|
|
|
|
|
|
|
/// File descriptor for UNIX datagram socket meant to transfer
|
|
|
|
/// network sockets from global namespace to the new one.
|
|
|
|
/// See `unshare(1)`, `namespaces(7)`, `sendmsg(2)`, `unix(7)`.
|
2024-04-07 21:12:20 +02:00
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
#[arg(long, value_name = "fd", hide(true))]
|
2024-04-03 14:26:46 +00:00
|
|
|
pub socket_transfer_fd: Option<i32>,
|
|
|
|
|
2024-04-07 21:12:20 +02:00
|
|
|
/// Specify a command to run with root-like capabilities in the new namespace
|
|
|
|
/// when using `--unshare`.
|
2024-04-03 14:26:46 +00:00
|
|
|
/// This could be useful to start additional daemons, e.g. `openvpn` instance.
|
2024-04-07 21:12:20 +02:00
|
|
|
#[cfg(target_os = "linux")]
|
2024-04-03 14:26:46 +00:00
|
|
|
#[arg(requires = "unshare")]
|
|
|
|
pub admin_command: Vec<OsString>,
|
|
|
|
|
2024-02-01 19:15:32 +08:00
|
|
|
/// IPv6 enabled
|
|
|
|
#[arg(short = '6', long)]
|
|
|
|
pub ipv6_enabled: bool,
|
|
|
|
|
2024-02-25 16:59:18 +08:00
|
|
|
/// Routing and system setup, which decides whether to setup the routing and system configuration.
|
2024-04-03 14:26:46 +00:00
|
|
|
/// This option is only available on Linux and requires root-like privileges. See `capabilities(7)`.
|
2024-04-06 23:21:50 +08:00
|
|
|
#[arg(short, long, default_value = if cfg!(target_os = "linux") { "false" } else { "true" })]
|
2024-02-01 19:15:32 +08:00
|
|
|
pub setup: bool,
|
|
|
|
|
|
|
|
/// DNS handling strategy
|
|
|
|
#[arg(short, long, value_name = "strategy", value_enum, default_value = "direct")]
|
|
|
|
pub dns: ArgDns,
|
|
|
|
|
|
|
|
/// DNS resolver address
|
|
|
|
#[arg(long, value_name = "IP", default_value = "8.8.8.8")]
|
|
|
|
pub dns_addr: IpAddr,
|
|
|
|
|
|
|
|
/// IPs used in routing setup which should bypass the tunnel
|
|
|
|
#[arg(short, long, value_name = "IP")]
|
|
|
|
pub bypass: Vec<IpAddr>,
|
|
|
|
|
2024-03-18 13:12:30 +08:00
|
|
|
/// TCP timeout in seconds
|
|
|
|
#[arg(long, value_name = "seconds", default_value = "600")]
|
|
|
|
pub tcp_timeout: u64,
|
|
|
|
|
2024-04-03 14:13:18 +00:00
|
|
|
/// UDP timeout in seconds
|
|
|
|
#[arg(long, value_name = "seconds", default_value = "10")]
|
|
|
|
pub udp_timeout: u64,
|
|
|
|
|
2024-02-01 19:15:32 +08:00
|
|
|
/// Verbosity level
|
|
|
|
#[arg(short, long, value_name = "level", value_enum, default_value = "info")]
|
|
|
|
pub verbosity: ArgVerbosity,
|
|
|
|
}
|
|
|
|
|
2024-04-06 23:21:50 +08:00
|
|
|
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())
|
|
|
|
}
|
|
|
|
|
2024-02-01 19:15:32 +08:00
|
|
|
impl Default for Args {
|
|
|
|
fn default() -> Self {
|
2024-04-06 23:21:50 +08:00
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
let setup = false;
|
|
|
|
#[cfg(not(target_os = "linux"))]
|
|
|
|
let setup = true;
|
2024-02-01 19:15:32 +08:00
|
|
|
Args {
|
|
|
|
proxy: ArgProxy::default(),
|
2024-02-25 16:59:18 +08:00
|
|
|
tun: None,
|
2024-02-01 19:15:32 +08:00
|
|
|
tun_fd: None,
|
2024-04-07 21:12:20 +02:00
|
|
|
#[cfg(target_os = "linux")]
|
2024-04-03 14:26:46 +00:00
|
|
|
unshare: false,
|
2024-04-07 21:12:20 +02:00
|
|
|
#[cfg(target_os = "linux")]
|
2024-04-03 14:26:46 +00:00
|
|
|
socket_transfer_fd: None,
|
2024-04-07 21:12:20 +02:00
|
|
|
#[cfg(target_os = "linux")]
|
2024-04-03 14:26:46 +00:00
|
|
|
admin_command: Vec::new(),
|
2024-02-01 19:15:32 +08:00
|
|
|
ipv6_enabled: false,
|
2024-04-06 23:21:50 +08:00
|
|
|
setup,
|
2024-02-01 19:15:32 +08:00
|
|
|
dns: ArgDns::default(),
|
|
|
|
dns_addr: "8.8.8.8".parse().unwrap(),
|
|
|
|
bypass: vec![],
|
2024-03-18 13:12:30 +08:00
|
|
|
tcp_timeout: 600,
|
2024-04-03 14:13:18 +00:00
|
|
|
udp_timeout: 10,
|
2024-02-01 19:15:32 +08:00
|
|
|
verbosity: ArgVerbosity::Info,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Args {
|
2024-04-06 23:21:50 +08:00
|
|
|
#[allow(clippy::let_and_return)]
|
2024-02-01 19:15:32 +08:00
|
|
|
pub fn parse_args() -> Self {
|
|
|
|
use clap::Parser;
|
2024-04-06 23:21:50 +08:00
|
|
|
let args = Self::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
|
2024-02-01 19:15:32 +08:00
|
|
|
}
|
|
|
|
|
2024-02-12 21:36:18 +08:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn tun_fd(&mut self, tun_fd: Option<i32>) -> &mut Self {
|
|
|
|
self.tun_fd = tun_fd;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn verbosity(&mut self, verbosity: ArgVerbosity) -> &mut Self {
|
|
|
|
self.verbosity = verbosity;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn tun(&mut self, tun: String) -> &mut Self {
|
2024-02-25 16:59:18 +08:00
|
|
|
self.tun = Some(tun);
|
2024-02-12 21:36:18 +08:00
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn dns_addr(&mut self, dns_addr: IpAddr) -> &mut Self {
|
|
|
|
self.dns_addr = dns_addr;
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bypass(&mut self, bypass: IpAddr) -> &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
|
2024-02-01 19:15:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-07 23:32:51 +08:00
|
|
|
#[repr(C)]
|
2024-02-01 19:15:32 +08:00
|
|
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
|
|
|
pub enum ArgVerbosity {
|
2024-02-07 23:32:51 +08:00
|
|
|
Off = 0,
|
2024-02-01 19:15:32 +08:00
|
|
|
Error,
|
|
|
|
Warn,
|
|
|
|
#[default]
|
|
|
|
Info,
|
|
|
|
Debug,
|
|
|
|
Trace,
|
|
|
|
}
|
|
|
|
|
2024-02-07 23:32:51 +08:00
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-01 19:15:32 +08:00
|
|
|
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"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// DNS query handling strategy
|
|
|
|
/// - Virtual: Use a virtual DNS server to handle DNS queries, also known as Fake-IP mode
|
|
|
|
/// - OverTcp: Use TCP to send DNS queries to the DNS server
|
|
|
|
/// - Direct: Do not handle DNS by relying on DNS server bypassing
|
2024-02-07 23:32:51 +08:00
|
|
|
#[repr(C)]
|
2024-02-01 19:15:32 +08:00
|
|
|
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, clap::ValueEnum)]
|
|
|
|
pub enum ArgDns {
|
2024-02-07 23:32:51 +08:00
|
|
|
Virtual = 0,
|
2024-02-01 19:15:32 +08:00
|
|
|
OverTcp,
|
|
|
|
#[default]
|
|
|
|
Direct,
|
|
|
|
}
|
|
|
|
|
2024-02-07 23:32:51 +08:00
|
|
|
#[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")),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-01 19:15:32 +08:00
|
|
|
#[derive(Clone, Debug)]
|
|
|
|
pub struct ArgProxy {
|
|
|
|
pub proxy_type: ProxyType,
|
|
|
|
pub addr: SocketAddr,
|
|
|
|
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,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:36:36 +08:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-01 19:15:32 +08:00
|
|
|
impl ArgProxy {
|
|
|
|
pub fn from_url(s: &str) -> Result<ArgProxy> {
|
2024-04-03 14:20:05 +00:00
|
|
|
if s == "none" {
|
|
|
|
return Ok(ArgProxy {
|
|
|
|
proxy_type: ProxyType::None,
|
|
|
|
addr: "0.0.0.0:0".parse().unwrap(),
|
|
|
|
credentials: None,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2024-02-01 19:15:32 +08:00
|
|
|
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 mut url_host = String::from(host);
|
|
|
|
let e = format!("`{s}` does not contain a port");
|
|
|
|
let port = url.port().ok_or(Error::from(&e))?;
|
|
|
|
url_host.push(':');
|
|
|
|
url_host.push_str(port.to_string().as_str());
|
|
|
|
|
|
|
|
let e = format!("`{host}` could not be resolved");
|
|
|
|
let mut addr_iter = url_host.to_socket_addrs().map_err(|_| Error::from(&e))?;
|
|
|
|
|
|
|
|
let e = format!("`{host}` does not resolve to a usable IP address");
|
|
|
|
let addr = addr_iter.next().ok_or(Error::from(&e))?;
|
|
|
|
|
|
|
|
let credentials = if url.username() == "" && url.password().is_none() {
|
|
|
|
None
|
|
|
|
} else {
|
2024-04-08 18:37:56 +08:00
|
|
|
use percent_encoding::percent_decode;
|
2024-04-08 19:23:13 +08:00
|
|
|
let username = percent_decode(url.username().as_bytes()).decode_utf8()?;
|
|
|
|
let password = percent_decode(url.password().unwrap_or("").as_bytes()).decode_utf8()?;
|
2024-02-01 19:15:32 +08:00
|
|
|
Some(UserKey::new(username, password))
|
|
|
|
};
|
|
|
|
|
|
|
|
let scheme = url.scheme();
|
|
|
|
|
|
|
|
let proxy_type = match url.scheme().to_ascii_lowercase().as_str() {
|
|
|
|
"socks4" => Some(ProxyType::Socks4),
|
|
|
|
"socks5" => Some(ProxyType::Socks5),
|
|
|
|
"http" => Some(ProxyType::Http),
|
|
|
|
_ => None,
|
|
|
|
}
|
|
|
|
.ok_or(Error::from(&format!("`{scheme}` is an invalid proxy type")))?;
|
|
|
|
|
|
|
|
Ok(ArgProxy {
|
|
|
|
proxy_type,
|
|
|
|
addr,
|
|
|
|
credentials,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-02-11 12:36:36 +08:00
|
|
|
#[repr(C)]
|
2024-02-01 19:15:32 +08:00
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Default)]
|
|
|
|
pub enum ProxyType {
|
2024-02-11 12:36:36 +08:00
|
|
|
Http = 0,
|
2024-02-01 19:15:32 +08:00
|
|
|
Socks4,
|
|
|
|
#[default]
|
|
|
|
Socks5,
|
2024-04-03 14:20:05 +00:00
|
|
|
None,
|
2024-02-01 19:15:32 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
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"),
|
2024-04-03 14:20:05 +00:00
|
|
|
ProxyType::None => write!(f, "none"),
|
2024-02-01 19:15:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|