tun2proxy/src/args.rs

374 lines
11 KiB
Rust
Raw Normal View History

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>,
/// 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")]
#[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))]
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`.
/// This could be useful to start additional daemons, e.g. `openvpn` instance.
2024-04-07 21:12:20 +02:00
#[cfg(target_os = "linux")]
#[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.
/// 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")]
unshare: false,
2024-04-07 21:12:20 +02:00
#[cfg(target_os = "linux")]
socket_transfer_fd: None,
2024-04-07 21:12:20 +02:00
#[cfg(target_os = "linux")]
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 {
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
}
}
}