From cf0737a832ffe88f075afe9c10eee2a98d82d5d5 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 21 Feb 2022 02:05:41 +0900 Subject: [PATCH 001/169] sync cargo lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 8dfc1f9a..d9255888 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -537,7 +537,7 @@ dependencies = [ [[package]] name = "realm" -version = "1.2.1" +version = "1.5.0" dependencies = [ "futures", "libc", From 42b8f42736deb22b4196b1d830c097b0429e0e24 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 8 Jul 2021 01:22:45 +0900 Subject: [PATCH 002/169] refactor --- Cargo.lock | 119 ++++++++-------------------- Cargo.toml | 16 ++-- rustfmt.toml | 4 + src/dns.rs | 33 ++++++++ src/lib.rs | 197 ----------------------------------------------- src/main.rs | 14 ++-- src/relay.rs | 139 ++++----------------------------- src/resolver.rs | 64 --------------- src/tcp.rs | 146 +++++++++++++++++++++++++++++++++++ src/udp.rs | 119 +++++++++++----------------- src/utils.rs | 62 +++++++++++++++ src/zero_copy.rs | 112 --------------------------- 12 files changed, 355 insertions(+), 670 deletions(-) create mode 100644 rustfmt.toml create mode 100644 src/dns.rs delete mode 100644 src/lib.rs delete mode 100644 src/resolver.rs create mode 100644 src/tcp.rs create mode 100644 src/utils.rs delete mode 100644 src/zero_copy.rs diff --git a/Cargo.lock b/Cargo.lock index d9255888..2975425d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,7 +1,5 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 - [[package]] name = "ansi_term" version = "0.11.0" @@ -102,9 +100,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f55667319111d593ba876406af7c409c0ebb44dc4be6132a783ccf163ea14c1" +checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" dependencies = [ "futures-channel", "futures-core", @@ -117,9 +115,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c2dd2df839b57db9ab69c2c9d8f3e8c81984781937fe2807dc6dcf3b2ad2939" +checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" dependencies = [ "futures-core", "futures-sink", @@ -127,15 +125,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15496a72fabf0e62bdc3df11a59a3787429221dd0710ba8ef163d6f7a9112c94" +checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" [[package]] name = "futures-executor" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "891a4b7b96d84d5940084b2a37632dd65deeae662c114ceaa2c879629c9c0ad1" +checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" dependencies = [ "futures-core", "futures-task", @@ -144,16 +142,17 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71c2c65c57704c32f5241c1223167c2c3294fd34ac020c807ddbe6db287ba59" +checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" [[package]] name = "futures-macro" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea405816a5139fb39af82c2beb921d52143f556038378d6db21183a5c37fbfb7" +checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" dependencies = [ + "autocfg", "proc-macro-hack", "proc-macro2", "quote", @@ -162,22 +161,23 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85754d98985841b7d4f5e8e6fbfa4a4ac847916893ec511a2917ccd8525b8bb3" +checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" [[package]] name = "futures-task" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa189ef211c15ee602667a6fcfe1c1fd9e07d42250d2156382820fba33c9df80" +checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" [[package]] name = "futures-util" -version = "0.3.13" +version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1812c7ab8aedf8d6f2701a43e1243acdbcc2b36ab26e2ad421eb99ac963d96d1" +checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" dependencies = [ + "autocfg", "futures-channel", "futures-core", "futures-io", @@ -382,9 +382,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "parking_lot" @@ -435,30 +435,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -537,13 +513,14 @@ dependencies = [ [[package]] name = "realm" -version = "1.5.0" +version = "1.3.0" dependencies = [ + "clap", "futures", + "lazy_static", "libc", "serde", "serde_json", - "structopt", "tokio", "trust-dns-resolver", ] @@ -581,18 +558,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "558dc50e1a5a5fa7112ca2ce4effcb321b0300c0d4ccf0776a9f60cd89031171" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.125" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b093b7a2bb58203b5da3056c05b4ec1fed827dcfdb37347a8841695263b3d06d" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2", "quote", @@ -612,9 +589,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ "libc", ] @@ -648,30 +625,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -[[package]] -name = "structopt" -version = "0.3.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5277acd7ee46e63e5168a80734c9f6ee81b1367a7d8772a2d765df2a3705d28c" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ba9cdfda491b814720b6b06e0cac513d922fc407582032e8706e9f137976f90" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "syn" version = "1.0.68" @@ -749,9 +702,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf7b11a536f46a809a8a9f0bb4237020f70ecbf115b842360afb127ea2fda57" +checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37" dependencies = [ "proc-macro2", "quote", @@ -857,12 +810,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 5f38f8b2..56066894 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "realm" -version = "1.5.0" +version = "1.3.0" authors = ["zhboner "] edition = "2018" @@ -8,9 +8,13 @@ edition = "2018" [dependencies] libc = "0.2" -tokio = { version = "1.8.1", features = ["full"] } -trust-dns-resolver = "0.20" futures = "0.3" -structopt = "0.3" -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0" + +clap = "2" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +tokio = { version = "1", features = ["full"] } +trust-dns-resolver = "0.20" + +lazy_static = "1" \ No newline at end of file diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000..d072c1f5 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,4 @@ +max_width = 80 +reorder_imports = false +reorder_modules = false +#fn_args_layout = "Vertical" diff --git a/src/dns.rs b/src/dns.rs new file mode 100644 index 00000000..2a5d14c7 --- /dev/null +++ b/src/dns.rs @@ -0,0 +1,33 @@ +use std::net::IpAddr; + +use tokio::io; +use tokio::runtime::Runtime; +use trust_dns_resolver::TokioAsyncResolver; +use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; +use lazy_static::lazy_static; + +use crate::utils; + +lazy_static! { + pub static ref DNS: TokioAsyncResolver = TokioAsyncResolver::tokio( + ResolverConfig::default(), + ResolverOpts::default() + ) + .unwrap(); +} + +pub fn resolve_sync(addr: &str) -> io::Result { + let rt = Runtime::new().unwrap(); + rt.block_on(resolve_async(addr)) +} + +pub async fn resolve_async(addr: &str) -> io::Result { + let res = DNS + .lookup_ip(addr) + .await + .map_err(|e| utils::new_io_err(&e.to_string()))? + .into_iter() + .next() + .unwrap(); + Ok(res) +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 32123726..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,197 +0,0 @@ -use serde::{Deserialize, Serialize}; -use std::fs::File; -use std::io::BufReader; -use std::path::PathBuf; -use std::str::FromStr; -use structopt::StructOpt; - -#[derive(StructOpt)] -#[structopt(name = "realm", about = "A high efficiency proxy tool.")] -pub struct Cli { - #[structopt(short = "l", long = "local")] - pub client: Option, - - #[structopt(short = "r", long = "remote")] - pub remote: Option, - - #[structopt( - short = "c", - long = "config", - parse(from_os_str), - name = "Optional config file", - conflicts_with_all = &["client", "remote"], - required_unless_all = &["client", "remote"] - )] - pub config_file: Option, -} - -pub struct RelayConfig { - pub listening_address: String, - pub listening_port: String, - pub remote_address: String, - pub remote_port: String, -} - -impl Default for RelayConfig { - fn default() -> RelayConfig { - RelayConfig { - listening_address: String::from("0.0.0.0"), - listening_port: String::from("1080"), - remote_address: String::from("127.0.0.1"), - remote_port: String::from("8080"), - } - } -} - -impl RelayConfig { - fn new(ld: String, lp: String, rd: String, rp: String) -> RelayConfig { - RelayConfig { - listening_address: ld, - listening_port: lp, - remote_address: rd, - remote_port: rp, - } - } -} - -#[derive(Serialize, Deserialize)] -pub struct ConfigFile { - pub listening_addresses: Vec, - pub listening_ports: Vec, - pub remote_addresses: Vec, - pub remote_ports: Vec, -} - -pub fn parse_arguments() -> Vec { - let input = Cli::from_args(); - let path = input.config_file; - if let Some(path) = path { - let configs = load_config(path); - return configs; - } - - let client = match input.client { - Some(client) => client, - None => panic!("No listening socket"), - }; - - let remote = match input.remote { - Some(remote) => remote, - None => panic!("No listening socket"), - }; - - let client_parse: Vec<&str> = client.rsplitn(2,":") - .collect::>() - .into_iter().rev().collect(); - if client_parse.len() != 2 { - panic!("client address is incorrect!"); - } - let listening_address = String::from_str(client_parse[0]).unwrap(); - - let remote_parse: Vec<&str> = remote.rsplitn(2,":") - .collect::>() - .into_iter().rev().collect(); - if remote_parse.len() != 2 { - panic!("remote address is incorrect!"); - } - - vec![RelayConfig { - listening_address: if listening_address == "" { - String::from("0.0.0.0") - } else { - listening_address - }, - listening_port: String::from_str(client_parse[1]).unwrap(), - remote_address: String::from_str(remote_parse[0]).unwrap(), - remote_port: String::from_str(remote_parse[1]).unwrap(), - }] -} - -fn ports2individuals(ports: Vec) -> Vec { - let mut output = vec![]; - - // Convert port ranges to individual ports - for lp in ports { - if lp.find("-").is_none() { - output.push(lp.parse::().unwrap()) - } else { - let ints: Vec<&str> = lp.split("-").collect(); - if ints.len() != 2 { - panic!("Invalid range") - } - let st = ints[0].parse::().unwrap(); - let end = ints[1].parse::().unwrap(); - if st > end { - panic!("Invalid range") - } - - for i in st..=end { - output.push(i); - } - } - } - output -} - -pub fn load_config(p: PathBuf) -> Vec { - // let path = Path::new(&p); - // let display = p.display(); - - let f = match File::open(&p) { - Err(e) => panic!("Could not open file {}: {}", p.display(), e), - Ok(f) => f, - }; - - let reader = BufReader::new(f); - let config: ConfigFile = serde_json::from_reader(reader).unwrap(); - - let listening_ports = ports2individuals(config.listening_ports); - let remote_ports = ports2individuals(config.remote_ports); - - // if listening_ports.len() != remote_ports.len() { - // panic!("Unmatched number of listening and remot ports") - // } - - // if config.listening_addresses.len() != 1 - // && config.listening_addresses.len() != listening_ports.len() - // { - // panic!("Unmatched listening address and ports") - // } - - // if config.remote_addresses.len() != 1 && config.remote_addresses.len() != remote_ports.len() { - // panic!("Unmatched remote address and ports") - // } - - let mut relay_configs = vec![]; - let total = listening_ports.len(); - - for i in 0..total { - let ld = match config.listening_addresses.get(i) { - Some(ld) => ld, - None => &config.listening_addresses[0], - }; - - let rd = match config.remote_addresses.get(i) { - Some(rd) => rd, - None => &config.remote_addresses[0], - }; - - let rp = match remote_ports.get(i) { - Some(rp) => rp, - None => &remote_ports[0], - }; - - let lp = match listening_ports.get(i) { - Some(lp) => lp, - None => &listening_ports[0], - }; - - relay_configs.push(RelayConfig::new( - ld.to_string(), - lp.to_string(), - rd.to_string(), - rp.to_string(), - )) - } - relay_configs -} diff --git a/src/main.rs b/src/main.rs index 61f3f2d1..fff5d5a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,11 @@ -use tokio; -mod relay; -mod resolver; +mod dns; +mod tcp; mod udp; - -#[cfg(target_os = "linux")] -mod zero_copy; +mod utils; +mod relay; #[tokio::main] async fn main() { - let relay_configs = realm::parse_arguments(); - relay::start_relay(relay_configs).await; + let eps = vec![utils::Endpoint::new("127.0.0.1:15000", "localhost:20000")]; + relay::run(eps).await } diff --git a/src/relay.rs b/src/relay.rs index 38ad35bb..9018b84d 100644 --- a/src/relay.rs +++ b/src/relay.rs @@ -1,135 +1,30 @@ +use std::net::SocketAddr; use futures::future::join_all; -use futures::future::try_join; -use futures::FutureExt; -use std::error::Error; -use std::net::{IpAddr, SocketAddr}; -use std::sync::{Arc, RwLock}; -use tokio; -use tokio::io; -use tokio::io::{AsyncWriteExt, AsyncReadExt}; -use tokio::net; +use tokio::io; +use tokio::net::TcpListener; -use crate::resolver; +use crate::tcp; use crate::udp; -use realm::RelayConfig; -use tokio::net::tcp::{ReadHalf, WriteHalf}; -use std::fs::read; - -// Initialize DNS recolver -// Set up channel between listener and resolver - -pub async fn start_relay(configs: Vec) { - let default_ip: IpAddr = String::from("0.0.0.0").parse::().unwrap(); - let remote_addrs: Vec = configs.iter().map(|x| x.remote_address.clone()).collect(); - - let mut remote_ips: Vec>> = Vec::new(); - for _ in 0..remote_addrs.len() { - remote_ips.push(Arc::new(RwLock::new(default_ip.clone()))) - } - let cloned_remote_ips = remote_ips.clone(); +use crate::utils::{Endpoint, RemoteAddr}; - tokio::spawn(resolver::resolve(remote_addrs, cloned_remote_ips)); - - // resolver::print_ips(&remote_ips); - - let mut iter = configs.into_iter().zip(remote_ips); - let mut runners = vec![]; - - while let Some((config, remote_ip)) = iter.next() { - runners.push(tokio::spawn(run(config, remote_ip))); +pub async fn run(eps: Vec) { + let mut workers = vec![]; + for ep in eps.into_iter() { + workers.push(tokio::spawn(proxy_tcp(ep.local, ep.remote.clone()))); + workers.push(tokio::spawn(proxy_udp(ep.local, ep.remote))) } - - join_all(runners).await; + join_all(workers).await; } -pub async fn run(config: RelayConfig, remote_ip: Arc>) { - let client_socket: SocketAddr = - format!("{}:{}", config.listening_address, config.listening_port) - .parse() - .unwrap(); - let tcp_listener = net::TcpListener::bind(&client_socket).await.unwrap(); - - let mut remote_socket: SocketAddr = - format!("{}:{}", remote_ip.read().unwrap(), config.remote_port) - .parse() - .unwrap(); - - // Start UDP connection - let udp_remote_ip = remote_ip.clone(); - tokio::spawn(udp::transfer_udp( - client_socket.clone(), - remote_socket.port(), - udp_remote_ip, - )); - - // Start TCP connection - loop { - match tcp_listener.accept().await { - Ok((inbound, _)) => { - inbound.set_nodelay(true).unwrap(); - remote_socket = format!("{}:{}", &(remote_ip.read().unwrap()), config.remote_port) - .parse() - .unwrap(); - let transfer = transfer_tcp(inbound, remote_socket.clone()).map(|r| { - if let Err(_) = r { - return; - } - }); - tokio::spawn(transfer); - } - Err(e) => { - println!( - "TCP forward error {}:{}, {}", - config.remote_address, config.remote_port, e - ); - break; - } - } +async fn proxy_tcp(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { + let lis = TcpListener::bind(&local).await.expect("unable to bind"); + while let Ok((stream, _)) = lis.accept().await { + tokio::spawn(tcp::proxy(stream, remote.clone())); } -} - -async fn transfer_tcp( - mut inbound: net::TcpStream, - remote_socket: SocketAddr, -) -> Result<(), Box> { - #[cfg(target_os = "linux")] - use crate::zero_copy::zero_copy as copy_data; - - let mut outbound = net::TcpStream::connect(remote_socket).await?; - outbound.set_nodelay(true).unwrap(); - let (mut ri, mut wi) = inbound.split(); - let (mut ro, mut wo) = outbound.split(); - - let client_to_server = async { - // io::copy(&mut ri, &mut wo).await?; - copy_data(&mut ri, &mut wo).await?; - wo.shutdown().await - }; - - let server_to_client = async { - // io::copy(&mut ro, &mut wi).await?; - copy_data(&mut ro, &mut wi).await?; - wi.shutdown().await - }; - - try_join(client_to_server, server_to_client).await?; - Ok(()) } -#[cfg(not(target_os = "linux"))] -async fn copy_data(reader: &mut ReadHalf<'_>, writer: &mut WriteHalf<'_>) -> Result<(), std::io::Error> { - let mut buf = vec![0u8; 0x4000]; - let mut n: usize; - - loop { - n = reader.read(&mut buf).await?; - if n == 0 { - break; - } - writer.write_all(&buf[..n]).await?; - } - writer.flush().await?; - Ok(()) +async fn proxy_udp(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { + udp::proxy(local, remote).await } diff --git a/src/resolver.rs b/src/resolver.rs deleted file mode 100644 index 4f041003..00000000 --- a/src/resolver.rs +++ /dev/null @@ -1,64 +0,0 @@ -use std::net; -use std::sync::{Arc, RwLock}; -use std::time::Duration; -use tokio::time::sleep; -use trust_dns_resolver::config::*; -use trust_dns_resolver::TokioAsyncResolver; - -fn need_resolve(addr: &str) -> bool { - addr.parse::().is_err() -} - -async fn resolve_single(resolver: &TokioAsyncResolver, addr: &String) -> Option { - if !need_resolve(&addr) { - return Some(addr.parse::().unwrap()); - } - - let remote_addr = format!("{}.", addr); - let res = resolver.lookup_ip(remote_addr).await.unwrap(); - - match res.iter().find(|ip| ip.is_ipv4()) { - Some(ip_v4) => Some(ip_v4), - None => { - if let Some(ip_v6) = res.iter().find(|ip| ip.is_ipv6()) { - Some(ip_v6) - } else { - println!("Cannot resolve {}", addr); - return None; - } - } - } -} - -pub async fn resolve(addr_list: Vec, ip_list: Vec>>) { - let resolver = - async { TokioAsyncResolver::tokio(ResolverConfig::default(), ResolverOpts::default()) } - .await - .unwrap(); - let cache = "0.0.0.0".parse::().unwrap(); - let size = addr_list.len(); - let mut cache_list = vec![cache.clone(); size]; - loop { - for (i, addr) in addr_list.iter().enumerate() { - if let Some(new_ip) = resolve_single(&resolver, addr).await { - if new_ip != cache_list[i] { - cache_list[i] = new_ip; - let mut w = ip_list[i].write().unwrap(); - *w = new_ip; - drop(w); - // println!("Resolved {}: {}", addr, new_ip); - } - } else { - println!("Cannot resolve address {}", addr); - } - } - - sleep(Duration::from_secs(60)).await; - } -} - -pub fn print_ips(ip_list: &Vec>>) { - for ip in ip_list { - println!("{}", ip.read().unwrap()); - } -} diff --git a/src/tcp.rs b/src/tcp.rs new file mode 100644 index 00000000..975d74d5 --- /dev/null +++ b/src/tcp.rs @@ -0,0 +1,146 @@ +use futures::try_join; + +use tokio::io::{self, AsyncWriteExt}; +use tokio::net::tcp::{ReadHalf, WriteHalf}; +use tokio::net::TcpStream; + +use crate::utils::{self, RemoteAddr}; + +pub async fn proxy( + mut inbound: TcpStream, + remote: RemoteAddr, +) -> io::Result<()> { + let mut outbound = TcpStream::connect(remote.to_sockaddr().await?).await?; + inbound.set_nodelay(true)?; + outbound.set_nodelay(true)?; + let (mut ri, mut wi) = inbound.split(); + let (mut ro, mut wo) = outbound.split(); + + let _ = try_join!(copy(&mut ri, &mut wo), copy(&mut ro, &mut wi)); + + Ok(()) +} + +const BUFFERSIZE: usize = if cfg!(not(target_os = "linux")) { + 0x4000 // 16k read/write buffer +} else { + 0x10000 // 64k pipe buffer +}; + +#[cfg(not(target_os = "linux"))] +async fn copy(r: &mut ReadHalf<'_>, w: &mut WriteHalf<'_>) -> io::Result<()> { + use io::AsyncReadExt; + let mut buf = vec![0u8; BUFFERSIZE]; + let mut n: usize; + loop { + n = r.read(&mut buf).await?; + if n == 0 { + break; + } + w.write(&buf[..n]).await?; + w.flush().await?; + } + w.shutdown().await?; + Ok(()) +} + +// zero copy +#[cfg(target_os = "linux")] +async fn copy(r: &mut ReadHalf<'_>, w: &mut WriteHalf<'_>) -> io::Result<()> { + use std::os::unix::prelude::AsRawFd; + // create pipe + let pipe = Pipe::create()?; + let (rpipe, wpipe) = (pipe.0, pipe.1); + // get raw fd + let rfd = r.as_ref().as_raw_fd(); + let wfd = w.as_ref().as_raw_fd(); + let mut n: usize = 0; + let mut done = false; + + 'LOOP: loop { + // read until the socket buffer is empty + // or the pipe is filled + r.as_ref().readable().await?; + while n < BUFFERSIZE { + match splice_n(rfd, wpipe, BUFFERSIZE - n) { + x if x > 0 => n += x as usize, + x if x == 0 => { + done = true; + break; + } + x if x < 0 && is_wouldblock() => break, + _ => break 'LOOP, + } + } + // write until the pipe is empty + while n > 0 { + w.as_ref().writable().await?; + match splice_n(rpipe, wfd, n) { + x if x > 0 => n -= x as usize, + x if x < 0 && is_wouldblock() => { + // clear readiness (EPOLLOUT) + let _ = r.as_ref().try_write(&[0u8; 0]); + } + _ => break 'LOOP, + } + } + // complete + if done { + break; + } + // clear readiness (EPOLLIN) + let _ = r.as_ref().try_read(&mut [0u8; 0]); + } + + w.shutdown().await?; + Ok(()) +} + +#[cfg(target_os = "linux")] +struct Pipe(i32, i32); + +#[cfg(target_os = "linux")] +impl Drop for Pipe { + fn drop(&mut self) { + unsafe { + libc::close(self.0); + libc::close(self.1); + } + } +} + +#[cfg(target_os = "linux")] +impl Pipe { + fn create() -> io::Result { + use libc::{c_int, O_NONBLOCK}; + let mut pipes = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); + unsafe { + if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 { + return Err(utils::new_io_err("failed to create a pipe")); + } + Ok(Pipe(pipes.assume_init()[0], pipes.assume_init()[1])) + } + } +} + +#[cfg(target_os = "linux")] +fn splice_n(r: i32, w: i32, n: usize) -> isize { + use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; + unsafe { + libc::splice( + r, + 0 as *mut loff_t, + w, + 0 as *mut loff_t, + n, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK, + ) + } +} + +#[cfg(target_os = "linux")] +fn is_wouldblock() -> bool { + use libc::{EAGAIN, EWOULDBLOCK}; + let errno = unsafe { *libc::__errno_location() }; + errno == EWOULDBLOCK || errno == EAGAIN +} diff --git a/src/udp.rs b/src/udp.rs index 3a5bab60..6d9a4c61 100644 --- a/src/udp.rs +++ b/src/udp.rs @@ -1,92 +1,61 @@ -use std::io; use std::time::Duration; -use std::net::{IpAddr, SocketAddr}; -use std::sync::{Arc, RwLock}; +use std::net::SocketAddr; +use std::sync::Arc; use std::collections::HashMap; +use tokio::io; use tokio::net::UdpSocket; -use tokio::time::timeout; +use tokio::sync::oneshot; +use tokio::time::sleep; -const BUFFERSIZE: usize = 0x4000; -const TIMEOUT: Duration = Duration::from_secs(20); +use crate::utils::RemoteAddr; -// client <--> allocated socket -type SockMap = Arc>>>; +const BUFFERSIZE: usize = 2048; +const TIMEOUT: Duration = Duration::from_secs(60 * 15); -pub async fn transfer_udp( - local_addr: SocketAddr, - remote_port: u16, - remote_ip: Arc>, -) -> io::Result<()> { - let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); - let local_sock = Arc::new(UdpSocket::bind(&local_addr).await.unwrap()); - let mut buf = vec![0u8; BUFFERSIZE]; +pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { + // records (client_addr, alloc_socket) + let mut record = HashMap::new(); + let local_socket = Arc::new(UdpSocket::bind(&local).await.unwrap()); + let mut buf = vec![0u8; BUFFERSIZE]; loop { - let (n, client_addr) = local_sock.recv_from(&mut buf).await?; - - let remote_addr = format!("{}:{}", remote_ip.read().unwrap(), remote_port) - .parse::() - .unwrap(); - - // the socket associated with a unique client - let alloc_sock = match get_socket(&sock_map, &client_addr) { - Some(x) => x, - None => alloc_new_socket( - &sock_map, client_addr, &remote_addr, local_sock.clone() - ).await - }; - - alloc_sock.send_to(&buf[..n], &remote_addr).await?; + tokio::select! { + _ = async { + let (n, client_addr) = local_socket.recv_from(&mut buf).await?; + if !record.contains_key(&client_addr) { + // pick a random port + let alloc_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap()); + let (emit, cancel) = oneshot::channel::<()>(); + tokio::spawn(send_back( + client_addr, local_socket.clone(), alloc_socket.clone(), cancel + )); + record.insert(client_addr, (alloc_socket,emit)); + } + let (alloc_socket, _) = record.get(&client_addr).unwrap(); + let remote_addr = remote.to_sockaddr_as_ref().await?; + alloc_socket.send_to(&buf[..n], &remote_addr).await?; + Ok::<_, io::Error>(()) + } => {} + _ = async { sleep(TIMEOUT).await } => record.clear() + } } } async fn send_back( - sock_map: SockMap, client_addr: SocketAddr, - local_sock: Arc, - alloc_sock: Arc, -){ + local_socket: Arc, + alloc_socket: Arc, + cancel: oneshot::Receiver<()>, +) -> io::Result<()> { let mut buf = vec![0u8; BUFFERSIZE]; - - while let Ok(Ok((n, _))) = timeout( - TIMEOUT, alloc_sock.recv_from(&mut buf) - ).await { - if local_sock.send_to(&buf[..n], &client_addr).await.is_err() { - break; - } + tokio::select! { + ret = async { + loop { + let (n, _) = alloc_socket.recv_from(&mut buf).await?; + local_socket.send_to(&buf[..n], &client_addr).await?; + } + } => { ret } + _ = cancel => Ok(()) } - - sock_map.write().unwrap().remove(&client_addr); -} - -#[inline] -fn get_socket( - sock_map: &SockMap, - client_addr: &SocketAddr, -) -> Option> { - let alloc_sock = sock_map.read().unwrap(); - alloc_sock.get(client_addr).cloned() - // drop the lock -} - -async fn alloc_new_socket( - sock_map: &SockMap, - client_addr: SocketAddr, - remote_addr: &SocketAddr, - local_sock: Arc -) -> Arc{ - // pick a random port - let alloc_sock = Arc::new(if remote_addr.is_ipv4(){ - UdpSocket::bind("0.0.0.0:0").await.unwrap() - } else { - UdpSocket::bind("[::]:0").await.unwrap() - }); - - // new send back task - tokio::spawn(send_back(sock_map.clone(), client_addr, local_sock, alloc_sock.clone())); - - sock_map.write().unwrap().insert(client_addr, alloc_sock.clone()); - alloc_sock - // drop the lock } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 00000000..e04aa393 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,62 @@ +use std::net::{SocketAddr, ToSocketAddrs}; + +use tokio::io; + +use crate::dns; + +#[derive(Clone)] +pub enum RemoteAddr { + SocketAddr(SocketAddr), + DomainName(String, u16), +} + +pub struct Endpoint { + pub local: SocketAddr, + pub remote: RemoteAddr, +} + +pub fn new_io_err(e: &str) -> io::Error { + io::Error::new(io::ErrorKind::Other, e) +} + +impl RemoteAddr { + pub async fn to_sockaddr(self) -> io::Result { + match self { + Self::SocketAddr(sockaddr) => Ok(sockaddr), + Self::DomainName(addr, port) => { + let ip = dns::resolve_async(&addr).await?; + Ok(SocketAddr::new(ip, port)) + } + } + } + pub async fn to_sockaddr_as_ref(&self) -> io::Result { + match self { + Self::SocketAddr(sockaddr) => Ok(*sockaddr), + Self::DomainName(addr, port) => { + let ip = dns::resolve_async(addr).await?; + Ok(SocketAddr::new(ip, *port)) + } + } + } +} + +impl Endpoint { + pub fn new(local: &str, remote: &str) -> Self { + let local = local + .to_socket_addrs() + .expect("invalid local address") + .next() + .unwrap(); + let remote = if let Ok(mut sockaddr) = remote.to_socket_addrs() { + RemoteAddr::SocketAddr(sockaddr.next().unwrap()) + } else { + let mut iter = remote.splitn(2, ':'); + let addr = iter.next().unwrap().to_string(); + let port = iter.next().unwrap().parse::().unwrap(); + // test addr + let _ = dns::resolve_sync(&addr).unwrap(); + RemoteAddr::DomainName(addr, port) + }; + Endpoint { local, remote } + } +} diff --git a/src/zero_copy.rs b/src/zero_copy.rs deleted file mode 100644 index f1e42a8e..00000000 --- a/src/zero_copy.rs +++ /dev/null @@ -1,112 +0,0 @@ -use std::io; -use std::ops::Drop; -use std::os::unix::io::AsRawFd; - -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt}; - -const PIPE_BUF_SIZE: usize = 0x10000; - -struct Pipe(i32, i32); - -impl Drop for Pipe { - fn drop(&mut self) { - unsafe { - libc::close(self.0); - libc::close(self.1); - } - } -} - -impl Pipe { - fn create() -> io::Result { - use libc::{c_int, O_NONBLOCK}; - let mut pipes = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); - unsafe { - if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 { - return Err(io::Error::new( - io::ErrorKind::Unsupported, - "failed to create a pipe", - )); - } - Ok(Pipe(pipes.assume_init()[0], pipes.assume_init()[1])) - } - } -} - -#[inline] -fn splice_n(r: i32, w: i32, n: usize) -> isize { - use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; - unsafe { - libc::splice( - r, - std::ptr::null_mut::(), - w, - std::ptr::null_mut::(), - n, - SPLICE_F_MOVE | SPLICE_F_NONBLOCK, - ) - } -} - -#[inline] -fn is_wouldblock() -> bool { - use libc::{EAGAIN, EWOULDBLOCK}; - let errno = unsafe { *libc::__errno_location() }; - errno == EWOULDBLOCK || errno == EAGAIN -} - -pub async fn zero_copy(mut r: R, mut w: W) -> io::Result<()> -where - X: AsRawFd, - Y: AsRawFd, - R: AsyncRead + AsRef + Unpin, - W: AsyncWrite + AsRef + Unpin, -{ - // create pipe - let pipe = Pipe::create()?; - let (rpipe, wpipe) = (pipe.0, pipe.1); - // get raw fd - let rfd = r.as_ref().as_raw_fd(); - let wfd = w.as_ref().as_raw_fd(); - let mut n: usize = 0; - let mut done = false; - - 'LOOP: loop { - // read until the socket buffer is empty - // or the pipe is filled - // clear readiness (EPOLLIN) - r.read(&mut [0u8; 0]).await?; - while n < PIPE_BUF_SIZE { - match splice_n(rfd, wpipe, PIPE_BUF_SIZE - n) { - x if x > 0 => n += x as usize, - // read EOF - // after this the read() syscall always returns 0 - x if x == 0 => { - done = true; - break; - } - // error occurs - x if x < 0 && is_wouldblock() => break, - _ => break 'LOOP, - } - } - // write until the pipe is empty - while n > 0 { - // clear readiness (EPOLLOUT) - w.write(&[0u8; 0]).await?; - match splice_n(rpipe, wfd, n) { - x if x > 0 => n -= x as usize, - // continue to write - x if x < 0 && is_wouldblock() => {}, - // error occurs - _ => break 'LOOP, - } - } - // complete - if done { - break; - } - } - - Ok(()) -} From 0cc4d58ce01ce31ae19da7ae10c5b1ae686397be Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 8 Jul 2021 01:47:20 +0900 Subject: [PATCH 003/169] refactor file tree --- src/main.rs | 6 +--- src/{ => relay}/dns.rs | 2 +- src/{relay.rs => relay/mod.rs} | 11 +++++-- src/{ => relay}/tcp.rs | 55 ++++------------------------------ src/{ => relay}/udp.rs | 2 +- src/{ => relay}/utils.rs | 4 +-- src/relay/zero_copy.rs | 47 +++++++++++++++++++++++++++++ 7 files changed, 64 insertions(+), 63 deletions(-) rename src/{ => relay}/dns.rs (97%) rename src/{relay.rs => relay/mod.rs} (86%) rename src/{ => relay}/tcp.rs (68%) rename src/{ => relay}/udp.rs (98%) rename src/{ => relay}/utils.rs (98%) create mode 100644 src/relay/zero_copy.rs diff --git a/src/main.rs b/src/main.rs index fff5d5a1..a6ac1897 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,7 @@ -mod dns; -mod tcp; -mod udp; -mod utils; mod relay; #[tokio::main] async fn main() { - let eps = vec![utils::Endpoint::new("127.0.0.1:15000", "localhost:20000")]; + let eps = vec![relay::Endpoint::new("127.0.0.1:15000", "localhost:20000")]; relay::run(eps).await } diff --git a/src/dns.rs b/src/relay/dns.rs similarity index 97% rename from src/dns.rs rename to src/relay/dns.rs index 2a5d14c7..b2fb5280 100644 --- a/src/dns.rs +++ b/src/relay/dns.rs @@ -6,7 +6,7 @@ use trust_dns_resolver::TokioAsyncResolver; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; use lazy_static::lazy_static; -use crate::utils; +use super::utils; lazy_static! { pub static ref DNS: TokioAsyncResolver = TokioAsyncResolver::tokio( diff --git a/src/relay.rs b/src/relay/mod.rs similarity index 86% rename from src/relay.rs rename to src/relay/mod.rs index 9018b84d..7704954e 100644 --- a/src/relay.rs +++ b/src/relay/mod.rs @@ -4,9 +4,14 @@ use futures::future::join_all; use tokio::io; use tokio::net::TcpListener; -use crate::tcp; -use crate::udp; -use crate::utils::{Endpoint, RemoteAddr}; +mod dns; +mod tcp; +mod udp; +mod utils; +pub use utils::{Endpoint, RemoteAddr}; + +#[cfg(target_os = "linux")] +mod zero_copy; pub async fn run(eps: Vec) { let mut workers = vec![]; diff --git a/src/tcp.rs b/src/relay/tcp.rs similarity index 68% rename from src/tcp.rs rename to src/relay/tcp.rs index 975d74d5..d96aa51b 100644 --- a/src/tcp.rs +++ b/src/relay/tcp.rs @@ -4,7 +4,7 @@ use tokio::io::{self, AsyncWriteExt}; use tokio::net::tcp::{ReadHalf, WriteHalf}; use tokio::net::TcpStream; -use crate::utils::{self, RemoteAddr}; +use super::utils::RemoteAddr; pub async fn proxy( mut inbound: TcpStream, @@ -45,9 +45,13 @@ async fn copy(r: &mut ReadHalf<'_>, w: &mut WriteHalf<'_>) -> io::Result<()> { } // zero copy +#[cfg(target_os = "linux")] +use crate::relay::zero_copy; + #[cfg(target_os = "linux")] async fn copy(r: &mut ReadHalf<'_>, w: &mut WriteHalf<'_>) -> io::Result<()> { use std::os::unix::prelude::AsRawFd; + use zero_copy::{Pipe, splice_n, is_wouldblock}; // create pipe let pipe = Pipe::create()?; let (rpipe, wpipe) = (pipe.0, pipe.1); @@ -95,52 +99,3 @@ async fn copy(r: &mut ReadHalf<'_>, w: &mut WriteHalf<'_>) -> io::Result<()> { w.shutdown().await?; Ok(()) } - -#[cfg(target_os = "linux")] -struct Pipe(i32, i32); - -#[cfg(target_os = "linux")] -impl Drop for Pipe { - fn drop(&mut self) { - unsafe { - libc::close(self.0); - libc::close(self.1); - } - } -} - -#[cfg(target_os = "linux")] -impl Pipe { - fn create() -> io::Result { - use libc::{c_int, O_NONBLOCK}; - let mut pipes = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); - unsafe { - if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 { - return Err(utils::new_io_err("failed to create a pipe")); - } - Ok(Pipe(pipes.assume_init()[0], pipes.assume_init()[1])) - } - } -} - -#[cfg(target_os = "linux")] -fn splice_n(r: i32, w: i32, n: usize) -> isize { - use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; - unsafe { - libc::splice( - r, - 0 as *mut loff_t, - w, - 0 as *mut loff_t, - n, - SPLICE_F_MOVE | SPLICE_F_NONBLOCK, - ) - } -} - -#[cfg(target_os = "linux")] -fn is_wouldblock() -> bool { - use libc::{EAGAIN, EWOULDBLOCK}; - let errno = unsafe { *libc::__errno_location() }; - errno == EWOULDBLOCK || errno == EAGAIN -} diff --git a/src/udp.rs b/src/relay/udp.rs similarity index 98% rename from src/udp.rs rename to src/relay/udp.rs index 6d9a4c61..1405c904 100644 --- a/src/udp.rs +++ b/src/relay/udp.rs @@ -8,7 +8,7 @@ use tokio::net::UdpSocket; use tokio::sync::oneshot; use tokio::time::sleep; -use crate::utils::RemoteAddr; +use super::utils::RemoteAddr; const BUFFERSIZE: usize = 2048; const TIMEOUT: Duration = Duration::from_secs(60 * 15); diff --git a/src/utils.rs b/src/relay/utils.rs similarity index 98% rename from src/utils.rs rename to src/relay/utils.rs index e04aa393..74766fe1 100644 --- a/src/utils.rs +++ b/src/relay/utils.rs @@ -1,8 +1,6 @@ use std::net::{SocketAddr, ToSocketAddrs}; - use tokio::io; - -use crate::dns; +use super::dns; #[derive(Clone)] pub enum RemoteAddr { diff --git a/src/relay/zero_copy.rs b/src/relay/zero_copy.rs new file mode 100644 index 00000000..78bdb58c --- /dev/null +++ b/src/relay/zero_copy.rs @@ -0,0 +1,47 @@ +use std::ops::Drop; +use tokio::io; +use super::utils; + +pub struct Pipe(pub i32, pub i32); + +impl Drop for Pipe { + fn drop(&mut self) { + unsafe { + libc::close(self.0); + libc::close(self.1); + } + } +} + +impl Pipe { + pub fn create() -> io::Result { + use libc::{c_int, O_NONBLOCK}; + let mut pipes = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); + unsafe { + if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 { + return Err(utils::new_io_err("failed to create a pipe")); + } + Ok(Pipe(pipes.assume_init()[0], pipes.assume_init()[1])) + } + } +} + +pub fn splice_n(r: i32, w: i32, n: usize) -> isize { + use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; + unsafe { + libc::splice( + r, + 0 as *mut loff_t, + w, + 0 as *mut loff_t, + n, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK, + ) + } +} + +pub fn is_wouldblock() -> bool { + use libc::{EAGAIN, EWOULDBLOCK}; + let errno = unsafe { *libc::__errno_location() }; + errno == EWOULDBLOCK || errno == EAGAIN +} From f26daf47bdda8694a4602f02e360753cbeb5f735 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 8 Jul 2021 22:26:28 +0900 Subject: [PATCH 004/169] refactor file tree & support config file --- src/cmd/mod.rs | 25 +++++++++++++++++++++++ src/config/dns_mode.rs | 34 +++++++++++++++++++++++++++++++ src/config/endpoint_config.rs | 16 +++++++++++++++ src/config/mod.rs | 23 +++++++++++++++++++++ src/main.rs | 35 ++++++++++++++++++++++++++++---- src/relay/dns.rs | 23 ++++++++++++++------- src/relay/mod.rs | 12 +++++++---- src/relay/tcp.rs | 2 +- src/relay/{utils.rs => types.rs} | 9 +++----- src/relay/udp.rs | 2 +- src/relay/zero_copy.rs | 2 +- src/utils/mod.rs | 5 +++++ 12 files changed, 164 insertions(+), 24 deletions(-) create mode 100644 src/cmd/mod.rs create mode 100644 src/config/dns_mode.rs create mode 100644 src/config/endpoint_config.rs create mode 100644 src/config/mod.rs rename src/relay/{utils.rs => types.rs} (89%) create mode 100644 src/utils/mod.rs diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs new file mode 100644 index 00000000..b60e32b2 --- /dev/null +++ b/src/cmd/mod.rs @@ -0,0 +1,25 @@ +use clap::{Arg, App, SubCommand}; + +pub enum CmdInput { + Config(String), + None, +} + +pub fn scan() -> CmdInput { + let matches = App::new("Realm") + .version("1.3-custom") + .about("A high efficiency proxy tool") + .arg( + Arg::with_name("config") + .short("c") + .long("config") + .value_name("json config file") + .help("specify a config file in json format") + .takes_value(true), + ) + .get_matches(); + if let Some(config) = matches.value_of("config") { + return CmdInput::Config(config.to_string()); + } + CmdInput::None +} diff --git a/src/config/dns_mode.rs b/src/config/dns_mode.rs new file mode 100644 index 00000000..eb8eb63e --- /dev/null +++ b/src/config/dns_mode.rs @@ -0,0 +1,34 @@ +use serde::{Serialize, Deserialize}; +use trust_dns_resolver::config::LookupIpStrategy; + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +pub enum DnsMode { + /// Only query for A (Ipv4) records + Ipv4Only, + /// Only query for AAAA (Ipv6) records + Ipv6Only, + /// Query for A and AAAA in parallel + Ipv4AndIpv6, + /// Query for Ipv4 if that fails, query for Ipv6 (default) + Ipv4thenIpv6, + /// Query for Ipv6 if that fails, query for Ipv4 + Ipv6thenIpv4, +} + +impl Default for DnsMode { + fn default() -> Self { + Self::Ipv4thenIpv6 + } +} + +impl DnsMode { + pub fn to_strategy(self) -> LookupIpStrategy { + match self { + Self::Ipv4Only => LookupIpStrategy::Ipv4Only, + Self::Ipv6Only => LookupIpStrategy::Ipv6Only, + Self::Ipv4AndIpv6 => LookupIpStrategy::Ipv4AndIpv6, + Self::Ipv4thenIpv6 => LookupIpStrategy::Ipv4thenIpv6, + Self::Ipv6thenIpv4 => LookupIpStrategy::Ipv6thenIpv4, + } + } +} diff --git a/src/config/endpoint_config.rs b/src/config/endpoint_config.rs new file mode 100644 index 00000000..c91a8dca --- /dev/null +++ b/src/config/endpoint_config.rs @@ -0,0 +1,16 @@ +use serde::{Serialize, Deserialize}; + +use crate::relay::Endpoint; + +#[derive(Debug, Serialize, Deserialize)] +pub struct EndpointConfig { + udp: bool, + local: String, + remote: String, +} + +impl EndpointConfig { + pub fn to_endpoint(self) -> Endpoint { + Endpoint::new(&self.local, &self.remote, self.udp) + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 00000000..4a15f2e9 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,23 @@ +use std::fs; + +use serde::{Serialize, Deserialize}; + +mod dns_mode; +mod endpoint_config; + +pub use dns_mode::DnsMode; +pub use endpoint_config::EndpointConfig; + +#[derive(Debug, Serialize, Deserialize)] +pub struct GlobalConfig { + #[serde(default)] + pub dns_mode: DnsMode, + pub endpoints: Vec, +} + +impl GlobalConfig { + pub fn from_config_file(file: &str) -> Self { + let config = fs::read_to_string(file).expect("invalid file path"); + serde_json::from_str(&config).expect("failed to parse config file") + } +} diff --git a/src/main.rs b/src/main.rs index a6ac1897..6ccd9e04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,34 @@ +mod cmd; mod relay; +mod utils; +mod config; -#[tokio::main] -async fn main() { - let eps = vec![relay::Endpoint::new("127.0.0.1:15000", "localhost:20000")]; - relay::run(eps).await +use cmd::CmdInput; +use config::GlobalConfig; +use relay::Endpoint; + +fn main() { + match cmd::scan() { + CmdInput::Config(c) => start_from_config(c), + CmdInput::None => {} + } +} + +fn start_from_config(c: String) { + let config = GlobalConfig::from_config_file(&c); + relay::init_resolver(config.dns_mode.to_strategy()); + let eps: Vec = config + .endpoints + .into_iter() + .map(|epc| epc.to_endpoint()) + .collect(); + run_relay(eps); +} + +fn run_relay(eps: Vec) { + tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + .unwrap() + .block_on(relay::run(eps)) } diff --git a/src/relay/dns.rs b/src/relay/dns.rs index b2fb5280..8468ad5c 100644 --- a/src/relay/dns.rs +++ b/src/relay/dns.rs @@ -3,17 +3,26 @@ use std::net::IpAddr; use tokio::io; use tokio::runtime::Runtime; use trust_dns_resolver::TokioAsyncResolver; -use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; +use trust_dns_resolver::config::{ResolverConfig, ResolverOpts, LookupIpStrategy}; use lazy_static::lazy_static; -use super::utils; +use crate::utils; + +static mut RESOLVE_STRATEGY: LookupIpStrategy = LookupIpStrategy::Ipv4thenIpv6; lazy_static! { - pub static ref DNS: TokioAsyncResolver = TokioAsyncResolver::tokio( - ResolverConfig::default(), - ResolverOpts::default() - ) - .unwrap(); + static ref DNS: TokioAsyncResolver = + TokioAsyncResolver::tokio(ResolverConfig::default(), { + let mut opts = ResolverOpts::default(); + opts.ip_strategy = unsafe { RESOLVE_STRATEGY }; + opts + }) + .unwrap(); +} + +pub fn init_resolver(strategy: LookupIpStrategy) { + unsafe { RESOLVE_STRATEGY = strategy }; + lazy_static::initialize(&DNS); } pub fn resolve_sync(addr: &str) -> io::Result { diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 7704954e..940bc673 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -7,8 +7,10 @@ use tokio::net::TcpListener; mod dns; mod tcp; mod udp; -mod utils; -pub use utils::{Endpoint, RemoteAddr}; +mod types; + +pub use types::{Endpoint, RemoteAddr}; +pub use dns::init_resolver; #[cfg(target_os = "linux")] mod zero_copy; @@ -16,8 +18,10 @@ mod zero_copy; pub async fn run(eps: Vec) { let mut workers = vec![]; for ep in eps.into_iter() { - workers.push(tokio::spawn(proxy_tcp(ep.local, ep.remote.clone()))); - workers.push(tokio::spawn(proxy_udp(ep.local, ep.remote))) + if ep.udp { + workers.push(tokio::spawn(proxy_udp(ep.local, ep.remote.clone()))) + } + workers.push(tokio::spawn(proxy_tcp(ep.local, ep.remote))); } join_all(workers).await; } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index d96aa51b..92029da7 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -4,7 +4,7 @@ use tokio::io::{self, AsyncWriteExt}; use tokio::net::tcp::{ReadHalf, WriteHalf}; use tokio::net::TcpStream; -use super::utils::RemoteAddr; +use super::types::RemoteAddr; pub async fn proxy( mut inbound: TcpStream, diff --git a/src/relay/utils.rs b/src/relay/types.rs similarity index 89% rename from src/relay/utils.rs rename to src/relay/types.rs index 74766fe1..5057982f 100644 --- a/src/relay/utils.rs +++ b/src/relay/types.rs @@ -9,14 +9,11 @@ pub enum RemoteAddr { } pub struct Endpoint { + pub udp: bool, pub local: SocketAddr, pub remote: RemoteAddr, } -pub fn new_io_err(e: &str) -> io::Error { - io::Error::new(io::ErrorKind::Other, e) -} - impl RemoteAddr { pub async fn to_sockaddr(self) -> io::Result { match self { @@ -39,7 +36,7 @@ impl RemoteAddr { } impl Endpoint { - pub fn new(local: &str, remote: &str) -> Self { + pub fn new(local: &str, remote: &str, udp: bool) -> Self { let local = local .to_socket_addrs() .expect("invalid local address") @@ -55,6 +52,6 @@ impl Endpoint { let _ = dns::resolve_sync(&addr).unwrap(); RemoteAddr::DomainName(addr, port) }; - Endpoint { local, remote } + Endpoint { local, remote, udp } } } diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 1405c904..fb9afdd8 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -8,7 +8,7 @@ use tokio::net::UdpSocket; use tokio::sync::oneshot; use tokio::time::sleep; -use super::utils::RemoteAddr; +use super::types::RemoteAddr; const BUFFERSIZE: usize = 2048; const TIMEOUT: Duration = Duration::from_secs(60 * 15); diff --git a/src/relay/zero_copy.rs b/src/relay/zero_copy.rs index 78bdb58c..22174b79 100644 --- a/src/relay/zero_copy.rs +++ b/src/relay/zero_copy.rs @@ -1,6 +1,6 @@ use std::ops::Drop; use tokio::io; -use super::utils; +use crate::utils; pub struct Pipe(pub i32, pub i32); diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 00000000..a9f324f1 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,5 @@ +use tokio::io; + +pub fn new_io_err(e: &str) -> io::Error { + io::Error::new(io::ErrorKind::Other, e) +} From 6afffae0b98cf85300f26b3a6dc0726e97b19f78 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 9 Jul 2021 01:40:46 +0900 Subject: [PATCH 005/169] fmt & doc --- readme.md | 29 +++++++++++++++++++++++------ src/cmd/mod.rs | 13 +++++++++++++ src/cmd/nav.rs | 3 +++ src/config/endpoint_config.rs | 1 - src/config/mod.rs | 1 - src/main.rs | 1 + src/relay/mod.rs | 1 - 7 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 src/cmd/nav.rs diff --git a/readme.md b/readme.md index edc3df70..600e5f7f 100644 --- a/readme.md +++ b/readme.md @@ -14,11 +14,28 @@ realm is a simple, high performance relay server written in rust. - Low resources cost. ## Usage -This executable takes 2 arguments: -- -l [--local] local socket address. Default address 127.0.0.1 is used when the address is omitted. -- -r [--remote] remote socker address. Both domain and ip address are accepted. If a domain is passed, the resolver will try to resolve and update the ip address regularly, ipv4 is preferred. - -An example to listen on port 30000 and forwarding traffic to example.com:12345 is as follows. +```bash +realm -c config.json +``` +>example.json +```json +{ + "dns_mode": "Ipv4Only", + "endpoints": [ + { + "local": "0.0.0.0:5000", + "remote": "1.1.1.1:443", + "udp": false + } + { + "local": "0.0.0.0:10000", + "remote": "www.google.com:443", + "udp": true + } + ] +} +``` +>dns_mode ``` -./realm -l 127.0.0.1:30000 -r example.com:12345 +Ipv4Only|Ipv6Only|Ipv4AndIpv6|Ipv4thenIpv6|Ipv6thenIpv4 ``` diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index b60e32b2..4079fda9 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,7 +1,11 @@ use clap::{Arg, App, SubCommand}; +mod nav; +pub use nav::run_navigator; + pub enum CmdInput { Config(String), + Navigate, None, } @@ -17,9 +21,18 @@ pub fn scan() -> CmdInput { .help("specify a config file in json format") .takes_value(true), ) + .subcommand( + SubCommand::with_name("nav") + .about("An Interactive configuration editor") + .version("0.1.0") + .author("zephyr "), + ) .get_matches(); if let Some(config) = matches.value_of("config") { return CmdInput::Config(config.to_string()); } + if let Some(_) = matches.subcommand_matches("nav") { + return CmdInput::Navigate; + } CmdInput::None } diff --git a/src/cmd/nav.rs b/src/cmd/nav.rs new file mode 100644 index 00000000..297cac73 --- /dev/null +++ b/src/cmd/nav.rs @@ -0,0 +1,3 @@ +pub fn run_navigator() { + todo!() +} diff --git a/src/config/endpoint_config.rs b/src/config/endpoint_config.rs index c91a8dca..13f3df30 100644 --- a/src/config/endpoint_config.rs +++ b/src/config/endpoint_config.rs @@ -1,5 +1,4 @@ use serde::{Serialize, Deserialize}; - use crate::relay::Endpoint; #[derive(Debug, Serialize, Deserialize)] diff --git a/src/config/mod.rs b/src/config/mod.rs index 4a15f2e9..8e58f86e 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -4,7 +4,6 @@ use serde::{Serialize, Deserialize}; mod dns_mode; mod endpoint_config; - pub use dns_mode::DnsMode; pub use endpoint_config::EndpointConfig; diff --git a/src/main.rs b/src/main.rs index 6ccd9e04..9eaf28db 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use relay::Endpoint; fn main() { match cmd::scan() { CmdInput::Config(c) => start_from_config(c), + CmdInput::Navigate => cmd::run_navigator(), CmdInput::None => {} } } diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 940bc673..87bb0ed7 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -8,7 +8,6 @@ mod dns; mod tcp; mod udp; mod types; - pub use types::{Endpoint, RemoteAddr}; pub use dns::init_resolver; From fe72ffe99c46c07982511cd519bcd2e7db84c029 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 9 Jul 2021 02:10:26 +0900 Subject: [PATCH 006/169] revert last? merge --- .github/workflows/relay.yml | 42 +++++++++++++++++++++++++++++++------ 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/.github/workflows/relay.yml b/.github/workflows/relay.yml index 7cc859b4..94d9116a 100644 --- a/.github/workflows/relay.yml +++ b/.github/workflows/relay.yml @@ -12,23 +12,53 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Install latest stable + - name: Install toolchain for linux-musl uses: actions-rs/toolchain@v1 with: toolchain: stable target: x86_64-unknown-linux-musl - override: true - components: rustfmt, clippy - - name: Run cargo build + - name: Install toolchain for linux-gnu + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-unknown-linux-gnu + + - name: Install toolchain for mingw + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: x86_64-pc-windows-gnu + + - name: Build for linux-musl uses: actions-rs/cargo@v1 with: use-cross: true command: build args: --release --target x86_64-unknown-linux-musl - + + - name: Build for linux-gnu + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target x86_64-unknown-linux-gnu + + - name: Build for windows + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target x86_64-pc-windows-gnu + + - name: Rename outputs + run: | + mkdir -p target/bin + mv target/x86_64-unknown-linux-musl/release/realm target/bin/realm-x86_64-unknown-linux-musl + mv target/x86_64-unknown-linux-gnu/release/realm target/bin/realm-x86_64-unknown-linux-gnu + mv target/x86_64-pc-windows-gnu/release/realm.exe target/bin/realm-x86_64-pc-windows-gnu - uses: actions/upload-artifact@v2 with: name: realm - path: target/x86_64-unknown-linux-musl + path: target/bin/ From 23fe88dfc7927ec6ce00524c8c2067d7320baf49 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 10 Jul 2021 18:03:07 +0900 Subject: [PATCH 007/169] fmt --- src/cmd/mod.rs | 2 +- src/config/endpoint_config.rs | 2 +- src/main.rs | 2 +- src/relay/dns.rs | 15 ++++++++------- src/relay/tcp.rs | 3 ++- src/relay/types.rs | 4 ++-- src/relay/udp.rs | 26 ++++++++++++++------------ src/relay/zero_copy.rs | 4 ++-- 8 files changed, 31 insertions(+), 27 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 4079fda9..bca6083a 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -31,7 +31,7 @@ pub fn scan() -> CmdInput { if let Some(config) = matches.value_of("config") { return CmdInput::Config(config.to_string()); } - if let Some(_) = matches.subcommand_matches("nav") { + if matches.subcommand_matches("nav").is_some() { return CmdInput::Navigate; } CmdInput::None diff --git a/src/config/endpoint_config.rs b/src/config/endpoint_config.rs index 13f3df30..19c2ff60 100644 --- a/src/config/endpoint_config.rs +++ b/src/config/endpoint_config.rs @@ -9,7 +9,7 @@ pub struct EndpointConfig { } impl EndpointConfig { - pub fn to_endpoint(self) -> Endpoint { + pub fn into_endpoint(self) -> Endpoint { Endpoint::new(&self.local, &self.remote, self.udp) } } diff --git a/src/main.rs b/src/main.rs index 9eaf28db..82254749 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ fn start_from_config(c: String) { let eps: Vec = config .endpoints .into_iter() - .map(|epc| epc.to_endpoint()) + .map(|epc| epc.into_endpoint()) .collect(); run_relay(eps); } diff --git a/src/relay/dns.rs b/src/relay/dns.rs index 8468ad5c..5df9e5e1 100644 --- a/src/relay/dns.rs +++ b/src/relay/dns.rs @@ -11,13 +11,14 @@ use crate::utils; static mut RESOLVE_STRATEGY: LookupIpStrategy = LookupIpStrategy::Ipv4thenIpv6; lazy_static! { - static ref DNS: TokioAsyncResolver = - TokioAsyncResolver::tokio(ResolverConfig::default(), { - let mut opts = ResolverOpts::default(); - opts.ip_strategy = unsafe { RESOLVE_STRATEGY }; - opts - }) - .unwrap(); + static ref DNS: TokioAsyncResolver = TokioAsyncResolver::tokio( + ResolverConfig::default(), + ResolverOpts { + ip_strategy: unsafe { RESOLVE_STRATEGY }, + ..Default::default() + } + ) + .unwrap(); } pub fn init_resolver(strategy: LookupIpStrategy) { diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 92029da7..cd313585 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -10,7 +10,8 @@ pub async fn proxy( mut inbound: TcpStream, remote: RemoteAddr, ) -> io::Result<()> { - let mut outbound = TcpStream::connect(remote.to_sockaddr().await?).await?; + let mut outbound = + TcpStream::connect(remote.into_sockaddr().await?).await?; inbound.set_nodelay(true)?; outbound.set_nodelay(true)?; let (mut ri, mut wi) = inbound.split(); diff --git a/src/relay/types.rs b/src/relay/types.rs index 5057982f..10a54c78 100644 --- a/src/relay/types.rs +++ b/src/relay/types.rs @@ -15,7 +15,7 @@ pub struct Endpoint { } impl RemoteAddr { - pub async fn to_sockaddr(self) -> io::Result { + pub async fn into_sockaddr(self) -> io::Result { match self { Self::SocketAddr(sockaddr) => Ok(sockaddr), Self::DomainName(addr, port) => { @@ -24,7 +24,7 @@ impl RemoteAddr { } } } - pub async fn to_sockaddr_as_ref(&self) -> io::Result { + pub async fn to_sockaddr(&self) -> io::Result { match self { Self::SocketAddr(sockaddr) => Ok(*sockaddr), Self::DomainName(addr, port) => { diff --git a/src/relay/udp.rs b/src/relay/udp.rs index fb9afdd8..c12f608e 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -10,12 +10,13 @@ use tokio::time::sleep; use super::types::RemoteAddr; +type Record = HashMap, oneshot::Sender<()>)>; const BUFFERSIZE: usize = 2048; const TIMEOUT: Duration = Duration::from_secs(60 * 15); pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { // records (client_addr, alloc_socket) - let mut record = HashMap::new(); + let mut record: Record = HashMap::new(); let local_socket = Arc::new(UdpSocket::bind(&local).await.unwrap()); let mut buf = vec![0u8; BUFFERSIZE]; @@ -23,17 +24,18 @@ pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { tokio::select! { _ = async { let (n, client_addr) = local_socket.recv_from(&mut buf).await?; - if !record.contains_key(&client_addr) { - // pick a random port - let alloc_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap()); - let (emit, cancel) = oneshot::channel::<()>(); - tokio::spawn(send_back( - client_addr, local_socket.clone(), alloc_socket.clone(), cancel - )); - record.insert(client_addr, (alloc_socket,emit)); - } - let (alloc_socket, _) = record.get(&client_addr).unwrap(); - let remote_addr = remote.to_sockaddr_as_ref().await?; + let (alloc_socket, _) = record.entry(client_addr).or_insert( + { + // pick a random port + let alloc_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap()); + let (emit, cancel) = oneshot::channel::<()>(); + tokio::spawn(send_back( + client_addr, local_socket.clone(), alloc_socket.clone(), cancel + )); + (alloc_socket, emit) + } + ); + let remote_addr = remote.to_sockaddr().await?; alloc_socket.send_to(&buf[..n], &remote_addr).await?; Ok::<_, io::Error>(()) } => {} diff --git a/src/relay/zero_copy.rs b/src/relay/zero_copy.rs index 22174b79..1bbc4f76 100644 --- a/src/relay/zero_copy.rs +++ b/src/relay/zero_copy.rs @@ -31,9 +31,9 @@ pub fn splice_n(r: i32, w: i32, n: usize) -> isize { unsafe { libc::splice( r, - 0 as *mut loff_t, + std::ptr::null_mut::(), w, - 0 as *mut loff_t, + std::ptr::null_mut::(), n, SPLICE_F_MOVE | SPLICE_F_NONBLOCK, ) From 6e49291d71a67400930119a0cf3d3324ac5faa1e Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 10 Jul 2021 18:31:58 +0900 Subject: [PATCH 008/169] indent --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 600e5f7f..12e18074 100644 --- a/readme.md +++ b/readme.md @@ -27,9 +27,9 @@ realm -c config.json "remote": "1.1.1.1:443", "udp": false } - { - "local": "0.0.0.0:10000", - "remote": "www.google.com:443", + { + "local": "0.0.0.0:10000", + "remote": "www.google.com:443", "udp": true } ] From e88c2225623a870917c815ed8e2664a0e479508a Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 14 Jul 2021 16:10:38 +0900 Subject: [PATCH 009/169] rename dns mode; fix resolver not applied --- readme.md | 4 ++-- src/config/dns_mode.rs | 11 ++++++----- src/relay/types.rs | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/readme.md b/readme.md index 12e18074..bfcbdf8e 100644 --- a/readme.md +++ b/readme.md @@ -20,7 +20,7 @@ realm -c config.json >example.json ```json { - "dns_mode": "Ipv4Only", + "dns_mode": "ipv4_only", "endpoints": [ { "local": "0.0.0.0:5000", @@ -37,5 +37,5 @@ realm -c config.json ``` >dns_mode ``` -Ipv4Only|Ipv6Only|Ipv4AndIpv6|Ipv4thenIpv6|Ipv6thenIpv4 +ipv4_only|ipv6_only|ipv4_and_ipv6|ipv4_then_ipv6|ipv6_then_ipv4 ``` diff --git a/src/config/dns_mode.rs b/src/config/dns_mode.rs index eb8eb63e..8034b5b2 100644 --- a/src/config/dns_mode.rs +++ b/src/config/dns_mode.rs @@ -2,6 +2,7 @@ use serde::{Serialize, Deserialize}; use trust_dns_resolver::config::LookupIpStrategy; #[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum DnsMode { /// Only query for A (Ipv4) records Ipv4Only, @@ -10,14 +11,14 @@ pub enum DnsMode { /// Query for A and AAAA in parallel Ipv4AndIpv6, /// Query for Ipv4 if that fails, query for Ipv6 (default) - Ipv4thenIpv6, + Ipv4ThenIpv6, /// Query for Ipv6 if that fails, query for Ipv4 - Ipv6thenIpv4, + Ipv6ThenIpv4, } impl Default for DnsMode { fn default() -> Self { - Self::Ipv4thenIpv6 + Self::Ipv4ThenIpv6 } } @@ -27,8 +28,8 @@ impl DnsMode { Self::Ipv4Only => LookupIpStrategy::Ipv4Only, Self::Ipv6Only => LookupIpStrategy::Ipv6Only, Self::Ipv4AndIpv6 => LookupIpStrategy::Ipv4AndIpv6, - Self::Ipv4thenIpv6 => LookupIpStrategy::Ipv4thenIpv6, - Self::Ipv6thenIpv4 => LookupIpStrategy::Ipv6thenIpv4, + Self::Ipv4ThenIpv6 => LookupIpStrategy::Ipv4thenIpv6, + Self::Ipv6ThenIpv4 => LookupIpStrategy::Ipv6thenIpv4, } } } diff --git a/src/relay/types.rs b/src/relay/types.rs index 10a54c78..f6ab4cfd 100644 --- a/src/relay/types.rs +++ b/src/relay/types.rs @@ -42,8 +42,8 @@ impl Endpoint { .expect("invalid local address") .next() .unwrap(); - let remote = if let Ok(mut sockaddr) = remote.to_socket_addrs() { - RemoteAddr::SocketAddr(sockaddr.next().unwrap()) + let remote = if let Ok(sockaddr) = remote.parse::() { + RemoteAddr::SocketAddr(sockaddr) } else { let mut iter = remote.splitn(2, ':'); let addr = iter.next().unwrap().to_string(); @@ -52,6 +52,6 @@ impl Endpoint { let _ = dns::resolve_sync(&addr).unwrap(); RemoteAddr::DomainName(addr, port) }; - Endpoint { local, remote, udp } + Endpoint { udp, local, remote } } } From 5d5d1fc032d45c609c1ec8d538662bbf73d670cf Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 01:31:59 +0900 Subject: [PATCH 010/169] fix resource leak --- src/relay/udp.rs | 124 +++++++++++++++++++++++++++++++---------------- 1 file changed, 81 insertions(+), 43 deletions(-) diff --git a/src/relay/udp.rs b/src/relay/udp.rs index c12f608e..e6dea6fa 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -1,63 +1,101 @@ use std::time::Duration; use std::net::SocketAddr; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; use std::collections::HashMap; -use tokio::io; +use std::io; use tokio::net::UdpSocket; -use tokio::sync::oneshot; -use tokio::time::sleep; +use tokio::time::timeout; use super::types::RemoteAddr; -type Record = HashMap, oneshot::Sender<()>)>; -const BUFFERSIZE: usize = 2048; -const TIMEOUT: Duration = Duration::from_secs(60 * 15); +// client <--> allocated socket +type SockMap = Arc>>>; +const BUFFERSIZE: usize = 0x1000; +const TIMEOUT: Duration = Duration::from_secs(20); pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { - // records (client_addr, alloc_socket) - let mut record: Record = HashMap::new(); - - let local_socket = Arc::new(UdpSocket::bind(&local).await.unwrap()); + let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); + let local_sock = Arc::new(UdpSocket::bind(&local).await.unwrap()); let mut buf = vec![0u8; BUFFERSIZE]; + loop { - tokio::select! { - _ = async { - let (n, client_addr) = local_socket.recv_from(&mut buf).await?; - let (alloc_socket, _) = record.entry(client_addr).or_insert( - { - // pick a random port - let alloc_socket = Arc::new(UdpSocket::bind("0.0.0.0:0").await.unwrap()); - let (emit, cancel) = oneshot::channel::<()>(); - tokio::spawn(send_back( - client_addr, local_socket.clone(), alloc_socket.clone(), cancel - )); - (alloc_socket, emit) - } - ); - let remote_addr = remote.to_sockaddr().await?; - alloc_socket.send_to(&buf[..n], &remote_addr).await?; - Ok::<_, io::Error>(()) - } => {} - _ = async { sleep(TIMEOUT).await } => record.clear() - } + let (n, client_addr) = local_sock.recv_from(&mut buf).await?; + + let remote_addr = remote.to_sockaddr().await?; + + // the socket associated with a unique client + let alloc_sock = match get_socket(&sock_map, &client_addr) { + Some(x) => x, + None => { + alloc_new_socket( + &sock_map, + client_addr, + &remote_addr, + local_sock.clone(), + ) + .await + } + }; + + alloc_sock.send_to(&buf[..n], &remote_addr).await?; } } async fn send_back( + sock_map: SockMap, client_addr: SocketAddr, - local_socket: Arc, - alloc_socket: Arc, - cancel: oneshot::Receiver<()>, -) -> io::Result<()> { + local_sock: Arc, + alloc_sock: Arc, +) { let mut buf = vec![0u8; BUFFERSIZE]; - tokio::select! { - ret = async { - loop { - let (n, _) = alloc_socket.recv_from(&mut buf).await?; - local_socket.send_to(&buf[..n], &client_addr).await?; - } - } => { ret } - _ = cancel => Ok(()) + + while let Ok(Ok((n, _))) = + timeout(TIMEOUT, alloc_sock.recv_from(&mut buf)).await + { + if local_sock.send_to(&buf[..n], &client_addr).await.is_err() { + break; + } } + + sock_map.write().unwrap().remove(&client_addr); +} + +#[inline] +fn get_socket( + sock_map: &SockMap, + client_addr: &SocketAddr, +) -> Option> { + let alloc_sock = sock_map.read().unwrap(); + alloc_sock.get(client_addr).cloned() + // drop the lock +} + +async fn alloc_new_socket( + sock_map: &SockMap, + client_addr: SocketAddr, + remote_addr: &SocketAddr, + local_sock: Arc, +) -> Arc { + // pick a random port + let alloc_sock = Arc::new(if remote_addr.is_ipv4() { + UdpSocket::bind("0.0.0.0:0").await.unwrap() + } else { + UdpSocket::bind("[::]:0").await.unwrap() + }); + + // new send back task + tokio::spawn(send_back( + sock_map.clone(), + client_addr, + local_sock, + alloc_sock.clone(), + )); + + sock_map + .write() + .unwrap() + .insert(client_addr, alloc_sock.clone()); + alloc_sock + // drop the lock } From c04e5d9c53821f93cb037d825631a4f404e8ba6b Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 01:33:30 +0900 Subject: [PATCH 011/169] correctly parse ipv6 addr --- src/relay/types.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/relay/types.rs b/src/relay/types.rs index f6ab4cfd..69586992 100644 --- a/src/relay/types.rs +++ b/src/relay/types.rs @@ -45,9 +45,9 @@ impl Endpoint { let remote = if let Ok(sockaddr) = remote.parse::() { RemoteAddr::SocketAddr(sockaddr) } else { - let mut iter = remote.splitn(2, ':'); - let addr = iter.next().unwrap().to_string(); + let mut iter = remote.rsplitn(2, ':'); let port = iter.next().unwrap().parse::().unwrap(); + let addr = iter.next().unwrap().to_string(); // test addr let _ = dns::resolve_sync(&addr).unwrap(); RemoteAddr::DomainName(addr, port) From 4b83365b33cbb7c1d22e91b850e36a8b898356ab Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 01:37:51 +0900 Subject: [PATCH 012/169] update workflow --- .github/workflows/ci.yml | 16 ++++++ .github/workflows/cross_compile.yml | 71 +++++++++++++++++++++++++ .github/workflows/relay.yml | 64 ----------------------- .github/workflows/release.yml | 80 +++++++++++++++++++++++++++++ 4 files changed, 167 insertions(+), 64 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/cross_compile.yml delete mode 100644 .github/workflows/relay.yml create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..923e3607 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,16 @@ +name: ci +on: push +jobs: + clippy_check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + components: clippy + override: true + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + args: --all-features diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml new file mode 100644 index 00000000..a0337549 --- /dev/null +++ b/.github/workflows/cross_compile.yml @@ -0,0 +1,71 @@ +name: compile +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +env: + CARGO_TERM_COLOR: always + +jobs: + build-corss: + runs-on: ubuntu-latest + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + - x86_64-pc-windows-gnu + include: + - target: x86_64-unknown-linux-gnu + output: realm + - target: x86_64-unknown-linux-musl + output: realm + - target: x86_64-pc-windows-gnu + output: realm.exe + steps: + - uses: actions/checkout@v2 + - name: install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.target }} + override: true + - name: compile + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target=${{ matrix.target }} + - name: upload + uses: actions/upload-artifact@v2 + with: + name: realm-${{ matrix.target }} + path: target/${{ matrix.target }}/release/${{ matrix.output }} + build-apple: + runs-on: macos-latest + strategy: + matrix: + target: + - x86_64-apple-darwin + steps: + - uses: actions/checkout@v2 + - name: install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.target }} + override: true + - name: compile + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: build + args: --release --target=${{ matrix.target }} + - name: upload + uses: actions/upload-artifact@v2 + with: + name: realm-${{ matrix.target }} + path: target/${{ matrix.target }}/release/realm + diff --git a/.github/workflows/relay.yml b/.github/workflows/relay.yml deleted file mode 100644 index 94d9116a..00000000 --- a/.github/workflows/relay.yml +++ /dev/null @@ -1,64 +0,0 @@ -on: - push: - branches: - - master - -name: realm - -jobs: - check: - name: Rust project - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install toolchain for linux-musl - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-unknown-linux-musl - - - name: Install toolchain for linux-gnu - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-unknown-linux-gnu - - - name: Install toolchain for mingw - uses: actions-rs/toolchain@v1 - with: - toolchain: stable - target: x86_64-pc-windows-gnu - - - name: Build for linux-musl - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target x86_64-unknown-linux-musl - - - name: Build for linux-gnu - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target x86_64-unknown-linux-gnu - - - name: Build for windows - uses: actions-rs/cargo@v1 - with: - use-cross: true - command: build - args: --release --target x86_64-pc-windows-gnu - - - name: Rename outputs - run: | - mkdir -p target/bin - mv target/x86_64-unknown-linux-musl/release/realm target/bin/realm-x86_64-unknown-linux-musl - mv target/x86_64-unknown-linux-gnu/release/realm target/bin/realm-x86_64-unknown-linux-gnu - mv target/x86_64-pc-windows-gnu/release/realm.exe target/bin/realm-x86_64-pc-windows-gnu - - uses: actions/upload-artifact@v2 - with: - name: realm - path: target/bin/ - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..7fcf7104 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,80 @@ +name: release +on: + push: + tags: + - 'v*.*.*' + +jobs: + release-corss: + runs-on: ubuntu-latest + strategy: + matrix: + target: + - x86_64-unknown-linux-gnu + - x86_64-unknown-linux-musl + - x86_64-pc-windows-gnu + include: + - target: x86_64-unknown-linux-gnu + output: realm + - target: x86_64-unknown-linux-musl + output: realm + - target: x86_64-pc-windows-gnu + output: realm.exe + steps: + - uses: actions/checkout@v2 + - name: install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.target }} + override: true + - name: compile + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target=${{ matrix.target }} + - name: pack + run: | + mkdir -p release-${{ matrix.target }} + cd release-${{ matrix.target }} + tar -C ../target/${{ matrix.target }}/release -zcf realm-${{ matrix.target }}.tar.gz ${{ matrix.output }} + sha256sum realm-${{ matrix.target }}.tar.gz > realm-${{ matrix.target }}.sha256 + - name: release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: release-${{ matrix.target }}/* + release-apple: + runs-on: macos-latest + strategy: + matrix: + target: + - x86_64-apple-darwin + steps: + - uses: actions/checkout@v2 + - name: install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.target }} + override: true + - name: compile + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: build + args: --release --target=${{ matrix.target }} + - name: pack + run: | + mkdir -p release-${{ matrix.target }} + cd release-${{ matrix.target }} + tar -C ../target/${{ matrix.target }}/release/ -zcf realm-${{ matrix.target }}.tar.gz realm + shasum -a 256 realm-${{ matrix.target }}.tar.gz > realm-${{ matrix.target }}.sha256 + - name: release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: release-${{ matrix.target }}/* From 92f9edea7258b26c0edc5774fa8ea2042db8ddac Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 01:39:49 +0900 Subject: [PATCH 013/169] rename module --- src/config/{endpoint_config.rs => endpoint.rs} | 0 src/config/mod.rs | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename src/config/{endpoint_config.rs => endpoint.rs} (100%) diff --git a/src/config/endpoint_config.rs b/src/config/endpoint.rs similarity index 100% rename from src/config/endpoint_config.rs rename to src/config/endpoint.rs diff --git a/src/config/mod.rs b/src/config/mod.rs index 8e58f86e..74899a76 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -3,9 +3,9 @@ use std::fs; use serde::{Serialize, Deserialize}; mod dns_mode; -mod endpoint_config; +mod endpoint; pub use dns_mode::DnsMode; -pub use endpoint_config::EndpointConfig; +pub use endpoint::EndpointConfig; #[derive(Debug, Serialize, Deserialize)] pub struct GlobalConfig { From ea70d74dfc28e260aecaa35906adf5c52777062b Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 01:46:54 +0900 Subject: [PATCH 014/169] use type converter --- src/config/dns_mode.rs | 16 ++++++++-------- src/main.rs | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/config/dns_mode.rs b/src/config/dns_mode.rs index 8034b5b2..bf0e9ced 100644 --- a/src/config/dns_mode.rs +++ b/src/config/dns_mode.rs @@ -22,14 +22,14 @@ impl Default for DnsMode { } } -impl DnsMode { - pub fn to_strategy(self) -> LookupIpStrategy { - match self { - Self::Ipv4Only => LookupIpStrategy::Ipv4Only, - Self::Ipv6Only => LookupIpStrategy::Ipv6Only, - Self::Ipv4AndIpv6 => LookupIpStrategy::Ipv4AndIpv6, - Self::Ipv4ThenIpv6 => LookupIpStrategy::Ipv4thenIpv6, - Self::Ipv6ThenIpv4 => LookupIpStrategy::Ipv6thenIpv4, +impl From for LookupIpStrategy { + fn from(mode: DnsMode) -> Self { + match mode { + DnsMode::Ipv4Only => LookupIpStrategy::Ipv4Only, + DnsMode::Ipv6Only => LookupIpStrategy::Ipv6Only, + DnsMode::Ipv4AndIpv6 => LookupIpStrategy::Ipv4AndIpv6, + DnsMode::Ipv4ThenIpv6 => LookupIpStrategy::Ipv4thenIpv6, + DnsMode::Ipv6ThenIpv4 => LookupIpStrategy::Ipv6thenIpv4, } } } diff --git a/src/main.rs b/src/main.rs index 82254749..afca76dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ fn main() { fn start_from_config(c: String) { let config = GlobalConfig::from_config_file(&c); - relay::init_resolver(config.dns_mode.to_strategy()); + relay::init_resolver(config.dns_mode.into()); let eps: Vec = config .endpoints .into_iter() From 636c3af5826236c7a0279acd599c691c37290023 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 02:12:48 +0900 Subject: [PATCH 015/169] support starting from command line --- src/cmd/mod.rs | 39 +++++++++++++++++++++++++++++++++++++-- src/main.rs | 5 +++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index bca6083a..bfdf7710 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,10 +1,13 @@ use clap::{Arg, App, SubCommand}; +use super::Endpoint; + mod nav; pub use nav::run_navigator; pub enum CmdInput { Config(String), + Endpoint(Endpoint), Navigate, None, } @@ -17,10 +20,29 @@ pub fn scan() -> CmdInput { Arg::with_name("config") .short("c") .long("config") - .value_name("json config file") - .help("specify a config file in json format") + .help("use config file") + .takes_value(true), + ) + .arg( + Arg::with_name("local") + .short("l") + .long("local") + .help("listen address") + .takes_value(true), + ) + .arg( + Arg::with_name("remote") + .short("r") + .long("remote") + .help("remote address") .takes_value(true), ) + .arg( + Arg::with_name("udp") + .short("u") + .long("udp") + .help("enable udp"), + ) .subcommand( SubCommand::with_name("nav") .about("An Interactive configuration editor") @@ -28,11 +50,24 @@ pub fn scan() -> CmdInput { .author("zephyr "), ) .get_matches(); + if let Some(config) = matches.value_of("config") { return CmdInput::Config(config.to_string()); } + + if let (Some(local), Some(remote)) = + (matches.value_of("local"), matches.value_of("remote")) + { + return CmdInput::Endpoint(Endpoint::new( + local, + remote, + matches.is_present("udp"), + )); + } + if matches.subcommand_matches("nav").is_some() { return CmdInput::Navigate; } + CmdInput::None } diff --git a/src/main.rs b/src/main.rs index afca76dc..6f87ba9f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,11 +10,16 @@ use relay::Endpoint; fn main() { match cmd::scan() { CmdInput::Config(c) => start_from_config(c), + CmdInput::Endpoint(ep) => start_from_cmd(ep), CmdInput::Navigate => cmd::run_navigator(), CmdInput::None => {} } } +fn start_from_cmd(c: Endpoint) { + run_relay(vec![c]) +} + fn start_from_config(c: String) { let config = GlobalConfig::from_config_file(&c); relay::init_resolver(config.dns_mode.into()); From 5c2f85e468c39625a425e2bb8f6ce257834dedd9 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 02:21:43 +0900 Subject: [PATCH 016/169] docs --- readme.md | 31 ++++++++++++++++++++++++++++--- src/cmd/mod.rs | 5 ++++- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index bfcbdf8e..7814e450 100644 --- a/readme.md +++ b/readme.md @@ -14,10 +14,35 @@ realm is a simple, high performance relay server written in rust. - Low resources cost. ## Usage -```bash +```shell +Realm 1.x +A high efficiency proxy tool + +USAGE: + realm [FLAGS] [OPTIONS] [SUBCOMMAND] + +FLAGS: + -h, --help Prints help information + -u, --udp enable udp + -V, --version Prints version information + +OPTIONS: + -c, --config use config file + -l, --listen listen address + -r, --remote remote address + +SUBCOMMANDS +``` + +Start from command line arguments: +```shell +realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --udp +``` + +Use a config file: +```shell realm -c config.json ``` ->example.json ```json { "dns_mode": "ipv4_only", @@ -35,7 +60,7 @@ realm -c config.json ] } ``` ->dns_mode +dns_mode: ``` ipv4_only|ipv6_only|ipv4_and_ipv6|ipv4_then_ipv6|ipv6_then_ipv4 ``` diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index bfdf7710..bb7441ed 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -21,13 +21,15 @@ pub fn scan() -> CmdInput { .short("c") .long("config") .help("use config file") + .value_name("path") .takes_value(true), ) .arg( Arg::with_name("local") .short("l") - .long("local") + .long("listen") .help("listen address") + .value_name("addr") .takes_value(true), ) .arg( @@ -35,6 +37,7 @@ pub fn scan() -> CmdInput { .short("r") .long("remote") .help("remote address") + .value_name("addr") .takes_value(true), ) .arg( From 2344564ac7501afbc6ab1929662e0c0d70daf2ff Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 02:24:43 +0900 Subject: [PATCH 017/169] update deps & build cfgs --- Cargo.lock | 36 ++++-------------------------------- Cargo.toml | 11 +++++++++-- 2 files changed, 13 insertions(+), 34 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2975425d..64b80b3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +version = 3 + [[package]] name = "ansi_term" version = "0.11.0" @@ -45,9 +47,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "bytes" -version = "1.0.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" +checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" [[package]] name = "cfg-if" @@ -380,12 +382,6 @@ dependencies = [ "libc", ] -[[package]] -name = "once_cell" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" - [[package]] name = "parking_lot" version = "0.11.1" @@ -587,15 +583,6 @@ dependencies = [ "serde", ] -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - [[package]] name = "slab" version = "0.4.2" @@ -692,25 +679,10 @@ dependencies = [ "memchr", "mio", "num_cpus", - "once_cell", - "parking_lot", "pin-project-lite", - "signal-hook-registry", - "tokio-macros", "winapi", ] -[[package]] -name = "tokio-macros" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c49e3df43841dafb86046472506755d8501c5615673955f6aa17181125d13c37" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "trust-dns-proto" version = "0.20.1" diff --git a/Cargo.toml b/Cargo.toml index 56066894..e4b31c29 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,14 @@ clap = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } trust-dns-resolver = "0.20" -lazy_static = "1" \ No newline at end of file +lazy_static = "1" + +[profile.release] +opt-level = 3 +lto = true + +[profile.dev] +opt-level = 0 From 34ecba081f874cef1e8247f537fd8c99bbe1db2e Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 6 Sep 2021 02:28:09 +0900 Subject: [PATCH 018/169] bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/cmd/mod.rs | 2 +- src/main.rs | 2 ++ 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64b80b3a..a897954e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -509,7 +509,7 @@ dependencies = [ [[package]] name = "realm" -version = "1.3.0" +version = "1.4.0" dependencies = [ "clap", "futures", diff --git a/Cargo.toml b/Cargo.toml index e4b31c29..db9e12b7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "realm" -version = "1.3.0" +version = "1.4.0" authors = ["zhboner "] edition = "2018" diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index bb7441ed..c180ab26 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -14,7 +14,7 @@ pub enum CmdInput { pub fn scan() -> CmdInput { let matches = App::new("Realm") - .version("1.3-custom") + .version(super::VERSION) .about("A high efficiency proxy tool") .arg( Arg::with_name("config") diff --git a/src/main.rs b/src/main.rs index 6f87ba9f..7a3ab842 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,6 +7,8 @@ use cmd::CmdInput; use config::GlobalConfig; use relay::Endpoint; +const VERSION: &str = "1.4.0-rc1"; + fn main() { match cmd::scan() { CmdInput::Config(c) => start_from_config(c), From 72e0db5ad25e89d48a89ab7b8562ff44d94f9768 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 30 Oct 2021 00:48:37 +0900 Subject: [PATCH 019/169] use try_io; rename src file --- Cargo.lock | 5 +- Cargo.toml | 3 +- src/{config => conf}/dns_mode.rs | 0 src/{config => conf}/endpoint.rs | 2 +- src/{config => conf}/mod.rs | 0 src/main.rs | 10 +- src/relay/mod.rs | 8 +- src/relay/tcp.rs | 213 +++++++++++++++++++++---------- src/relay/udp.rs | 6 +- src/relay/zero_copy.rs | 47 ------- src/{relay => utils}/dns.rs | 10 +- src/utils/mod.rs | 8 +- src/{relay => utils}/types.rs | 6 +- 13 files changed, 169 insertions(+), 149 deletions(-) rename src/{config => conf}/dns_mode.rs (100%) rename src/{config => conf}/endpoint.rs (91%) rename src/{config => conf}/mod.rs (100%) delete mode 100644 src/relay/zero_copy.rs rename src/{relay => utils}/dns.rs (80%) rename src/{relay => utils}/types.rs (91%) diff --git a/Cargo.lock b/Cargo.lock index a897954e..ece1746b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -511,6 +511,7 @@ dependencies = [ name = "realm" version = "1.4.0" dependencies = [ + "cfg-if", "clap", "futures", "lazy_static", @@ -669,9 +670,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.8.1" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98c8b05dc14c75ea83d63dd391100353789f5f24b8b3866542a5e85c8be8e985" +checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" dependencies = [ "autocfg", "bytes", diff --git a/Cargo.toml b/Cargo.toml index db9e12b7..a532f3db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,12 +9,13 @@ edition = "2018" [dependencies] libc = "0.2" futures = "0.3" +cfg-if = "1" clap = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } +tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } trust-dns-resolver = "0.20" lazy_static = "1" diff --git a/src/config/dns_mode.rs b/src/conf/dns_mode.rs similarity index 100% rename from src/config/dns_mode.rs rename to src/conf/dns_mode.rs diff --git a/src/config/endpoint.rs b/src/conf/endpoint.rs similarity index 91% rename from src/config/endpoint.rs rename to src/conf/endpoint.rs index 19c2ff60..516cbe13 100644 --- a/src/config/endpoint.rs +++ b/src/conf/endpoint.rs @@ -1,5 +1,5 @@ use serde::{Serialize, Deserialize}; -use crate::relay::Endpoint; +use crate::utils::Endpoint; #[derive(Debug, Serialize, Deserialize)] pub struct EndpointConfig { diff --git a/src/config/mod.rs b/src/conf/mod.rs similarity index 100% rename from src/config/mod.rs rename to src/conf/mod.rs diff --git a/src/main.rs b/src/main.rs index 7a3ab842..8afc8fbb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,11 @@ mod cmd; -mod relay; +mod conf; mod utils; -mod config; +mod relay; use cmd::CmdInput; -use config::GlobalConfig; -use relay::Endpoint; +use conf::GlobalConfig; +use utils::Endpoint; const VERSION: &str = "1.4.0-rc1"; @@ -24,7 +24,7 @@ fn start_from_cmd(c: Endpoint) { fn start_from_config(c: String) { let config = GlobalConfig::from_config_file(&c); - relay::init_resolver(config.dns_mode.into()); + utils::init_resolver(config.dns_mode.into()); let eps: Vec = config .endpoints .into_iter() diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 87bb0ed7..da67841a 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -4,15 +4,9 @@ use futures::future::join_all; use tokio::io; use tokio::net::TcpListener; -mod dns; mod tcp; mod udp; -mod types; -pub use types::{Endpoint, RemoteAddr}; -pub use dns::init_resolver; - -#[cfg(target_os = "linux")] -mod zero_copy; +use crate::utils::{Endpoint, RemoteAddr}; pub async fn run(eps: Vec) { let mut workers = vec![]; diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index cd313585..5a0140c7 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -1,102 +1,175 @@ +use std::io::{Result, Error, ErrorKind}; use futures::try_join; -use tokio::io::{self, AsyncWriteExt}; use tokio::net::tcp::{ReadHalf, WriteHalf}; use tokio::net::TcpStream; -use super::types::RemoteAddr; +use crate::utils::RemoteAddr; -pub async fn proxy( - mut inbound: TcpStream, - remote: RemoteAddr, -) -> io::Result<()> { +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(target_os = "linux")] { + use zero_copy::copy; + const BUFFER_SIZE: usize = 0x10000; + } else { + use normal_copy::copy; + const BUFFER_SIZE: usize = 0x4000; + } +} + +pub async fn proxy(mut inbound: TcpStream, remote: RemoteAddr) -> Result<()> { let mut outbound = TcpStream::connect(remote.into_sockaddr().await?).await?; inbound.set_nodelay(true)?; outbound.set_nodelay(true)?; - let (mut ri, mut wi) = inbound.split(); - let (mut ro, mut wo) = outbound.split(); + let (ri, wi) = inbound.split(); + let (ro, wo) = outbound.split(); - let _ = try_join!(copy(&mut ri, &mut wo), copy(&mut ro, &mut wi)); + let _ = try_join!(copy(ri, wo), copy(ro, wi)); Ok(()) } -const BUFFERSIZE: usize = if cfg!(not(target_os = "linux")) { - 0x4000 // 16k read/write buffer -} else { - 0x10000 // 64k pipe buffer -}; - #[cfg(not(target_os = "linux"))] -async fn copy(r: &mut ReadHalf<'_>, w: &mut WriteHalf<'_>) -> io::Result<()> { - use io::AsyncReadExt; - let mut buf = vec![0u8; BUFFERSIZE]; - let mut n: usize; - loop { - n = r.read(&mut buf).await?; - if n == 0 { - break; +mod normal_copy { + use super::*; + pub async fn copy(mut r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> { + use tokio::io::{AsyncReadExt, AsyncWriteExt}; + let mut buf = vec![0u8; BUFFER_SIZE]; + let mut n: usize; + loop { + n = r.read(&mut buf).await?; + if n == 0 { + break; + } + w.write(&buf[..n]).await?; + w.flush().await?; } - w.write(&buf[..n]).await?; - w.flush().await?; + w.shutdown().await?; + Ok(()) } - w.shutdown().await?; - Ok(()) } -// zero copy #[cfg(target_os = "linux")] -use crate::relay::zero_copy; +mod zero_copy { + use super::*; + use std::ops::Drop; + use tokio::io::Interest; -#[cfg(target_os = "linux")] -async fn copy(r: &mut ReadHalf<'_>, w: &mut WriteHalf<'_>) -> io::Result<()> { - use std::os::unix::prelude::AsRawFd; - use zero_copy::{Pipe, splice_n, is_wouldblock}; - // create pipe - let pipe = Pipe::create()?; - let (rpipe, wpipe) = (pipe.0, pipe.1); - // get raw fd - let rfd = r.as_ref().as_raw_fd(); - let wfd = w.as_ref().as_raw_fd(); - let mut n: usize = 0; - let mut done = false; - - 'LOOP: loop { - // read until the socket buffer is empty - // or the pipe is filled - r.as_ref().readable().await?; - while n < BUFFERSIZE { - match splice_n(rfd, wpipe, BUFFERSIZE - n) { - x if x > 0 => n += x as usize, - x if x == 0 => { - done = true; - break; + struct Pipe(pub i32, pub i32); + + impl Drop for Pipe { + fn drop(&mut self) { + unsafe { + libc::close(self.0); + libc::close(self.1); + } + } + } + + impl Pipe { + fn create() -> Result { + use libc::{c_int, O_NONBLOCK}; + let mut pipes = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); + unsafe { + if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 + { + return Err(Error::new( + ErrorKind::Unsupported, + "failed to create a pipe", + )); } - x if x < 0 && is_wouldblock() => break, - _ => break 'LOOP, + Ok(Pipe(pipes.assume_init()[0], pipes.assume_init()[1])) } } - // write until the pipe is empty - while n > 0 { - w.as_ref().writable().await?; - match splice_n(rpipe, wfd, n) { - x if x > 0 => n -= x as usize, - x if x < 0 && is_wouldblock() => { - // clear readiness (EPOLLOUT) - let _ = r.as_ref().try_write(&[0u8; 0]); + } + + #[inline] + fn splice_n(r: i32, w: i32, n: usize) -> isize { + use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; + unsafe { + libc::splice( + r, + std::ptr::null_mut::(), + w, + std::ptr::null_mut::(), + n, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK, + ) + } + } + + #[inline] + fn is_wouldblock() -> bool { + use libc::{EAGAIN, EWOULDBLOCK}; + let errno = unsafe { *libc::__errno_location() }; + errno == EWOULDBLOCK || errno == EAGAIN + } + + #[inline] + fn clear_readiness(x: &TcpStream, interest: Interest) { + let _ = x.try_io(interest, || { + Err(Error::new(ErrorKind::WouldBlock, "")) as Result<()> + }); + } + + pub async fn copy(r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> { + use std::os::unix::io::AsRawFd; + use tokio::io::AsyncWriteExt; + // init pipe + let pipe = Pipe::create()?; + let (rpipe, wpipe) = (pipe.0, pipe.1); + // rw ref + let rx = r.as_ref(); + let wx = w.as_ref(); + // rw raw fd + let rfd = rx.as_raw_fd(); + let wfd = wx.as_raw_fd(); + // ctrl + let mut n: usize = 0; + let mut done = false; + + 'LOOP: loop { + // read until the socket buffer is empty + // or the pipe is filled + rx.readable().await?; + while n < BUFFER_SIZE { + match splice_n(rfd, wpipe, BUFFER_SIZE - n) { + x if x > 0 => n += x as usize, + x if x == 0 => { + done = true; + break; + } + x if x < 0 && is_wouldblock() => { + clear_readiness(rx, Interest::READABLE); + break; + } + _ => break 'LOOP, + } + } + // write until the pipe is empty + while n > 0 { + wx.writable().await?; + match splice_n(rpipe, wfd, n) { + x if x > 0 => n -= x as usize, + x if x < 0 && is_wouldblock() => { + clear_readiness(wx, Interest::WRITABLE) + } + _ => break 'LOOP, } - _ => break 'LOOP, + } + // complete + if done { + break; } } - // complete + if done { - break; + w.shutdown().await?; + Ok(()) + } else { + Err(Error::new(ErrorKind::ConnectionReset, "connection reset")) } - // clear readiness (EPOLLIN) - let _ = r.as_ref().try_read(&mut [0u8; 0]); } - - w.shutdown().await?; - Ok(()) } diff --git a/src/relay/udp.rs b/src/relay/udp.rs index e6dea6fa..fd1c6bd4 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -1,20 +1,20 @@ +use std::io::Result; use std::time::Duration; use std::net::SocketAddr; use std::sync::{Arc, RwLock}; use std::collections::HashMap; -use std::io; use tokio::net::UdpSocket; use tokio::time::timeout; -use super::types::RemoteAddr; +use crate::utils::RemoteAddr; // client <--> allocated socket type SockMap = Arc>>>; const BUFFERSIZE: usize = 0x1000; const TIMEOUT: Duration = Duration::from_secs(20); -pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { +pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> Result<()> { let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let local_sock = Arc::new(UdpSocket::bind(&local).await.unwrap()); let mut buf = vec![0u8; BUFFERSIZE]; diff --git a/src/relay/zero_copy.rs b/src/relay/zero_copy.rs deleted file mode 100644 index 1bbc4f76..00000000 --- a/src/relay/zero_copy.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::ops::Drop; -use tokio::io; -use crate::utils; - -pub struct Pipe(pub i32, pub i32); - -impl Drop for Pipe { - fn drop(&mut self) { - unsafe { - libc::close(self.0); - libc::close(self.1); - } - } -} - -impl Pipe { - pub fn create() -> io::Result { - use libc::{c_int, O_NONBLOCK}; - let mut pipes = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); - unsafe { - if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 { - return Err(utils::new_io_err("failed to create a pipe")); - } - Ok(Pipe(pipes.assume_init()[0], pipes.assume_init()[1])) - } - } -} - -pub fn splice_n(r: i32, w: i32, n: usize) -> isize { - use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; - unsafe { - libc::splice( - r, - std::ptr::null_mut::(), - w, - std::ptr::null_mut::(), - n, - SPLICE_F_MOVE | SPLICE_F_NONBLOCK, - ) - } -} - -pub fn is_wouldblock() -> bool { - use libc::{EAGAIN, EWOULDBLOCK}; - let errno = unsafe { *libc::__errno_location() }; - errno == EWOULDBLOCK || errno == EAGAIN -} diff --git a/src/relay/dns.rs b/src/utils/dns.rs similarity index 80% rename from src/relay/dns.rs rename to src/utils/dns.rs index 5df9e5e1..a0ce82ce 100644 --- a/src/relay/dns.rs +++ b/src/utils/dns.rs @@ -1,13 +1,11 @@ +use std::io::{Result, Error, ErrorKind}; use std::net::IpAddr; -use tokio::io; use tokio::runtime::Runtime; use trust_dns_resolver::TokioAsyncResolver; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts, LookupIpStrategy}; use lazy_static::lazy_static; -use crate::utils; - static mut RESOLVE_STRATEGY: LookupIpStrategy = LookupIpStrategy::Ipv4thenIpv6; lazy_static! { @@ -26,16 +24,16 @@ pub fn init_resolver(strategy: LookupIpStrategy) { lazy_static::initialize(&DNS); } -pub fn resolve_sync(addr: &str) -> io::Result { +pub fn resolve_sync(addr: &str) -> Result { let rt = Runtime::new().unwrap(); rt.block_on(resolve_async(addr)) } -pub async fn resolve_async(addr: &str) -> io::Result { +pub async fn resolve_async(addr: &str) -> Result { let res = DNS .lookup_ip(addr) .await - .map_err(|e| utils::new_io_err(&e.to_string()))? + .map_err(|e| Error::new(ErrorKind::Other, e))? .into_iter() .next() .unwrap(); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index a9f324f1..8d8a26ee 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,5 +1,5 @@ -use tokio::io; +mod dns; +mod types; -pub fn new_io_err(e: &str) -> io::Error { - io::Error::new(io::ErrorKind::Other, e) -} +pub use dns::*; +pub use types::*; diff --git a/src/relay/types.rs b/src/utils/types.rs similarity index 91% rename from src/relay/types.rs rename to src/utils/types.rs index 69586992..5fa51a7e 100644 --- a/src/relay/types.rs +++ b/src/utils/types.rs @@ -1,5 +1,5 @@ +use std::io::Result; use std::net::{SocketAddr, ToSocketAddrs}; -use tokio::io; use super::dns; #[derive(Clone)] @@ -15,7 +15,7 @@ pub struct Endpoint { } impl RemoteAddr { - pub async fn into_sockaddr(self) -> io::Result { + pub async fn into_sockaddr(self) -> Result { match self { Self::SocketAddr(sockaddr) => Ok(sockaddr), Self::DomainName(addr, port) => { @@ -24,7 +24,7 @@ impl RemoteAddr { } } } - pub async fn to_sockaddr(&self) -> io::Result { + pub async fn to_sockaddr(&self) -> Result { match self { Self::SocketAddr(sockaddr) => Ok(*sockaddr), Self::DomainName(addr, port) => { From 816e26245161e7950c4f7d85574c3be94860a5c9 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 30 Oct 2021 18:53:14 +0900 Subject: [PATCH 020/169] tfo support --- Cargo.lock | 72 ++++++++++++++++++++- Cargo.toml | 1 + src/relay/mod.rs | 11 ++-- src/relay/tcp.rs | 159 ++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 231 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ece1746b..f7a6df63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -260,7 +260,7 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" dependencies = [ - "socket2", + "socket2 0.3.19", "widestring", "winapi", "winreg", @@ -286,9 +286,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.93" +version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" +checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" [[package]] name = "linked-hash-map" @@ -382,6 +382,12 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + [[package]] name = "parking_lot" version = "0.11.1" @@ -413,6 +419,26 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" +[[package]] +name = "pin-project" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.6" @@ -519,6 +545,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tokio-tfo", "trust-dns-resolver", ] @@ -607,6 +634,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "strsim" version = "0.8.0" @@ -681,6 +718,35 @@ dependencies = [ "mio", "num_cpus", "pin-project-lite", + "tokio-macros", + "winapi", +] + +[[package]] +name = "tokio-macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-tfo" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e145fcaa4c3dc54e42c8877aef0d7fcb1529e2dbba0033757ebd9156e24174b8" +dependencies = [ + "cfg-if", + "futures", + "libc", + "log", + "once_cell", + "pin-project", + "socket2 0.4.2", + "tokio", "winapi", ] diff --git a/Cargo.toml b/Cargo.toml index a532f3db..7d7d9a50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ serde_json = "1" tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } trust-dns-resolver = "0.20" +tokio-tfo = "0.1.4" lazy_static = "1" diff --git a/src/relay/mod.rs b/src/relay/mod.rs index da67841a..85937736 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -1,11 +1,10 @@ +use std::io::Result; use std::net::SocketAddr; use futures::future::join_all; -use tokio::io; -use tokio::net::TcpListener; - mod tcp; mod udp; +use tcp::TcpListener; use crate::utils::{Endpoint, RemoteAddr}; pub async fn run(eps: Vec) { @@ -19,14 +18,14 @@ pub async fn run(eps: Vec) { join_all(workers).await; } -async fn proxy_tcp(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { - let lis = TcpListener::bind(&local).await.expect("unable to bind"); +async fn proxy_tcp(local: SocketAddr, remote: RemoteAddr) -> Result<()> { + let lis = TcpListener::bind(local).await.expect("unable to bind"); while let Ok((stream, _)) = lis.accept().await { tokio::spawn(tcp::proxy(stream, remote.clone())); } Ok(()) } -async fn proxy_udp(local: SocketAddr, remote: RemoteAddr) -> io::Result<()> { +async fn proxy_udp(local: SocketAddr, remote: RemoteAddr) -> Result<()> { udp::proxy(local, remote).await } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 5a0140c7..0174a48b 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -1,13 +1,22 @@ use std::io::{Result, Error, ErrorKind}; use futures::try_join; -use tokio::net::tcp::{ReadHalf, WriteHalf}; -use tokio::net::TcpStream; - use crate::utils::RemoteAddr; use cfg_if::cfg_if; +cfg_if! { + if #[cfg(all(feature = "tfo", not(target_os = "linux")))] { + use tfo::TcpStream; + use tfo::{ReadHalf, WriteHalf}; + pub use tfo::TcpListener; + } else { + use tokio::net::TcpStream; + use tokio::net::tcp::{ReadHalf, WriteHalf}; + pub use tokio::net::TcpListener; + } +} + cfg_if! { if #[cfg(target_os = "linux")] { use zero_copy::copy; @@ -173,3 +182,147 @@ mod zero_copy { } } } + +mod tfo { + use std::io::Result; + use std::net::SocketAddr; + use std::pin::Pin; + use std::task::{Poll, Context}; + + use tokio_tfo::{TfoStream, TfoListener}; + use tokio::io::{AsyncRead, AsyncWrite}; + use tokio::io::ReadBuf; + + pub struct TcpListener(TfoListener); + + pub struct TcpStream(TfoStream); + pub struct ReadHalf<'a>(&'a TcpStream); + pub struct WriteHalf<'a>(&'a TcpStream); + + #[allow(clippy::mut_from_ref)] + #[inline] + unsafe fn const_cast(x: &T) -> &mut T { + let const_ptr = x as *const T; + let mut_ptr = const_ptr as *mut T; + &mut *mut_ptr + } + + impl TcpListener { + pub async fn bind(addr: SocketAddr) -> Result { + TfoListener::bind(addr).await.map(|x| TcpListener(x)) + } + + pub async fn accept(&self) -> Result<(TcpStream, SocketAddr)> { + self.0.accept().await.map(|(x, addr)| (TcpStream(x), addr)) + } + } + + impl TcpStream { + pub async fn connect(addr: SocketAddr) -> Result { + TfoStream::connect(addr).await.map(|x| TcpStream(x)) + } + + pub fn set_nodelay(&self, nodelay: bool) -> Result<()> { + self.0.set_nodelay(nodelay) + } + + pub fn split<'a>(&'a mut self) -> (ReadHalf<'a>, WriteHalf<'a>) { + (ReadHalf(&*self), WriteHalf(&*self)) + } + } + + impl AsyncRead for TcpStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_read(cx, buf) + } + } + + impl AsyncWrite for TcpStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_write(cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_shutdown(cx) + } + } + + impl<'a> AsyncRead for ReadHalf<'a> { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(unsafe { const_cast(self.0) }).poll_read(cx, buf) + } + } + + impl<'a> AsyncWrite for WriteHalf<'a> { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(unsafe { const_cast(self.0) }).poll_write(cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(unsafe { const_cast(self.0) }).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(unsafe { const_cast(self.0) }).poll_shutdown(cx) + } + } + + impl<'a> AsRef for ReadHalf<'a> { + fn as_ref(&self) -> &TcpStream { + self.0 + } + } + + impl<'a> AsRef for WriteHalf<'a> { + fn as_ref(&self) -> &TcpStream { + self.0 + } + } + + #[cfg(target_os = "linux")] + mod linux_ext { + use super::*; + use std::os::unix::io::AsRawFd; + use std::os::unix::prelude::RawFd; + impl AsRawFd for TcpStream { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } + } + + #[cfg(target_os = "linux")] + use linux_ext::*; +} From 3047faa0e05939d5c042f074997b0ebd21571c10 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 30 Oct 2021 19:00:34 +0900 Subject: [PATCH 021/169] add tfo to features --- Cargo.toml | 6 +++++- src/relay/tcp.rs | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7d7d9a50..e252577b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1" tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } trust-dns-resolver = "0.20" -tokio-tfo = "0.1.4" +tokio-tfo = { version = "0.1.4", optional = true } lazy_static = "1" @@ -27,3 +27,7 @@ lto = true [profile.dev] opt-level = 0 + +[features] +default = [] +tfo = ["tokio-tfo"] diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 0174a48b..7ae0ec41 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -183,6 +183,7 @@ mod zero_copy { } } +#[cfg(feature = "tfo")] mod tfo { use std::io::Result; use std::net::SocketAddr; From f817c73765d938b526cbf246f090b0e4de07848d Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 30 Oct 2021 22:39:32 +0900 Subject: [PATCH 022/169] add tokio-tfo submodule --- .gitmodules | 3 +++ Cargo.lock | 2 -- Cargo.toml | 4 ++-- tokio-tfo | 1 + 4 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 .gitmodules create mode 160000 tokio-tfo diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..198bc68e --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "tokio-tfo"] + path = tokio-tfo + url = https://github.com/zonyitoo/tokio-tfo diff --git a/Cargo.lock b/Cargo.lock index f7a6df63..c93b084a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -736,8 +736,6 @@ dependencies = [ [[package]] name = "tokio-tfo" version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e145fcaa4c3dc54e42c8877aef0d7fcb1529e2dbba0033757ebd9156e24174b8" dependencies = [ "cfg-if", "futures", diff --git a/Cargo.toml b/Cargo.toml index e252577b..ce2409b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,7 @@ serde_json = "1" tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } trust-dns-resolver = "0.20" -tokio-tfo = { version = "0.1.4", optional = true } +tokio-tfo = { path = "tokio-tfo", version = "0.1.4", optional = true } lazy_static = "1" @@ -29,5 +29,5 @@ lto = true opt-level = 0 [features] -default = [] +default = ["tfo"] tfo = ["tokio-tfo"] diff --git a/tokio-tfo b/tokio-tfo new file mode 160000 index 00000000..e69137c7 --- /dev/null +++ b/tokio-tfo @@ -0,0 +1 @@ +Subproject commit e69137c75f711a6cbe75780d496c6e01c724a2ff From 193fd16588508a03dc38277825c6bf1cd7a5bcd7 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 30 Oct 2021 22:45:34 +0900 Subject: [PATCH 023/169] compatible with zero-copy(kernel>=4.11) --- src/relay/tcp.rs | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 7ae0ec41..5e3ba8bc 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -6,7 +6,7 @@ use crate::utils::RemoteAddr; use cfg_if::cfg_if; cfg_if! { - if #[cfg(all(feature = "tfo", not(target_os = "linux")))] { + if #[cfg(feature = "tfo")] { use tfo::TcpStream; use tfo::{ReadHalf, WriteHalf}; pub use tfo::TcpListener; @@ -193,6 +193,7 @@ mod tfo { use tokio_tfo::{TfoStream, TfoListener}; use tokio::io::{AsyncRead, AsyncWrite}; use tokio::io::ReadBuf; + use tokio::io::Interest; pub struct TcpListener(TfoListener); @@ -223,6 +224,14 @@ mod tfo { TfoStream::connect(addr).await.map(|x| TcpStream(x)) } + pub async fn readable(&self) -> Result<()> { + self.0.inner().readable().await + } + + pub async fn writable(&self) -> Result<()> { + self.0.inner().writable().await + } + pub fn set_nodelay(&self, nodelay: bool) -> Result<()> { self.0.set_nodelay(nodelay) } @@ -230,6 +239,14 @@ mod tfo { pub fn split<'a>(&'a mut self) -> (ReadHalf<'a>, WriteHalf<'a>) { (ReadHalf(&*self), WriteHalf(&*self)) } + + pub fn try_io( + &self, + interest: Interest, + f: impl FnOnce() -> Result, + ) -> Result { + self.0.inner().try_io(interest, f) + } } impl AsyncRead for TcpStream { @@ -323,7 +340,4 @@ mod tfo { } } } - - #[cfg(target_os = "linux")] - use linux_ext::*; } From 83322115b6fef67dd0029d25c26e01e1b143694b Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 30 Oct 2021 23:02:06 +0900 Subject: [PATCH 024/169] clean up --- src/relay/tcp.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 5e3ba8bc..9c31810e 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -1,8 +1,3 @@ -use std::io::{Result, Error, ErrorKind}; -use futures::try_join; - -use crate::utils::RemoteAddr; - use cfg_if::cfg_if; cfg_if! { @@ -27,6 +22,10 @@ cfg_if! { } } +use std::io::Result; +use futures::try_join; +use crate::utils::RemoteAddr; + pub async fn proxy(mut inbound: TcpStream, remote: RemoteAddr) -> Result<()> { let mut outbound = TcpStream::connect(remote.into_sockaddr().await?).await?; @@ -64,6 +63,7 @@ mod normal_copy { mod zero_copy { use super::*; use std::ops::Drop; + use std::io::{Error, ErrorKind}; use tokio::io::Interest; struct Pipe(pub i32, pub i32); @@ -211,7 +211,7 @@ mod tfo { impl TcpListener { pub async fn bind(addr: SocketAddr) -> Result { - TfoListener::bind(addr).await.map(|x| TcpListener(x)) + TfoListener::bind(addr).await.map(TcpListener) } pub async fn accept(&self) -> Result<(TcpStream, SocketAddr)> { @@ -221,13 +221,15 @@ mod tfo { impl TcpStream { pub async fn connect(addr: SocketAddr) -> Result { - TfoStream::connect(addr).await.map(|x| TcpStream(x)) + TfoStream::connect(addr).await.map(TcpStream) } + #[allow(unused)] pub async fn readable(&self) -> Result<()> { self.0.inner().readable().await } + #[allow(unused)] pub async fn writable(&self) -> Result<()> { self.0.inner().writable().await } @@ -236,10 +238,11 @@ mod tfo { self.0.set_nodelay(nodelay) } - pub fn split<'a>(&'a mut self) -> (ReadHalf<'a>, WriteHalf<'a>) { + pub fn split(&mut self) -> (ReadHalf, WriteHalf) { (ReadHalf(&*self), WriteHalf(&*self)) } + #[allow(unused)] pub fn try_io( &self, interest: Interest, From 1be9565d6e4dd10aae487192741050396ed973a6 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 31 Oct 2021 00:23:39 +0900 Subject: [PATCH 025/169] sync submodules --- .gitmodules | 3 ++- tokio-tfo | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitmodules b/.gitmodules index 198bc68e..01b6a5b1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,4 @@ [submodule "tokio-tfo"] path = tokio-tfo - url = https://github.com/zonyitoo/tokio-tfo + url = git@github.com:zephyrchien/tokio-tfo.git + branch = main diff --git a/tokio-tfo b/tokio-tfo index e69137c7..43541091 160000 --- a/tokio-tfo +++ b/tokio-tfo @@ -1 +1 @@ -Subproject commit e69137c75f711a6cbe75780d496c6e01c724a2ff +Subproject commit 43541091350acf639b674e64f5068e420ae675fe From 1bbfaa360394cbd12bfa07387ef6ae76a3b13c64 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 31 Oct 2021 00:51:12 +0900 Subject: [PATCH 026/169] make [udp, tfo, zero-copy] optional --- Cargo.lock | 1 + Cargo.toml | 14 ++++++++++---- src/conf/endpoint.rs | 1 + src/relay/mod.rs | 6 +++++- src/relay/tcp.rs | 6 +++--- src/utils/types.rs | 1 + 6 files changed, 21 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c93b084a..d747aee2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -736,6 +736,7 @@ dependencies = [ [[package]] name = "tokio-tfo" version = "0.1.4" +source = "git+https://github.com/zephyrchien/tokio-tfo?branch=main#43541091350acf639b674e64f5068e420ae675fe" dependencies = [ "cfg-if", "futures", diff --git a/Cargo.toml b/Cargo.toml index ce2409b4..25bf9466 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,9 +7,8 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -libc = "0.2" -futures = "0.3" cfg-if = "1" +futures = "0.3" clap = "2" serde = { version = "1", features = ["derive"] } @@ -17,10 +16,15 @@ serde_json = "1" tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } trust-dns-resolver = "0.20" -tokio-tfo = { path = "tokio-tfo", version = "0.1.4", optional = true } lazy_static = "1" +# tfo +tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.4", optional = true } + +# zero-copy +libc = { version = "0.2", optional = true } + [profile.release] opt-level = 3 lto = true @@ -29,5 +33,7 @@ lto = true opt-level = 0 [features] -default = ["tfo"] +default = [] +udp = [] tfo = ["tokio-tfo"] +zero-copy = ["libc"] \ No newline at end of file diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index 516cbe13..ca937155 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -3,6 +3,7 @@ use crate::utils::Endpoint; #[derive(Debug, Serialize, Deserialize)] pub struct EndpointConfig { + #[serde(default)] udp: bool, local: String, remote: String, diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 85937736..78124fee 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -3,13 +3,13 @@ use std::net::SocketAddr; use futures::future::join_all; mod tcp; -mod udp; use tcp::TcpListener; use crate::utils::{Endpoint, RemoteAddr}; pub async fn run(eps: Vec) { let mut workers = vec![]; for ep in eps.into_iter() { + #[cfg(feature = "udp")] if ep.udp { workers.push(tokio::spawn(proxy_udp(ep.local, ep.remote.clone()))) } @@ -26,6 +26,10 @@ async fn proxy_tcp(local: SocketAddr, remote: RemoteAddr) -> Result<()> { Ok(()) } +#[cfg(feature = "udp")] +mod udp; + +#[cfg(feature = "udp")] async fn proxy_udp(local: SocketAddr, remote: RemoteAddr) -> Result<()> { udp::proxy(local, remote).await } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 9c31810e..88b1cdae 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -13,7 +13,7 @@ cfg_if! { } cfg_if! { - if #[cfg(target_os = "linux")] { + if #[cfg(all(target_os = "linux", feature = "zero-copy"))] { use zero_copy::copy; const BUFFER_SIZE: usize = 0x10000; } else { @@ -39,7 +39,7 @@ pub async fn proxy(mut inbound: TcpStream, remote: RemoteAddr) -> Result<()> { Ok(()) } -#[cfg(not(target_os = "linux"))] +#[cfg(not(all(target_os = "linux", feature = "zero-copy")))] mod normal_copy { use super::*; pub async fn copy(mut r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> { @@ -59,7 +59,7 @@ mod normal_copy { } } -#[cfg(target_os = "linux")] +#[cfg(all(target_os = "linux", feature = "zero-copy"))] mod zero_copy { use super::*; use std::ops::Drop; diff --git a/src/utils/types.rs b/src/utils/types.rs index 5fa51a7e..fdbac621 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -24,6 +24,7 @@ impl RemoteAddr { } } } + #[allow(unused)] pub async fn to_sockaddr(&self) -> Result { match self { Self::SocketAddr(sockaddr) => Ok(*sockaddr), From ddcb4f80bf8d3b63dfbdf980408670418db520d9 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 31 Oct 2021 00:52:02 +0900 Subject: [PATCH 027/169] v1.4.0-rc2 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 8afc8fbb..37952853 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use cmd::CmdInput; use conf::GlobalConfig; use utils::Endpoint; -const VERSION: &str = "1.4.0-rc1"; +const VERSION: &str = "1.4.0-rc2"; fn main() { match cmd::scan() { From b34b55171183fd359c7b9bd386e7686fa4e6edec Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 31 Oct 2021 01:07:08 +0900 Subject: [PATCH 028/169] docs --- readme.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 7814e450..92d65987 100644 --- a/readme.md +++ b/readme.md @@ -30,8 +30,6 @@ OPTIONS: -c, --config use config file -l, --listen listen address -r, --remote remote address - -SUBCOMMANDS ``` Start from command line arguments: @@ -64,3 +62,17 @@ dns_mode: ``` ipv4_only|ipv6_only|ipv4_and_ipv6|ipv4_then_ipv6|ipv6_then_ipv4 ``` + +## Custom Build +Available Options: +- udp +- tfo +- zero-copy + +```shell +# simple tcp +cargo build --release --no-default-features + +# enable other options +cargo build --release --no-default-features --features udp, tfo, zero-copy +``` From 53417a22879d0391a753c2d1e82661ca52b6f40c Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 31 Oct 2021 01:08:52 +0900 Subject: [PATCH 029/169] enable all features by default --- Cargo.toml | 2 +- src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 25bf9466..8f3d1484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ lto = true opt-level = 0 [features] -default = [] +default = ["udp", "tfo", "zero-copy"] udp = [] tfo = ["tokio-tfo"] zero-copy = ["libc"] \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 37952853..3cbe5d7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use cmd::CmdInput; use conf::GlobalConfig; use utils::Endpoint; -const VERSION: &str = "1.4.0-rc2"; +const VERSION: &str = "1.4.0-rc3"; fn main() { match cmd::scan() { From 828157b5f88195b073b0832c39062d95b2845fa7 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 00:27:33 +0900 Subject: [PATCH 030/169] disable tfo by default --- Cargo.lock | 2 +- Cargo.toml | 4 ++-- readme.md | 43 ++++++++++++++++++++++--------------------- src/main.rs | 2 +- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d747aee2..799fbf8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -535,7 +535,7 @@ dependencies = [ [[package]] name = "realm" -version = "1.4.0" +version = "1.5.0" dependencies = [ "cfg-if", "clap", diff --git a/Cargo.toml b/Cargo.toml index 8f3d1484..b3b9dc25 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "realm" -version = "1.4.0" +version = "1.5.0" authors = ["zhboner "] edition = "2018" @@ -33,7 +33,7 @@ lto = true opt-level = 0 [features] -default = ["udp", "tfo", "zero-copy"] +default = ["udp", "zero-copy"] udp = [] tfo = ["tokio-tfo"] zero-copy = ["libc"] \ No newline at end of file diff --git a/readme.md b/readme.md index 92d65987..bec28fe8 100644 --- a/readme.md +++ b/readme.md @@ -13,6 +13,20 @@ realm is a simple, high performance relay server written in rust. - Concurrency. Bidirectional concurrent traffic leads to high performance. - Low resources cost. +## Custom Build +Available Options: +- udp *(enabled)* +- tfo *(disabled)* +- zero-copy *(enabled on linux)* + +```shell +# simple tcp +cargo build --release --no-default-features + +# enable other options +cargo build --release --no-default-features --features udp, tfo, zero-copy +``` + ## Usage ```shell Realm 1.x @@ -47,10 +61,9 @@ realm -c config.json "endpoints": [ { "local": "0.0.0.0:5000", - "remote": "1.1.1.1:443", - "udp": false - } - { + "remote": "1.1.1.1:443" + }, + { "local": "0.0.0.0:10000", "remote": "www.google.com:443", "udp": true @@ -59,20 +72,8 @@ realm -c config.json } ``` dns_mode: -``` -ipv4_only|ipv6_only|ipv4_and_ipv6|ipv4_then_ipv6|ipv6_then_ipv4 -``` - -## Custom Build -Available Options: -- udp -- tfo -- zero-copy - -```shell -# simple tcp -cargo build --release --no-default-features - -# enable other options -cargo build --release --no-default-features --features udp, tfo, zero-copy -``` +- ipv4_only +- ipv6_only +- ipv4_then_ipv6 *(default)* +- ipv6_then_ipv4 +- ipv4_and_ipv6 diff --git a/src/main.rs b/src/main.rs index 3cbe5d7b..63cc4f68 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use cmd::CmdInput; use conf::GlobalConfig; use utils::Endpoint; -const VERSION: &str = "1.4.0-rc3"; +const VERSION: &str = "1.5.0"; fn main() { match cmd::scan() { From 59aa607f338991316a24cb474327b78b29ec476e Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 00:30:46 +0900 Subject: [PATCH 031/169] indent --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index bec28fe8..b22776b0 100644 --- a/readme.md +++ b/readme.md @@ -63,7 +63,7 @@ realm -c config.json "local": "0.0.0.0:5000", "remote": "1.1.1.1:443" }, - { + { "local": "0.0.0.0:10000", "remote": "www.google.com:443", "udp": true From 8ea95ff8ac7e19ce7eacc571e8fda3f21783128f Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 11:33:04 +0900 Subject: [PATCH 032/169] update deps --- Cargo.lock | 4 ++-- Cargo.toml | 4 ++-- tokio-tfo | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 799fbf8c..362aceb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -735,8 +735,8 @@ dependencies = [ [[package]] name = "tokio-tfo" -version = "0.1.4" -source = "git+https://github.com/zephyrchien/tokio-tfo?branch=main#43541091350acf639b674e64f5068e420ae675fe" +version = "0.1.6" +source = "git+https://github.com/zephyrchien/tokio-tfo?branch=main#e64331f7e056f74c158281ca100b6758e43e41cf" dependencies = [ "cfg-if", "futures", diff --git a/Cargo.toml b/Cargo.toml index b3b9dc25..6e4615ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,13 @@ clap = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" -tokio = { version = "1.12", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } +tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } trust-dns-resolver = "0.20" lazy_static = "1" # tfo -tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.4", optional = true } +tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.6", optional = true } # zero-copy libc = { version = "0.2", optional = true } diff --git a/tokio-tfo b/tokio-tfo index 43541091..e64331f7 160000 --- a/tokio-tfo +++ b/tokio-tfo @@ -1 +1 @@ -Subproject commit 43541091350acf639b674e64f5068e420ae675fe +Subproject commit e64331f7e056f74c158281ca100b6758e43e41cf From 8b303210474ba62263a979651b28308addf6499c Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 16:43:19 +0900 Subject: [PATCH 033/169] support bind ip before connect --- src/cmd/mod.rs | 9 +++++++++ src/conf/endpoint.rs | 6 ++++-- src/main.rs | 2 +- src/relay/mod.rs | 10 +++++----- src/relay/tcp.rs | 25 ++++++++++++++++++++++--- src/relay/udp.rs | 24 ++++++++++++++++++------ src/utils/mod.rs | 14 ++++++++++++++ src/utils/types.rs | 33 +++++++++++++++++++++++++++++---- 8 files changed, 102 insertions(+), 21 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index c180ab26..2d646b16 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -40,6 +40,14 @@ pub fn scan() -> CmdInput { .value_name("addr") .takes_value(true), ) + .arg( + Arg::with_name("through") + .short("x") + .long("through") + .help("send through") + .value_name("addr") + .takes_value(true), + ) .arg( Arg::with_name("udp") .short("u") @@ -64,6 +72,7 @@ pub fn scan() -> CmdInput { return CmdInput::Endpoint(Endpoint::new( local, remote, + matches.value_of("through").unwrap_or(""), matches.is_present("udp"), )); } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index ca937155..f628c153 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -7,10 +7,12 @@ pub struct EndpointConfig { udp: bool, local: String, remote: String, + #[serde(default)] + through: String, } impl EndpointConfig { - pub fn into_endpoint(self) -> Endpoint { - Endpoint::new(&self.local, &self.remote, self.udp) + pub fn build(&self) -> Endpoint { + Endpoint::new(&self.local, &self.remote, &self.through, self.udp) } } diff --git a/src/main.rs b/src/main.rs index 63cc4f68..157cbcf7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,7 +28,7 @@ fn start_from_config(c: String) { let eps: Vec = config .endpoints .into_iter() - .map(|epc| epc.into_endpoint()) + .map(|epc| epc.build()) .collect(); run_relay(eps); } diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 78124fee..f4342235 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -4,7 +4,7 @@ use futures::future::join_all; mod tcp; use tcp::TcpListener; -use crate::utils::{Endpoint, RemoteAddr}; +use crate::utils::{Endpoint, Remote}; pub async fn run(eps: Vec) { let mut workers = vec![]; @@ -18,10 +18,10 @@ pub async fn run(eps: Vec) { join_all(workers).await; } -async fn proxy_tcp(local: SocketAddr, remote: RemoteAddr) -> Result<()> { +async fn proxy_tcp(local: SocketAddr, remote: Remote) -> Result<()> { let lis = TcpListener::bind(local).await.expect("unable to bind"); while let Ok((stream, _)) = lis.accept().await { - tokio::spawn(tcp::proxy(stream, remote.clone())); + tokio::spawn(tcp::proxy(stream, remote.addr.clone(), remote.through)); } Ok(()) } @@ -30,6 +30,6 @@ async fn proxy_tcp(local: SocketAddr, remote: RemoteAddr) -> Result<()> { mod udp; #[cfg(feature = "udp")] -async fn proxy_udp(local: SocketAddr, remote: RemoteAddr) -> Result<()> { - udp::proxy(local, remote).await +async fn proxy_udp(local: SocketAddr, remote: Remote) -> Result<()> { + udp::proxy(local, remote.addr.clone(), remote.through).await } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 88b1cdae..ecc9317f 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -23,12 +23,31 @@ cfg_if! { } use std::io::Result; +use std::net::SocketAddr; use futures::try_join; +use tokio::net::TcpSocket; use crate::utils::RemoteAddr; -pub async fn proxy(mut inbound: TcpStream, remote: RemoteAddr) -> Result<()> { - let mut outbound = - TcpStream::connect(remote.into_sockaddr().await?).await?; +pub async fn proxy( + mut inbound: TcpStream, + remote: RemoteAddr, + through: Option, +) -> Result<()> { + let remote = remote.into_sockaddr().await?; + let mut outbound = match through { + Some(x) => { + let socket = match x { + SocketAddr::V4(_) => TcpSocket::new_v4()?, + SocketAddr::V6(_) => TcpSocket::new_v6()?, + }; + socket.set_reuseaddr(true)?; + #[cfg(unix)] + socket.set_reuseport(true)?; + socket.bind(x)?; + socket.connect(remote).await? + } + None => TcpStream::connect(remote).await?, + }; inbound.set_nodelay(true)?; outbound.set_nodelay(true)?; let (ri, wi) = inbound.split(); diff --git a/src/relay/udp.rs b/src/relay/udp.rs index fd1c6bd4..319c7be0 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -8,13 +8,18 @@ use tokio::net::UdpSocket; use tokio::time::timeout; use crate::utils::RemoteAddr; +use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; // client <--> allocated socket type SockMap = Arc>>>; const BUFFERSIZE: usize = 0x1000; const TIMEOUT: Duration = Duration::from_secs(20); -pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> Result<()> { +pub async fn proxy( + local: SocketAddr, + remote: RemoteAddr, + through: Option, +) -> Result<()> { let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let local_sock = Arc::new(UdpSocket::bind(&local).await.unwrap()); let mut buf = vec![0u8; BUFFERSIZE]; @@ -32,6 +37,7 @@ pub async fn proxy(local: SocketAddr, remote: RemoteAddr) -> Result<()> { &sock_map, client_addr, &remote_addr, + &through, local_sock.clone(), ) .await @@ -75,15 +81,21 @@ async fn alloc_new_socket( sock_map: &SockMap, client_addr: SocketAddr, remote_addr: &SocketAddr, + send_through: &Option, local_sock: Arc, ) -> Arc { // pick a random port - let alloc_sock = Arc::new(if remote_addr.is_ipv4() { - UdpSocket::bind("0.0.0.0:0").await.unwrap() - } else { - UdpSocket::bind("[::]:0").await.unwrap() + let alloc_sock = Arc::new(match send_through { + Some(x) => UdpSocket::bind(x).await.unwrap(), + None => match remote_addr { + SocketAddr::V4(_) => { + UdpSocket::bind(new_sockaddr_v4()).await.unwrap() + } + SocketAddr::V6(_) => { + UdpSocket::bind(new_sockaddr_v6()).await.unwrap() + } + }, }); - // new send back task tokio::spawn(send_back( sock_map.clone(), diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8d8a26ee..96e703a2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -3,3 +3,17 @@ mod types; pub use dns::*; pub use types::*; + +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; + +#[allow(unused)] +#[inline] +pub fn new_sockaddr_v4() -> SocketAddr { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0) +} + +#[allow(unused)] +#[inline] +pub fn new_sockaddr_v6() -> SocketAddr { + SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0) +} diff --git a/src/utils/types.rs b/src/utils/types.rs index fdbac621..fbc141d8 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -1,5 +1,5 @@ use std::io::Result; -use std::net::{SocketAddr, ToSocketAddrs}; +use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use super::dns; #[derive(Clone)] @@ -8,10 +8,16 @@ pub enum RemoteAddr { DomainName(String, u16), } +#[derive(Clone)] +pub struct Remote { + pub addr: RemoteAddr, + pub through: Option, +} + pub struct Endpoint { pub udp: bool, pub local: SocketAddr, - pub remote: RemoteAddr, + pub remote: Remote, } impl RemoteAddr { @@ -37,12 +43,15 @@ impl RemoteAddr { } impl Endpoint { - pub fn new(local: &str, remote: &str, udp: bool) -> Self { + pub fn new(local: &str, remote: &str, through: &str, udp: bool) -> Self { + // check local addr let local = local .to_socket_addrs() .expect("invalid local address") .next() .unwrap(); + + // check remote addr let remote = if let Ok(sockaddr) = remote.parse::() { RemoteAddr::SocketAddr(sockaddr) } else { @@ -53,6 +62,22 @@ impl Endpoint { let _ = dns::resolve_sync(&addr).unwrap(); RemoteAddr::DomainName(addr, port) }; - Endpoint { udp, local, remote } + + // check bind addr + let through = match through.to_socket_addrs() { + Ok(mut x) => Some(x.next().unwrap()), + Err(_) => through + .parse::() + .map_or(None, |ip| Some(SocketAddr::new(ip, 0))), + }; + + Endpoint { + udp, + local, + remote: Remote { + addr: remote, + through, + }, + } } } From b5191be7f9ad0e03f801cfdd0d047acf0ab12228 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 16:54:49 +0900 Subject: [PATCH 034/169] docs --- readme.md | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index b22776b0..1ae4d132 100644 --- a/readme.md +++ b/readme.md @@ -41,20 +41,27 @@ FLAGS: -V, --version Prints version information OPTIONS: - -c, --config use config file - -l, --listen listen address - -r, --remote remote address + -c, --config use config file + -l, --listen listen address + -r, --remote remote address + -x, --through send through ``` Start from command line arguments: ```shell +# enable udp realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --udp + +# specify outbound ip +realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --through 127.0.0.1 ``` Use a config file: ```shell realm -c config.json ``` + +Example: ```json { "dns_mode": "ipv4_only", @@ -67,6 +74,11 @@ realm -c config.json "local": "0.0.0.0:10000", "remote": "www.google.com:443", "udp": true + }, + { + "local": "0.0.0.0:15000", + "remote": "www.microsoft.com:443", + "through": "127.0.0.1" } ] } @@ -77,3 +89,9 @@ dns_mode: - ipv4_then_ipv6 *(default)* - ipv6_then_ipv4 - ipv4_and_ipv6 + +endpoint objects: +- local *(listen address)* +- remote *(remote address)* +- through *(send through specified ip or address, this is optional)* +- udp *(true|false, default=false)* \ No newline at end of file From 6049e4635b5c93482c45f654a327e0a120298029 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 16:57:11 +0900 Subject: [PATCH 035/169] bump version --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 157cbcf7..2e361a7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use cmd::CmdInput; use conf::GlobalConfig; use utils::Endpoint; -const VERSION: &str = "1.5.0"; +const VERSION: &str = "1.5.0-rc1"; fn main() { match cmd::scan() { From 6b76377223b787e29463658a7621a5d0f31156e2 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 17:15:22 +0900 Subject: [PATCH 036/169] compatibility fix --- src/main.rs | 2 +- src/relay/tcp.rs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 2e361a7a..75a957dc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ use cmd::CmdInput; use conf::GlobalConfig; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc1"; +const VERSION: &str = "1.5.0-rc2"; fn main() { match cmd::scan() { diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index ecc9317f..54fa6bf3 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -44,6 +44,13 @@ pub async fn proxy( #[cfg(unix)] socket.set_reuseport(true)?; socket.bind(x)?; + + #[cfg(feature = "tfo")] + { + TcpStream::connect_with_socket(socket, remote).await? + } + + #[cfg(not(feature = "tfo"))] socket.connect(remote).await? } None => TcpStream::connect(remote).await?, @@ -213,6 +220,7 @@ mod tfo { use tokio::io::{AsyncRead, AsyncWrite}; use tokio::io::ReadBuf; use tokio::io::Interest; + use tokio::net::TcpSocket; pub struct TcpListener(TfoListener); @@ -243,6 +251,14 @@ mod tfo { TfoStream::connect(addr).await.map(TcpStream) } + pub async fn connect_with_socket( + socket: TcpSocket, + addr: SocketAddr, + ) -> Result { + TfoStream::connect_with_socket(socket, addr) + .await + .map(TcpStream) + } #[allow(unused)] pub async fn readable(&self) -> Result<()> { self.0.inner().readable().await From eecd826b53c3ab3e1282c819639c1de67880af4e Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 21:10:24 +0900 Subject: [PATCH 037/169] polish up --- src/cmd/mod.rs | 2 +- src/conf/{dns_mode.rs => dns.rs} | 0 src/conf/mod.rs | 11 ++++++----- src/main.rs | 4 ++-- src/relay/mod.rs | 31 ++++++++++++++++++++++--------- src/utils/types.rs | 14 ++++---------- 6 files changed, 35 insertions(+), 27 deletions(-) rename src/conf/{dns_mode.rs => dns.rs} (100%) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 2d646b16..d45a489a 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -44,7 +44,7 @@ pub fn scan() -> CmdInput { Arg::with_name("through") .short("x") .long("through") - .help("send through") + .help("send through ip or address") .value_name("addr") .takes_value(true), ) diff --git a/src/conf/dns_mode.rs b/src/conf/dns.rs similarity index 100% rename from src/conf/dns_mode.rs rename to src/conf/dns.rs diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 74899a76..26dd228e 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -2,21 +2,22 @@ use std::fs; use serde::{Serialize, Deserialize}; -mod dns_mode; +mod dns; mod endpoint; -pub use dns_mode::DnsMode; +pub use dns::DnsMode; pub use endpoint::EndpointConfig; #[derive(Debug, Serialize, Deserialize)] -pub struct GlobalConfig { +pub struct FullConfig { #[serde(default)] pub dns_mode: DnsMode, pub endpoints: Vec, } -impl GlobalConfig { +impl FullConfig { pub fn from_config_file(file: &str) -> Self { - let config = fs::read_to_string(file).expect("invalid file path"); + let config = fs::read_to_string(file) + .expect(&format!("unable to open {}", file)); serde_json::from_str(&config).expect("failed to parse config file") } } diff --git a/src/main.rs b/src/main.rs index 75a957dc..fa03467c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,7 @@ mod utils; mod relay; use cmd::CmdInput; -use conf::GlobalConfig; +use conf::FullConfig; use utils::Endpoint; const VERSION: &str = "1.5.0-rc2"; @@ -23,7 +23,7 @@ fn start_from_cmd(c: Endpoint) { } fn start_from_config(c: String) { - let config = GlobalConfig::from_config_file(&c); + let config = FullConfig::from_config_file(&c); utils::init_resolver(config.dns_mode.into()); let eps: Vec = config .endpoints diff --git a/src/relay/mod.rs b/src/relay/mod.rs index f4342235..a653ee30 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -1,27 +1,34 @@ use std::io::Result; -use std::net::SocketAddr; use futures::future::join_all; mod tcp; use tcp::TcpListener; -use crate::utils::{Endpoint, Remote}; +use crate::utils::Endpoint; pub async fn run(eps: Vec) { let mut workers = vec![]; for ep in eps.into_iter() { #[cfg(feature = "udp")] if ep.udp { - workers.push(tokio::spawn(proxy_udp(ep.local, ep.remote.clone()))) + workers.push(tokio::spawn(proxy_udp(ep.clone()))) } - workers.push(tokio::spawn(proxy_tcp(ep.local, ep.remote))); + workers.push(tokio::spawn(proxy_tcp(ep))); } join_all(workers).await; } -async fn proxy_tcp(local: SocketAddr, remote: Remote) -> Result<()> { - let lis = TcpListener::bind(local).await.expect("unable to bind"); +async fn proxy_tcp(ep: Endpoint) -> Result<()> { + let Endpoint { + local, + remote, + through, + .. + } = ep; + let lis = TcpListener::bind(local) + .await + .expect(&format!("unable to bind {}", &local)); while let Ok((stream, _)) = lis.accept().await { - tokio::spawn(tcp::proxy(stream, remote.addr.clone(), remote.through)); + tokio::spawn(tcp::proxy(stream, remote.clone(), through)); } Ok(()) } @@ -30,6 +37,12 @@ async fn proxy_tcp(local: SocketAddr, remote: Remote) -> Result<()> { mod udp; #[cfg(feature = "udp")] -async fn proxy_udp(local: SocketAddr, remote: Remote) -> Result<()> { - udp::proxy(local, remote.addr.clone(), remote.through).await +async fn proxy_udp(ep: Endpoint) -> Result<()> { + let Endpoint { + local, + remote, + through, + .. + } = ep; + udp::proxy(local, remote.clone(), through).await } diff --git a/src/utils/types.rs b/src/utils/types.rs index fbc141d8..69f81143 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -9,15 +9,11 @@ pub enum RemoteAddr { } #[derive(Clone)] -pub struct Remote { - pub addr: RemoteAddr, - pub through: Option, -} - pub struct Endpoint { pub udp: bool, pub local: SocketAddr, - pub remote: Remote, + pub remote: RemoteAddr, + pub through: Option, } impl RemoteAddr { @@ -74,10 +70,8 @@ impl Endpoint { Endpoint { udp, local, - remote: Remote { - addr: remote, - through, - }, + remote, + through, } } } From b5fa5e8cb22a260f6f6c456dbc65d85302ccc9be Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 21:21:05 +0900 Subject: [PATCH 038/169] allow ip format like [::1] --- src/utils/types.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/utils/types.rs b/src/utils/types.rs index 69f81143..758cc3b1 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -62,9 +62,13 @@ impl Endpoint { // check bind addr let through = match through.to_socket_addrs() { Ok(mut x) => Some(x.next().unwrap()), - Err(_) => through - .parse::() - .map_or(None, |ip| Some(SocketAddr::new(ip, 0))), + Err(_) => { + let mut ipstr = String::from(through); + ipstr.retain(|c| c != '[' && c != ']'); + ipstr + .parse::() + .map_or(None, |ip| Some(SocketAddr::new(ip, 0))) + } }; Endpoint { From 8b788756988270478c1cf27b5e54cecc10af5a15 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 6 Nov 2021 21:22:23 +0900 Subject: [PATCH 039/169] clippy --- src/conf/mod.rs | 5 +++-- src/relay/mod.rs | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 26dd228e..f3c1e9f3 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -17,7 +17,8 @@ pub struct FullConfig { impl FullConfig { pub fn from_config_file(file: &str) -> Self { let config = fs::read_to_string(file) - .expect(&format!("unable to open {}", file)); - serde_json::from_str(&config).expect("failed to parse config file") + .unwrap_or_else(|_| panic!("unable to open {}", file)); + serde_json::from_str(&config) + .unwrap_or_else(|_| panic!("unable to open {}", file)) } } diff --git a/src/relay/mod.rs b/src/relay/mod.rs index a653ee30..3cc09ae9 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -26,7 +26,7 @@ async fn proxy_tcp(ep: Endpoint) -> Result<()> { } = ep; let lis = TcpListener::bind(local) .await - .expect(&format!("unable to bind {}", &local)); + .unwrap_or_else(|_| panic!("unable to bind {}", &local)); while let Ok((stream, _)) = lis.accept().await { tokio::spawn(tcp::proxy(stream, remote.clone(), through)); } From 0c4e4b7c251809fc4de4f09dc24ea45c4648941d Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 7 Nov 2021 20:20:52 +0900 Subject: [PATCH 040/169] extend dns options --- Cargo.toml | 7 ++-- src/conf/dns.rs | 79 +++++++++++++++++++++++++++++++++++++++----- src/conf/endpoint.rs | 4 +-- src/conf/mod.rs | 23 ++++++++----- src/dns/mod.rs | 12 +++++++ src/dns/sys_dns.rs | 8 +++++ src/dns/trust_dns.rs | 75 +++++++++++++++++++++++++++++++++++++++++ src/main.rs | 41 ++++++++++++++++------- src/utils/dns.rs | 41 ----------------------- src/utils/mod.rs | 2 -- src/utils/types.rs | 27 +++++++++++---- 11 files changed, 235 insertions(+), 84 deletions(-) create mode 100644 src/dns/mod.rs create mode 100644 src/dns/sys_dns.rs create mode 100644 src/dns/trust_dns.rs delete mode 100644 src/utils/dns.rs diff --git a/Cargo.toml b/Cargo.toml index 6e4615ec..20805185 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,7 +15,7 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] } -trust-dns-resolver = "0.20" +trust-dns-resolver = { version = "0.20", optional = true } lazy_static = "1" @@ -33,7 +33,8 @@ lto = true opt-level = 0 [features] -default = ["udp", "zero-copy"] +default = ["udp", "zero-copy", "trust-dns"] udp = [] tfo = ["tokio-tfo"] -zero-copy = ["libc"] \ No newline at end of file +zero-copy = ["libc"] +trust-dns = ["trust-dns-resolver"] \ No newline at end of file diff --git a/src/conf/dns.rs b/src/conf/dns.rs index bf0e9ced..475c86ef 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -1,18 +1,19 @@ use serde::{Serialize, Deserialize}; -use trust_dns_resolver::config::LookupIpStrategy; -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +use std::net::ToSocketAddrs; + +use trust_dns_resolver as resolver; +use resolver::config::{LookupIpStrategy, NameServerConfig, Protocol}; +use resolver::config::{ResolverConfig, ResolverOpts}; + +// dns mode +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum DnsMode { - /// Only query for A (Ipv4) records Ipv4Only, - /// Only query for AAAA (Ipv6) records Ipv6Only, - /// Query for A and AAAA in parallel Ipv4AndIpv6, - /// Query for Ipv4 if that fails, query for Ipv6 (default) Ipv4ThenIpv6, - /// Query for Ipv6 if that fails, query for Ipv4 Ipv6ThenIpv4, } @@ -22,14 +23,74 @@ impl Default for DnsMode { } } -impl From for LookupIpStrategy { +impl From for ResolverOpts { fn from(mode: DnsMode) -> Self { - match mode { + let ip_strategy = match mode { DnsMode::Ipv4Only => LookupIpStrategy::Ipv4Only, DnsMode::Ipv6Only => LookupIpStrategy::Ipv6Only, DnsMode::Ipv4AndIpv6 => LookupIpStrategy::Ipv4AndIpv6, DnsMode::Ipv4ThenIpv6 => LookupIpStrategy::Ipv4thenIpv6, DnsMode::Ipv6ThenIpv4 => LookupIpStrategy::Ipv6thenIpv4, + }; + ResolverOpts { + ip_strategy, + ..Default::default() + } + } +} + +// dns config +#[derive(Debug, Serialize, Deserialize)] +pub struct DnsConf { + #[serde(default)] + pub mode: DnsMode, + #[serde(default)] + pub protocol: String, + pub nameservers: Vec, +} + +fn read_protocol(net: &str) -> Vec { + match net.to_ascii_lowercase().as_str() { + "tcp" => vec![Protocol::Tcp], + "udp" => vec![Protocol::Udp], + _ => vec![Protocol::Tcp, Protocol::Udp], + } +} + +impl From for (ResolverConfig, ResolverOpts) { + fn from(config: DnsConf) -> Self { + if config.nameservers.is_empty() { + panic!("no nameserver provided"); + } + let opts = config.mode.into(); + let mut conf = ResolverConfig::new(); + let protocols = read_protocol(&config.protocol); + for addr in config.nameservers { + let socket_addr = addr.to_socket_addrs().unwrap().next().unwrap(); + for protocol in protocols.clone() { + conf.add_name_server(NameServerConfig { + socket_addr, + protocol, + tls_dns_name: None, + trust_nx_responses: true, + }); + } } + (conf, opts) + } +} + +// compatible dns config +#[derive(Debug, Serialize, Deserialize)] +#[serde(untagged, rename_all = "snake_case")] +pub enum CompatibeDnsConf { + Dns(DnsConf), + DnsMode(DnsMode), + None, +} + +impl Default for CompatibeDnsConf { + fn default() -> Self { + Self::None } } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index f628c153..4a600108 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -2,7 +2,7 @@ use serde::{Serialize, Deserialize}; use crate::utils::Endpoint; #[derive(Debug, Serialize, Deserialize)] -pub struct EndpointConfig { +pub struct EndpointConf { #[serde(default)] udp: bool, local: String, @@ -11,7 +11,7 @@ pub struct EndpointConfig { through: String, } -impl EndpointConfig { +impl EndpointConf { pub fn build(&self) -> Endpoint { Endpoint::new(&self.local, &self.remote, &self.through, self.udp) } diff --git a/src/conf/mod.rs b/src/conf/mod.rs index f3c1e9f3..52f4e640 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -2,23 +2,28 @@ use std::fs; use serde::{Serialize, Deserialize}; -mod dns; mod endpoint; -pub use dns::DnsMode; -pub use endpoint::EndpointConfig; +pub use endpoint::EndpointConf; + +#[cfg(feature = "trust-dns")] +mod dns; +#[cfg(feature = "trust-dns")] +pub use dns::CompatibeDnsConf; #[derive(Debug, Serialize, Deserialize)] -pub struct FullConfig { - #[serde(default)] - pub dns_mode: DnsMode, - pub endpoints: Vec, +pub struct FullConf { + #[cfg(feature = "trust-dns")] + #[serde(default, rename = "dns_mode")] + pub dns: CompatibeDnsConf, + + pub endpoints: Vec, } -impl FullConfig { +impl FullConf { pub fn from_config_file(file: &str) -> Self { let config = fs::read_to_string(file) .unwrap_or_else(|_| panic!("unable to open {}", file)); serde_json::from_str(&config) - .unwrap_or_else(|_| panic!("unable to open {}", file)) + .unwrap_or_else(|_| panic!("unable to parse {}", file)) } } diff --git a/src/dns/mod.rs b/src/dns/mod.rs new file mode 100644 index 00000000..0d622a52 --- /dev/null +++ b/src/dns/mod.rs @@ -0,0 +1,12 @@ +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "trust-dns")] { + mod trust_dns; + pub use trust_dns::*; + } else { + mod sys_dns; + pub use sys_dns::*; + pub use resolve as resolve_sync; + } +} diff --git a/src/dns/sys_dns.rs b/src/dns/sys_dns.rs new file mode 100644 index 00000000..09a8a4d1 --- /dev/null +++ b/src/dns/sys_dns.rs @@ -0,0 +1,8 @@ +use std::io::Result; +use std::net::{SocketAddr, ToSocketAddrs}; + +pub fn resolve(addr: &str, port: u16) -> Result { + (addr, port) + .to_socket_addrs() + .map(|mut x| x.next().unwrap()) +} diff --git a/src/dns/trust_dns.rs b/src/dns/trust_dns.rs new file mode 100644 index 00000000..f45a4e19 --- /dev/null +++ b/src/dns/trust_dns.rs @@ -0,0 +1,75 @@ +use std::io::{Result, Error, ErrorKind}; +use std::net::SocketAddr; +use std::sync::Mutex; + +use tokio::runtime::Runtime; + +use trust_dns_resolver as resolver; +use resolver::TokioAsyncResolver; +use resolver::config::{ResolverConfig, ResolverOpts}; +use resolver::system_conf::read_system_conf; + +use lazy_static::lazy_static; + +#[derive(Clone)] +pub struct DnsConf { + conf: ResolverConfig, + opts: ResolverOpts, +} + +impl Default for DnsConf { + fn default() -> Self { + #[cfg(any(unix, windows))] + let (conf, opts) = read_system_conf().unwrap(); + + #[cfg(not(any(unix, windows)))] + let (conf, opts) = Default::default(); + + Self { conf, opts } + } +} + +impl DnsConf { + pub fn set_conf(&mut self, conf: ResolverConfig) { + self.conf = conf; + } + + pub fn set_opts(&mut self, opts: ResolverOpts) { + self.opts = opts; + } +} + +lazy_static! { + static ref DNS_CONF: Mutex = Mutex::new(DnsConf::default()); + static ref DNS: TokioAsyncResolver = { + let DnsConf { conf, opts } = DNS_CONF.lock().unwrap().clone(); + TokioAsyncResolver::tokio(conf, opts).unwrap() + }; +} + +pub fn configure(conf: Option, opts: Option) { + lazy_static::initialize(&DNS_CONF); + + if let Some(conf) = conf { + DNS_CONF.lock().unwrap().set_conf(conf); + } + if let Some(opts) = opts { + DNS_CONF.lock().unwrap().set_opts(opts); + } +} + +pub fn build() { + lazy_static::initialize(&DNS); +} + +pub async fn resolve(addr: &str, port: u16) -> Result { + DNS.lookup_ip(addr).await.map_or_else( + |e| Err(Error::new(ErrorKind::Other, e)), + |ip| Ok(SocketAddr::new(ip.into_iter().next().unwrap(), port)), + ) +} + +pub fn resolve_sync(addr: &str, port: u16) -> Result { + let rt = Runtime::new().unwrap(); + rt.block_on(resolve(addr, port)) +} diff --git a/src/main.rs b/src/main.rs index fa03467c..08a3c380 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,38 +1,55 @@ mod cmd; +mod dns; mod conf; mod utils; mod relay; use cmd::CmdInput; -use conf::FullConfig; +use conf::FullConf; use utils::Endpoint; const VERSION: &str = "1.5.0-rc2"; fn main() { match cmd::scan() { - CmdInput::Config(c) => start_from_config(c), CmdInput::Endpoint(ep) => start_from_cmd(ep), + CmdInput::Config(conf) => start_from_conf(conf), CmdInput::Navigate => cmd::run_navigator(), CmdInput::None => {} } } -fn start_from_cmd(c: Endpoint) { - run_relay(vec![c]) +fn start_from_cmd(ep: Endpoint) { + run_relay(vec![ep]) } -fn start_from_config(c: String) { - let config = FullConfig::from_config_file(&c); - utils::init_resolver(config.dns_mode.into()); - let eps: Vec = config - .endpoints - .into_iter() - .map(|epc| epc.build()) - .collect(); +fn start_from_conf(conf: String) { + let conf = FullConf::from_config_file(&conf); + #[cfg(feature = "trust-dns")] + { + let FullConf { dns, .. } = conf; + setup_dns(dns); + } + + let eps: Vec = + conf.endpoints.into_iter().map(|epc| epc.build()).collect(); run_relay(eps); } +#[cfg(feature = "trust-dns")] +fn setup_dns(dns: conf::CompatibeDnsConf) { + use conf::CompatibeDnsConf::*; + match dns { + Dns(conf) => { + let (conf, opts) = conf.into(); + dns::configure(Some(conf), Some(opts)); + } + DnsMode(mode) => dns::configure(Option::None, Some(mode.into())), + None => (), + } + dns::build(); +} + fn run_relay(eps: Vec) { tokio::runtime::Builder::new_multi_thread() .enable_all() diff --git a/src/utils/dns.rs b/src/utils/dns.rs deleted file mode 100644 index a0ce82ce..00000000 --- a/src/utils/dns.rs +++ /dev/null @@ -1,41 +0,0 @@ -use std::io::{Result, Error, ErrorKind}; -use std::net::IpAddr; - -use tokio::runtime::Runtime; -use trust_dns_resolver::TokioAsyncResolver; -use trust_dns_resolver::config::{ResolverConfig, ResolverOpts, LookupIpStrategy}; -use lazy_static::lazy_static; - -static mut RESOLVE_STRATEGY: LookupIpStrategy = LookupIpStrategy::Ipv4thenIpv6; - -lazy_static! { - static ref DNS: TokioAsyncResolver = TokioAsyncResolver::tokio( - ResolverConfig::default(), - ResolverOpts { - ip_strategy: unsafe { RESOLVE_STRATEGY }, - ..Default::default() - } - ) - .unwrap(); -} - -pub fn init_resolver(strategy: LookupIpStrategy) { - unsafe { RESOLVE_STRATEGY = strategy }; - lazy_static::initialize(&DNS); -} - -pub fn resolve_sync(addr: &str) -> Result { - let rt = Runtime::new().unwrap(); - rt.block_on(resolve_async(addr)) -} - -pub async fn resolve_async(addr: &str) -> Result { - let res = DNS - .lookup_ip(addr) - .await - .map_err(|e| Error::new(ErrorKind::Other, e))? - .into_iter() - .next() - .unwrap(); - Ok(res) -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 96e703a2..2e759c89 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,5 @@ -mod dns; mod types; -pub use dns::*; pub use types::*; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; diff --git a/src/utils/types.rs b/src/utils/types.rs index 758cc3b1..4ad53c84 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -1,6 +1,7 @@ use std::io::Result; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; -use super::dns; + +use crate::dns; #[derive(Clone)] pub enum RemoteAddr { @@ -21,8 +22,15 @@ impl RemoteAddr { match self { Self::SocketAddr(sockaddr) => Ok(sockaddr), Self::DomainName(addr, port) => { - let ip = dns::resolve_async(&addr).await?; - Ok(SocketAddr::new(ip, port)) + #[cfg(feature = "trust-dns")] + { + dns::resolve(&addr, port).await + } + + #[cfg(not(feature = "trust-dns"))] + { + dns::resolve(&addr, port) + } } } } @@ -31,8 +39,15 @@ impl RemoteAddr { match self { Self::SocketAddr(sockaddr) => Ok(*sockaddr), Self::DomainName(addr, port) => { - let ip = dns::resolve_async(addr).await?; - Ok(SocketAddr::new(ip, *port)) + #[cfg(feature = "trust-dns")] + { + dns::resolve(addr, *port).await + } + + #[cfg(not(feature = "trust-dns"))] + { + dns::resolve(addr, *port) + } } } } @@ -55,7 +70,7 @@ impl Endpoint { let port = iter.next().unwrap().parse::().unwrap(); let addr = iter.next().unwrap().to_string(); // test addr - let _ = dns::resolve_sync(&addr).unwrap(); + let _ = dns::resolve_sync(&addr, 0).unwrap(); RemoteAddr::DomainName(addr, port) }; From fd1746e407bfd50720fa6f7284e3eb9aec739ab1 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 8 Nov 2021 18:52:25 +0900 Subject: [PATCH 041/169] compile with all features, add options for zero-copy and tfo --- Cargo.toml | 4 ++-- src/cmd/mod.rs | 14 ++++++++++++++ src/conf/endpoint.rs | 18 +++++++++++++++++- src/relay/mod.rs | 8 ++++---- src/relay/tcp.rs | 38 ++++++++++++++++++++++++++++++-------- src/relay/udp.rs | 7 ++++--- src/utils/types.rs | 24 +++++++++++++++++++++--- 7 files changed, 92 insertions(+), 21 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 20805185..a24684d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,8 +33,8 @@ lto = true opt-level = 0 [features] -default = ["udp", "zero-copy", "trust-dns"] +default = ["udp", "tfo", "zero-copy", "trust-dns"] udp = [] tfo = ["tokio-tfo"] zero-copy = ["libc"] -trust-dns = ["trust-dns-resolver"] \ No newline at end of file +trust-dns = ["trust-dns-resolver"] diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index d45a489a..ebe491ff 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -54,6 +54,18 @@ pub fn scan() -> CmdInput { .long("udp") .help("enable udp"), ) + .arg( + Arg::with_name("fast_open") + .short("f") + .long("tfo") + .help("enable tfo"), + ) + .arg( + Arg::with_name("zero_copy") + .short("z") + .long("zero-copy") + .help("enable tcp zero-copy"), + ) .subcommand( SubCommand::with_name("nav") .about("An Interactive configuration editor") @@ -74,6 +86,8 @@ pub fn scan() -> CmdInput { remote, matches.value_of("through").unwrap_or(""), matches.is_present("udp"), + matches.is_present("fast_open"), + matches.is_present("zero_copy"), )); } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index 4a600108..cb42105c 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -5,14 +5,30 @@ use crate::utils::Endpoint; pub struct EndpointConf { #[serde(default)] udp: bool, + + #[serde(default)] + fast_open: bool, + + #[serde(default)] + zero_copy: bool, + local: String, + remote: String, + #[serde(default)] through: String, } impl EndpointConf { pub fn build(&self) -> Endpoint { - Endpoint::new(&self.local, &self.remote, &self.through, self.udp) + Endpoint::new( + &self.local, + &self.remote, + &self.through, + self.udp, + self.fast_open, + self.zero_copy, + ) } } diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 3cc09ae9..298f53ea 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -21,14 +21,14 @@ async fn proxy_tcp(ep: Endpoint) -> Result<()> { let Endpoint { local, remote, - through, + conn_opts, .. } = ep; let lis = TcpListener::bind(local) .await .unwrap_or_else(|_| panic!("unable to bind {}", &local)); while let Ok((stream, _)) = lis.accept().await { - tokio::spawn(tcp::proxy(stream, remote.clone(), through)); + tokio::spawn(tcp::proxy(stream, remote.clone(), conn_opts)); } Ok(()) } @@ -41,8 +41,8 @@ async fn proxy_udp(ep: Endpoint) -> Result<()> { let Endpoint { local, remote, - through, + conn_opts, .. } = ep; - udp::proxy(local, remote.clone(), through).await + udp::proxy(local, remote.clone(), conn_opts).await } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 54fa6bf3..04d78fcf 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -14,10 +14,8 @@ cfg_if! { cfg_if! { if #[cfg(all(target_os = "linux", feature = "zero-copy"))] { - use zero_copy::copy; const BUFFER_SIZE: usize = 0x10000; } else { - use normal_copy::copy; const BUFFER_SIZE: usize = 0x4000; } } @@ -26,15 +24,22 @@ use std::io::Result; use std::net::SocketAddr; use futures::try_join; use tokio::net::TcpSocket; -use crate::utils::RemoteAddr; +use tokio::io::{AsyncRead, AsyncWrite}; + +use crate::utils::{RemoteAddr, ConnectOpts}; pub async fn proxy( mut inbound: TcpStream, remote: RemoteAddr, - through: Option, + conn_opts: ConnectOpts, ) -> Result<()> { + let ConnectOpts { + fast_open, + zero_copy, + send_through, + } = conn_opts; let remote = remote.into_sockaddr().await?; - let mut outbound = match through { + let mut outbound = match send_through { Some(x) => { let socket = match x { SocketAddr::V4(_) => TcpSocket::new_v4()?, @@ -60,15 +65,32 @@ pub async fn proxy( let (ri, wi) = inbound.split(); let (ro, wo) = outbound.split(); - let _ = try_join!(copy(ri, wo), copy(ro, wi)); + #[cfg(all(target_os = "linux", feature = "zero-copy"))] + if zero_copy { + use zero_copy::copy; + let _ = try_join!(copy(ri, wo), copy(ro, wi)); + } else { + use normal_copy::copy; + let _ = try_join!(copy(ri, wo), copy(ro, wi)); + } + + #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] + { + use normal_copy::copy; + let _ = try_join!(copy(ri, wo), copy(ro, wi)); + } Ok(()) } -#[cfg(not(all(target_os = "linux", feature = "zero-copy")))] mod normal_copy { use super::*; - pub async fn copy(mut r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> { + #[allow(unused)] + pub async fn copy(mut r: R, mut w: W) -> Result<()> + where + R: AsyncRead + Unpin, + W: AsyncWrite + Unpin, + { use tokio::io::{AsyncReadExt, AsyncWriteExt}; let mut buf = vec![0u8; BUFFER_SIZE]; let mut n: usize; diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 319c7be0..67c683dd 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -7,7 +7,7 @@ use std::collections::HashMap; use tokio::net::UdpSocket; use tokio::time::timeout; -use crate::utils::RemoteAddr; +use crate::utils::{RemoteAddr, ConnectOpts}; use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; // client <--> allocated socket @@ -18,8 +18,9 @@ const TIMEOUT: Duration = Duration::from_secs(20); pub async fn proxy( local: SocketAddr, remote: RemoteAddr, - through: Option, + conn_opts: ConnectOpts, ) -> Result<()> { + let ConnectOpts { send_through, .. } = conn_opts; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let local_sock = Arc::new(UdpSocket::bind(&local).await.unwrap()); let mut buf = vec![0u8; BUFFERSIZE]; @@ -37,7 +38,7 @@ pub async fn proxy( &sock_map, client_addr, &remote_addr, - &through, + &send_through, local_sock.clone(), ) .await diff --git a/src/utils/types.rs b/src/utils/types.rs index 4ad53c84..61021784 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -9,12 +9,19 @@ pub enum RemoteAddr { DomainName(String, u16), } +#[derive(Clone, Copy)] +pub struct ConnectOpts { + pub fast_open: bool, + pub zero_copy: bool, + pub send_through: Option, +} + #[derive(Clone)] pub struct Endpoint { pub udp: bool, pub local: SocketAddr, pub remote: RemoteAddr, - pub through: Option, + pub conn_opts: ConnectOpts, } impl RemoteAddr { @@ -54,7 +61,14 @@ impl RemoteAddr { } impl Endpoint { - pub fn new(local: &str, remote: &str, through: &str, udp: bool) -> Self { + pub fn new( + local: &str, + remote: &str, + through: &str, + udp: bool, + fast_open: bool, + zero_copy: bool, + ) -> Self { // check local addr let local = local .to_socket_addrs() @@ -90,7 +104,11 @@ impl Endpoint { udp, local, remote, - through, + conn_opts: ConnectOpts { + fast_open, + zero_copy, + send_through: through, + }, } } } From f0d94dad5d22f7ad0751deffb2d82b30d635718f Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 8 Nov 2021 19:07:04 +0900 Subject: [PATCH 042/169] rollback zero-copy implementation --- src/relay/tcp.rs | 61 +++++++++++++++++++++--------------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 04d78fcf..5bff56bb 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -3,7 +3,6 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(feature = "tfo")] { use tfo::TcpStream; - use tfo::{ReadHalf, WriteHalf}; pub use tfo::TcpListener; } else { use tokio::net::TcpStream; @@ -111,8 +110,9 @@ mod normal_copy { mod zero_copy { use super::*; use std::ops::Drop; + use std::os::unix::io::AsRawFd; use std::io::{Error, ErrorKind}; - use tokio::io::Interest; + use tokio::io::{AsyncReadExt, AsyncWriteExt}; struct Pipe(pub i32, pub i32); @@ -164,55 +164,51 @@ mod zero_copy { errno == EWOULDBLOCK || errno == EAGAIN } - #[inline] - fn clear_readiness(x: &TcpStream, interest: Interest) { - let _ = x.try_io(interest, || { - Err(Error::new(ErrorKind::WouldBlock, "")) as Result<()> - }); - } - - pub async fn copy(r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> { - use std::os::unix::io::AsRawFd; - use tokio::io::AsyncWriteExt; - // init pipe + pub async fn copy(mut r: R, mut w: W) -> Result<()> + where + X: AsRawFd, + Y: AsRawFd, + R: AsyncRead + AsRef + Unpin, + W: AsyncWrite + AsRef + Unpin, + { + // create pipe let pipe = Pipe::create()?; let (rpipe, wpipe) = (pipe.0, pipe.1); - // rw ref - let rx = r.as_ref(); - let wx = w.as_ref(); - // rw raw fd - let rfd = rx.as_raw_fd(); - let wfd = wx.as_raw_fd(); - // ctrl + // get raw fd + let rfd = r.as_ref().as_raw_fd(); + let wfd = w.as_ref().as_raw_fd(); let mut n: usize = 0; let mut done = false; 'LOOP: loop { // read until the socket buffer is empty // or the pipe is filled - rx.readable().await?; + // clear readiness (EPOLLIN) + r.read(&mut [0u8; 0]).await?; while n < BUFFER_SIZE { match splice_n(rfd, wpipe, BUFFER_SIZE - n) { x if x > 0 => n += x as usize, + // read EOF + // after this the read() syscall always returns 0 x if x == 0 => { done = true; break; } - x if x < 0 && is_wouldblock() => { - clear_readiness(rx, Interest::READABLE); - break; - } + // the recv socket buffer is drained out + x if x < 0 && is_wouldblock() => break, + // error occurs _ => break 'LOOP, } } // write until the pipe is empty while n > 0 { - wx.writable().await?; + // clear readiness (EPOLLOUT) + w.write(&[0u8; 0]).await?; match splice_n(rpipe, wfd, n) { x if x > 0 => n -= x as usize, - x if x < 0 && is_wouldblock() => { - clear_readiness(wx, Interest::WRITABLE) - } + // continue to write + x if x < 0 && is_wouldblock() => {} + // error occurs _ => break 'LOOP, } } @@ -222,12 +218,7 @@ mod zero_copy { } } - if done { - w.shutdown().await?; - Ok(()) - } else { - Err(Error::new(ErrorKind::ConnectionReset, "connection reset")) - } + Ok(()) } } From 3e2fab31bde7cffdda5c932b8faeda55c0cd0970 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 8 Nov 2021 22:04:22 +0900 Subject: [PATCH 043/169] handle tfo opt, no matter whether tfo feature is enabled or not --- Cargo.toml | 2 +- src/main.rs | 2 +- src/relay/tcp.rs | 19 +++++++++++++++++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a24684d6..dabab1f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,7 +33,7 @@ lto = true opt-level = 0 [features] -default = ["udp", "tfo", "zero-copy", "trust-dns"] +default = ["udp", "zero-copy", "trust-dns"] udp = [] tfo = ["tokio-tfo"] zero-copy = ["libc"] diff --git a/src/main.rs b/src/main.rs index 08a3c380..5f648098 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use cmd::CmdInput; use conf::FullConf; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc2"; +const VERSION: &str = "1.5.0-rc3"; fn main() { match cmd::scan() { diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 5bff56bb..af6ebaaa 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -6,7 +6,6 @@ cfg_if! { pub use tfo::TcpListener; } else { use tokio::net::TcpStream; - use tokio::net::tcp::{ReadHalf, WriteHalf}; pub use tokio::net::TcpListener; } } @@ -27,6 +26,7 @@ use tokio::io::{AsyncRead, AsyncWrite}; use crate::utils::{RemoteAddr, ConnectOpts}; +#[allow(unused_variables)] pub async fn proxy( mut inbound: TcpStream, remote: RemoteAddr, @@ -50,8 +50,10 @@ pub async fn proxy( socket.bind(x)?; #[cfg(feature = "tfo")] - { + if fast_open { TcpStream::connect_with_socket(socket, remote).await? + } else { + socket.connect(remote).await?.into() } #[cfg(not(feature = "tfo"))] @@ -272,6 +274,7 @@ mod tfo { .await .map(TcpStream) } + #[allow(unused)] pub async fn readable(&self) -> Result<()> { self.0.inner().readable().await @@ -300,6 +303,18 @@ mod tfo { } } + impl From for TcpStream { + fn from(x: TfoStream) -> Self { + TcpStream(x) + } + } + + impl From for TcpStream { + fn from(x: tokio::net::TcpStream) -> Self { + TcpStream(x.into()) + } + } + impl AsyncRead for TcpStream { fn poll_read( self: Pin<&mut Self>, From d96950f432943ec11f273d41800e5bbc9d6e3ef6 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 8 Nov 2021 22:45:27 +0900 Subject: [PATCH 044/169] docs --- readme.md | 77 ++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 54 insertions(+), 23 deletions(-) diff --git a/readme.md b/readme.md index 1ae4d132..2f7d082a 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,5 @@ -![realm](https://github.com/zhboner/realm/workflows/realm/badge.svg) +![realm](https://github.com/zephyrchien/realm/workflows/ci/badge.svg) +![realm](https://github.com/zephyrchien/realm/workflows/release/badge.svg) [中文说明](https://zhb.me/realm) @@ -14,17 +15,18 @@ realm is a simple, high performance relay server written in rust. - Low resources cost. ## Custom Build -Available Options: +available Options: - udp *(enabled)* -- tfo *(disabled)* +- trust-dns *(enabled)* - zero-copy *(enabled on linux)* +- tfo *(disabled)* ```shell # simple tcp cargo build --release --no-default-features # enable other options -cargo build --release --no-default-features --features udp, tfo, zero-copy +cargo build --release --no-default-features --features udp, tfo, zero-copy, trust-dns ``` ## Usage @@ -36,18 +38,20 @@ USAGE: realm [FLAGS] [OPTIONS] [SUBCOMMAND] FLAGS: - -h, --help Prints help information - -u, --udp enable udp - -V, --version Prints version information + -f, --tfo enable tfo + -u, --udp enable udp + -z, --zero-copy enable tcp zero-copy + -h, --help Prints help information + -V, --version Prints version information OPTIONS: - -c, --config use config file - -l, --listen listen address - -r, --remote remote address - -x, --through send through + -c, --config use config file + -l, --listen listen address + -r, --remote remote address + -x, --through send through specific ip or address ``` -Start from command line arguments: +start from command line arguments: ```shell # enable udp realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --udp @@ -56,15 +60,22 @@ realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --udp realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --through 127.0.0.1 ``` -Use a config file: +or use a config file: ```shell realm -c config.json ``` -Example: -```json -{ - "dns_mode": "ipv4_only", +## Configuration + +
+Example +
+{
+	"dns_mode": {
+		"mode": "ipv4_only",
+		"protocol": "tcp+udp",
+		"nameservers": ["8.8.8.8", "8.8.4.4"]
+	},
 	"endpoints": [
 		{
 			"local": "0.0.0.0:5000",
@@ -73,7 +84,9 @@ Example:
 		{
 			"local": "0.0.0.0:10000",
 			"remote": "www.google.com:443",
-			"udp": true
+			"udp": true,
+			"fast_open": true,
+			"zero_copy": true
 		},
 		{
 			"local": "0.0.0.0:15000",
@@ -81,17 +94,35 @@ Example:
 			"through": "127.0.0.1"
 		}
 	]
-}
-```
-dns_mode:
+}
+
+
+ +### dns +this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lookup priority with `"dns_mode": "ipv4_only"`, which is equal to `"dns_mode": {"mode": "ipv4_only"}` + +#### mode - ipv4_only - ipv6_only - ipv4_then_ipv6 *(default)* - ipv6_then_ipv4 - ipv4_and_ipv6 -endpoint objects: +#### protocol +- tcp +- udp +- tcp+udp *(default)* + +#### nameservers +format: ["server1", "server2" ...] + +default: +On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). Otherwise use google's public dns as default upstream resolver(`8.8.8.8`, `8.8.4.4` and `2001:4860:4860::8888`, `2001:4860:4860::8844`). + +### endpoint objects - local *(listen address)* - remote *(remote address)* - through *(send through specified ip or address, this is optional)* -- udp *(true|false, default=false)* \ No newline at end of file +- udp *(true|false, default=false)* +- zero_copy *(true|false, default=false)* +- fast_open *(true|false, default=false)* \ No newline at end of file From 526af175c58736878c831ed6ab315616d76f1a5a Mon Sep 17 00:00:00 2001 From: zephyr <51367850+zephyrchien@users.noreply.github.com> Date: Mon, 8 Nov 2021 23:40:59 +0900 Subject: [PATCH 045/169] Update readme.md --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 2f7d082a..8f6d5d22 100644 --- a/readme.md +++ b/readme.md @@ -41,8 +41,8 @@ FLAGS: -f, --tfo enable tfo -u, --udp enable udp -z, --zero-copy enable tcp zero-copy - -h, --help Prints help information - -V, --version Prints version information + -h, --help Prints help information + -V, --version Prints version information OPTIONS: -c, --config use config file @@ -125,4 +125,4 @@ On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.c - through *(send through specified ip or address, this is optional)* - udp *(true|false, default=false)* - zero_copy *(true|false, default=false)* -- fast_open *(true|false, default=false)* \ No newline at end of file +- fast_open *(true|false, default=false)* From 19e98cc20eca86b2ab5ea4b325c096b3caed7662 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 9 Nov 2021 18:33:24 +0900 Subject: [PATCH 046/169] Revert "rollback zero-copy implementation" This reverts commit d089e3dfa6a69a99f67884e744609383f06a312e. --- src/relay/tcp.rs | 61 +++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 26 deletions(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index af6ebaaa..ae25acfc 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -3,6 +3,7 @@ use cfg_if::cfg_if; cfg_if! { if #[cfg(feature = "tfo")] { use tfo::TcpStream; + use tfo::{ReadHalf, WriteHalf}; pub use tfo::TcpListener; } else { use tokio::net::TcpStream; @@ -112,9 +113,8 @@ mod normal_copy { mod zero_copy { use super::*; use std::ops::Drop; - use std::os::unix::io::AsRawFd; use std::io::{Error, ErrorKind}; - use tokio::io::{AsyncReadExt, AsyncWriteExt}; + use tokio::io::Interest; struct Pipe(pub i32, pub i32); @@ -166,51 +166,55 @@ mod zero_copy { errno == EWOULDBLOCK || errno == EAGAIN } - pub async fn copy(mut r: R, mut w: W) -> Result<()> - where - X: AsRawFd, - Y: AsRawFd, - R: AsyncRead + AsRef + Unpin, - W: AsyncWrite + AsRef + Unpin, - { - // create pipe + #[inline] + fn clear_readiness(x: &TcpStream, interest: Interest) { + let _ = x.try_io(interest, || { + Err(Error::new(ErrorKind::WouldBlock, "")) as Result<()> + }); + } + + pub async fn copy(r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> { + use std::os::unix::io::AsRawFd; + use tokio::io::AsyncWriteExt; + // init pipe let pipe = Pipe::create()?; let (rpipe, wpipe) = (pipe.0, pipe.1); - // get raw fd - let rfd = r.as_ref().as_raw_fd(); - let wfd = w.as_ref().as_raw_fd(); + // rw ref + let rx = r.as_ref(); + let wx = w.as_ref(); + // rw raw fd + let rfd = rx.as_raw_fd(); + let wfd = wx.as_raw_fd(); + // ctrl let mut n: usize = 0; let mut done = false; 'LOOP: loop { // read until the socket buffer is empty // or the pipe is filled - // clear readiness (EPOLLIN) - r.read(&mut [0u8; 0]).await?; + rx.readable().await?; while n < BUFFER_SIZE { match splice_n(rfd, wpipe, BUFFER_SIZE - n) { x if x > 0 => n += x as usize, - // read EOF - // after this the read() syscall always returns 0 x if x == 0 => { done = true; break; } - // the recv socket buffer is drained out - x if x < 0 && is_wouldblock() => break, - // error occurs + x if x < 0 && is_wouldblock() => { + clear_readiness(rx, Interest::READABLE); + break; + } _ => break 'LOOP, } } // write until the pipe is empty while n > 0 { - // clear readiness (EPOLLOUT) - w.write(&[0u8; 0]).await?; + wx.writable().await?; match splice_n(rpipe, wfd, n) { x if x > 0 => n -= x as usize, - // continue to write - x if x < 0 && is_wouldblock() => {} - // error occurs + x if x < 0 && is_wouldblock() => { + clear_readiness(wx, Interest::WRITABLE) + } _ => break 'LOOP, } } @@ -220,7 +224,12 @@ mod zero_copy { } } - Ok(()) + if done { + w.shutdown().await?; + Ok(()) + } else { + Err(Error::new(ErrorKind::ConnectionReset, "connection reset")) + } } } From 5a51dd7e9636f1d376e8aa1fe46e145758ae94f8 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 9 Nov 2021 19:39:03 +0900 Subject: [PATCH 047/169] no need to use generics --- src/relay/tcp.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index ae25acfc..1c908384 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -7,6 +7,7 @@ cfg_if! { pub use tfo::TcpListener; } else { use tokio::net::TcpStream; + use tokio::net::tcp::{ReadHalf,WriteHalf}; pub use tokio::net::TcpListener; } } @@ -23,7 +24,6 @@ use std::io::Result; use std::net::SocketAddr; use futures::try_join; use tokio::net::TcpSocket; -use tokio::io::{AsyncRead, AsyncWrite}; use crate::utils::{RemoteAddr, ConnectOpts}; @@ -87,12 +87,10 @@ pub async fn proxy( mod normal_copy { use super::*; + #[allow(unused)] - pub async fn copy(mut r: R, mut w: W) -> Result<()> - where - R: AsyncRead + Unpin, - W: AsyncWrite + Unpin, - { + pub async fn copy(mut r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> +where { use tokio::io::{AsyncReadExt, AsyncWriteExt}; let mut buf = vec![0u8; BUFFER_SIZE]; let mut n: usize; From ce7f9c52d0cb586a1d89e9a3ff71d0b50ddac646 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 9 Nov 2021 22:04:45 +0900 Subject: [PATCH 048/169] ensure all data is written to socket buffer, where connection is slow. related: #1 --- src/relay/tcp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 1c908384..5d9209a7 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -99,7 +99,7 @@ where { if n == 0 { break; } - w.write(&buf[..n]).await?; + w.write_all(&buf[..n]).await?; w.flush().await?; } w.shutdown().await?; From 2a5525742093e93e53ebbb5e47f148a792e5c408 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 9 Nov 2021 22:30:57 +0900 Subject: [PATCH 049/169] daemonize --- Cargo.lock | 17 +++++++++++++++++ Cargo.toml | 3 +++ src/cmd/mod.rs | 11 +++++++++++ src/utils/mod.rs | 16 ++++++++++++++++ 4 files changed, 47 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 362aceb0..14bcf816 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -45,6 +45,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +[[package]] +name = "boxfnonce" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" + [[package]] name = "bytes" version = "1.1.0" @@ -72,6 +78,16 @@ dependencies = [ "vec_map", ] +[[package]] +name = "daemonize" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70c24513e34f53b640819f0ac9f705b673fcf4006d7aab8778bee72ebfc89815" +dependencies = [ + "boxfnonce", + "libc", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -539,6 +555,7 @@ version = "1.5.0" dependencies = [ "cfg-if", "clap", + "daemonize", "futures", "lazy_static", "libc", diff --git a/Cargo.toml b/Cargo.toml index dabab1f8..fa18ae12 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,9 @@ tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", # zero-copy libc = { version = "0.2", optional = true } +[target.'cfg(unix)'.dependencies] +daemonize = "0.4" + [profile.release] opt-level = 3 lto = true diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index ebe491ff..dbf00e0a 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -66,6 +66,12 @@ pub fn scan() -> CmdInput { .long("zero-copy") .help("enable tcp zero-copy"), ) + .arg( + Arg::with_name("daemon") + .short("d") + .long("daemon") + .help("daemonize"), + ) .subcommand( SubCommand::with_name("nav") .about("An Interactive configuration editor") @@ -74,6 +80,11 @@ pub fn scan() -> CmdInput { ) .get_matches(); + #[cfg(unix)] + if matches.is_present("daemon") { + crate::utils::daemonize(); + } + if let Some(config) = matches.value_of("config") { return CmdInput::Config(config.to_string()); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2e759c89..b66a16f6 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -15,3 +15,19 @@ pub fn new_sockaddr_v4() -> SocketAddr { pub fn new_sockaddr_v6() -> SocketAddr { SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0) } + +#[cfg(unix)] +pub fn daemonize() { + use std::env::current_dir; + use daemonize::Daemonize; + + let pwd = current_dir().unwrap().canonicalize().unwrap(); + let daemon = Daemonize::new() + .umask(0) + .working_directory(pwd) + .exit_action(|| println!("realm is running in the background")); + + daemon + .start() + .unwrap_or_else(|e| eprintln!("failed to daemonize, {}", e)); +} From 9866f486c8d67561d96060aba8f92c66f31c753a Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 9 Nov 2021 22:33:38 +0900 Subject: [PATCH 050/169] docs & bump version --- readme.md | 1 + src/main.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 8f6d5d22..27531468 100644 --- a/readme.md +++ b/readme.md @@ -38,6 +38,7 @@ USAGE: realm [FLAGS] [OPTIONS] [SUBCOMMAND] FLAGS: + -d, --daemon daemonize -f, --tfo enable tfo -u, --udp enable udp -z, --zero-copy enable tcp zero-copy diff --git a/src/main.rs b/src/main.rs index 5f648098..ea94f44c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use cmd::CmdInput; use conf::FullConf; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc3"; +const VERSION: &str = "1.5.0-rc4"; fn main() { match cmd::scan() { From 1d0bd3024fd92483732a99448918da1c05554381 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 12 Nov 2021 15:39:19 +0900 Subject: [PATCH 051/169] add timeout option --- src/cmd/mod.rs | 12 ++++++++++++ src/conf/endpoint.rs | 8 ++++++++ src/relay/tcp.rs | 30 ++++++++++++++++++++++++------ src/relay/udp.rs | 16 ++++++++++++---- src/utils/types.rs | 5 +++++ 5 files changed, 61 insertions(+), 10 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index dbf00e0a..3199c87d 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,6 +1,7 @@ use clap::{Arg, App, SubCommand}; use super::Endpoint; +use crate::utils::TIMEOUT; mod nav; pub use nav::run_navigator; @@ -40,6 +41,14 @@ pub fn scan() -> CmdInput { .value_name("addr") .takes_value(true), ) + .arg( + Arg::with_name("timeout") + .short("t") + .long("timeout") + .help("set timeout value") + .value_name("second") + .takes_value(true), + ) .arg( Arg::with_name("through") .short("x") @@ -99,6 +108,9 @@ pub fn scan() -> CmdInput { matches.is_present("udp"), matches.is_present("fast_open"), matches.is_present("zero_copy"), + matches + .value_of("timeout") + .map_or(TIMEOUT, |t| t.parse::().unwrap_or(TIMEOUT)), )); } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index cb42105c..56bc668f 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -12,6 +12,9 @@ pub struct EndpointConf { #[serde(default)] zero_copy: bool, + #[serde(default = "df_timeout")] + timeout: usize, + local: String, remote: String, @@ -20,6 +23,10 @@ pub struct EndpointConf { through: String, } +const fn df_timeout() -> usize { + crate::utils::TIMEOUT +} + impl EndpointConf { pub fn build(&self) -> Endpoint { Endpoint::new( @@ -29,6 +36,7 @@ impl EndpointConf { self.udp, self.fast_open, self.zero_copy, + self.timeout, ) } } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 5d9209a7..dd6d8113 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -22,8 +22,11 @@ cfg_if! { use std::io::Result; use std::net::SocketAddr; +use std::time::Duration; use futures::try_join; + use tokio::net::TcpSocket; +use tokio::time::timeout as timeoutfut; use crate::utils::{RemoteAddr, ConnectOpts}; @@ -34,11 +37,15 @@ pub async fn proxy( conn_opts: ConnectOpts, ) -> Result<()> { let ConnectOpts { + timeout, fast_open, zero_copy, send_through, } = conn_opts; + let remote = remote.into_sockaddr().await?; + let timeout = Duration::from_secs(timeout as u64); + let mut outbound = match send_through { Some(x) => { let socket = match x { @@ -62,18 +69,20 @@ pub async fn proxy( } None => TcpStream::connect(remote).await?, }; + inbound.set_nodelay(true)?; outbound.set_nodelay(true)?; + let (ri, wi) = inbound.split(); let (ro, wo) = outbound.split(); #[cfg(all(target_os = "linux", feature = "zero-copy"))] if zero_copy { use zero_copy::copy; - let _ = try_join!(copy(ri, wo), copy(ro, wi)); + let _ = try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)); } else { use normal_copy::copy; - let _ = try_join!(copy(ri, wo), copy(ro, wi)); + let _ = try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)); } #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] @@ -89,13 +98,18 @@ mod normal_copy { use super::*; #[allow(unused)] - pub async fn copy(mut r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> + pub async fn copy( + mut r: ReadHalf<'_>, + mut w: WriteHalf<'_>, + timeout: Duration, + ) -> Result<()> where { use tokio::io::{AsyncReadExt, AsyncWriteExt}; + let mut buf = vec![0u8; BUFFER_SIZE]; let mut n: usize; loop { - n = r.read(&mut buf).await?; + n = timeoutfut(timeout, r.read(&mut buf)).await??; if n == 0 { break; } @@ -171,7 +185,11 @@ mod zero_copy { }); } - pub async fn copy(r: ReadHalf<'_>, mut w: WriteHalf<'_>) -> Result<()> { + pub async fn copy( + r: ReadHalf<'_>, + mut w: WriteHalf<'_>, + timeout: Duration, + ) -> Result<()> { use std::os::unix::io::AsRawFd; use tokio::io::AsyncWriteExt; // init pipe @@ -190,7 +208,7 @@ mod zero_copy { 'LOOP: loop { // read until the socket buffer is empty // or the pipe is filled - rx.readable().await?; + timeoutfut(timeout, rx.readable()).await??; while n < BUFFER_SIZE { match splice_n(rfd, wpipe, BUFFER_SIZE - n) { x if x > 0 => n += x as usize, diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 67c683dd..8bce4258 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -5,7 +5,7 @@ use std::sync::{Arc, RwLock}; use std::collections::HashMap; use tokio::net::UdpSocket; -use tokio::time::timeout; +use tokio::time::timeout as timeoutfut; use crate::utils::{RemoteAddr, ConnectOpts}; use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; @@ -13,16 +13,20 @@ use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; // client <--> allocated socket type SockMap = Arc>>>; const BUFFERSIZE: usize = 0x1000; -const TIMEOUT: Duration = Duration::from_secs(20); pub async fn proxy( local: SocketAddr, remote: RemoteAddr, conn_opts: ConnectOpts, ) -> Result<()> { - let ConnectOpts { send_through, .. } = conn_opts; + let ConnectOpts { + send_through, + timeout, + .. + } = conn_opts; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let local_sock = Arc::new(UdpSocket::bind(&local).await.unwrap()); + let timeout = Duration::from_secs(timeout as u64); let mut buf = vec![0u8; BUFFERSIZE]; loop { @@ -40,6 +44,7 @@ pub async fn proxy( &remote_addr, &send_through, local_sock.clone(), + timeout, ) .await } @@ -54,11 +59,12 @@ async fn send_back( client_addr: SocketAddr, local_sock: Arc, alloc_sock: Arc, + timeout: Duration, ) { let mut buf = vec![0u8; BUFFERSIZE]; while let Ok(Ok((n, _))) = - timeout(TIMEOUT, alloc_sock.recv_from(&mut buf)).await + timeoutfut(timeout, alloc_sock.recv_from(&mut buf)).await { if local_sock.send_to(&buf[..n], &client_addr).await.is_err() { break; @@ -84,6 +90,7 @@ async fn alloc_new_socket( remote_addr: &SocketAddr, send_through: &Option, local_sock: Arc, + timeout: Duration, ) -> Arc { // pick a random port let alloc_sock = Arc::new(match send_through { @@ -103,6 +110,7 @@ async fn alloc_new_socket( client_addr, local_sock, alloc_sock.clone(), + timeout, )); sock_map diff --git a/src/utils/types.rs b/src/utils/types.rs index 61021784..6c7cdf5b 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -3,6 +3,8 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use crate::dns; +pub const TIMEOUT: usize = 15; + #[derive(Clone)] pub enum RemoteAddr { SocketAddr(SocketAddr), @@ -13,6 +15,7 @@ pub enum RemoteAddr { pub struct ConnectOpts { pub fast_open: bool, pub zero_copy: bool, + pub timeout: usize, pub send_through: Option, } @@ -68,6 +71,7 @@ impl Endpoint { udp: bool, fast_open: bool, zero_copy: bool, + timeout: usize, ) -> Self { // check local addr let local = local @@ -107,6 +111,7 @@ impl Endpoint { conn_opts: ConnectOpts { fast_open, zero_copy, + timeout, send_through: through, }, } From b7460f0c367553d32d7f6b0a2964e40decb3db9e Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 12 Nov 2021 15:47:35 +0900 Subject: [PATCH 052/169] read errno via safe method --- src/relay/tcp.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index dd6d8113..9c8e8709 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -173,9 +173,12 @@ mod zero_copy { #[inline] fn is_wouldblock() -> bool { - use libc::{EAGAIN, EWOULDBLOCK}; - let errno = unsafe { *libc::__errno_location() }; - errno == EWOULDBLOCK || errno == EAGAIN + use libc::{EWOULDBLOCK, EAGAIN}; + let err = Error::last_os_error().raw_os_error(); + match err { + Some(e) => e == EWOULDBLOCK || e == EAGAIN, + None => false, + } } #[inline] From 6f4496dd5a191f8c8054a503be8b2efccda4b0ba Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 12 Nov 2021 15:51:00 +0900 Subject: [PATCH 053/169] docs --- readme.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 27531468..dcb0039b 100644 --- a/readme.md +++ b/readme.md @@ -50,6 +50,7 @@ OPTIONS: -l, --listen listen address -r, --remote remote address -x, --through send through specific ip or address + -t, --timeout set timeout value ``` start from command line arguments: @@ -87,12 +88,14 @@ realm -c config.json "remote": "www.google.com:443", "udp": true, "fast_open": true, - "zero_copy": true + "zero_copy": true, + "timeout": 60 }, { "local": "0.0.0.0:15000", "remote": "www.microsoft.com:443", - "through": "127.0.0.1" + "through": "127.0.0.1", + "timeout": 120 } ] } @@ -127,3 +130,4 @@ On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.c - udp *(true|false, default=false)* - zero_copy *(true|false, default=false)* - fast_open *(true|false, default=false)* +- timeout *(tcp/udp timeout(sec), default=15)* From 73728c5b0a52e05a013f3c1bfb01874b13608644 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 12 Nov 2021 15:58:14 +0900 Subject: [PATCH 054/169] solve conflicts on other os --- src/relay/tcp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 9c8e8709..b7fa7e3d 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -88,7 +88,7 @@ pub async fn proxy( #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] { use normal_copy::copy; - let _ = try_join!(copy(ri, wo), copy(ro, wi)); + let _ = try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)); } Ok(()) From adc45c0fd859b4cd180ef7fcdd4f9cbc7f8d2ed1 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 13 Nov 2021 23:17:42 +0900 Subject: [PATCH 055/169] reset default timeout;tcp=300s, udp=30s --- src/cmd/mod.rs | 28 ++++++++++++++++++++-------- src/conf/endpoint.rs | 18 +++++++++++++----- src/main.rs | 2 +- src/relay/tcp.rs | 3 ++- src/relay/udp.rs | 2 +- src/utils/types.rs | 13 +++++++++---- 6 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 3199c87d..b97094ab 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,7 +1,8 @@ use clap::{Arg, App, SubCommand}; use super::Endpoint; -use crate::utils::TIMEOUT; +use crate::utils::TCP_TIMEOUT; +use crate::utils::UDP_TIMEOUT; mod nav; pub use nav::run_navigator; @@ -42,10 +43,16 @@ pub fn scan() -> CmdInput { .takes_value(true), ) .arg( - Arg::with_name("timeout") - .short("t") - .long("timeout") - .help("set timeout value") + Arg::with_name("tcp_timeout") + .long("tcp-timeout") + .help("set timeout value for tcp") + .value_name("second") + .takes_value(true), + ) + .arg( + Arg::with_name("udp_timeout") + .long("udp-timeout") + .help("set timeout value for udp") .value_name("second") .takes_value(true), ) @@ -101,6 +108,12 @@ pub fn scan() -> CmdInput { if let (Some(local), Some(remote)) = (matches.value_of("local"), matches.value_of("remote")) { + let tcp_timeout = matches + .value_of("tcp_timeout") + .map_or(TCP_TIMEOUT, |t| t.parse::().unwrap_or(TCP_TIMEOUT)); + let udp_timeout = matches + .value_of("udp_timeout") + .map_or(UDP_TIMEOUT, |t| t.parse::().unwrap_or(UDP_TIMEOUT)); return CmdInput::Endpoint(Endpoint::new( local, remote, @@ -108,9 +121,8 @@ pub fn scan() -> CmdInput { matches.is_present("udp"), matches.is_present("fast_open"), matches.is_present("zero_copy"), - matches - .value_of("timeout") - .map_or(TIMEOUT, |t| t.parse::().unwrap_or(TIMEOUT)), + tcp_timeout, + udp_timeout, )); } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index 56bc668f..17c2292e 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -12,8 +12,11 @@ pub struct EndpointConf { #[serde(default)] zero_copy: bool, - #[serde(default = "df_timeout")] - timeout: usize, + #[serde(default = "tcp_timeout")] + tcp_timeout: usize, + + #[serde(default = "udp_timeout")] + udp_timeout: usize, local: String, @@ -23,8 +26,12 @@ pub struct EndpointConf { through: String, } -const fn df_timeout() -> usize { - crate::utils::TIMEOUT +const fn tcp_timeout() -> usize { + crate::utils::TCP_TIMEOUT +} + +const fn udp_timeout() -> usize { + crate::utils::UDP_TIMEOUT } impl EndpointConf { @@ -36,7 +43,8 @@ impl EndpointConf { self.udp, self.fast_open, self.zero_copy, - self.timeout, + self.tcp_timeout, + self.udp_timeout, ) } } diff --git a/src/main.rs b/src/main.rs index ea94f44c..a7b00cbd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,7 +8,7 @@ use cmd::CmdInput; use conf::FullConf; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc4"; +const VERSION: &str = "1.5.0-rc5"; fn main() { match cmd::scan() { diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index b7fa7e3d..112b9932 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -37,10 +37,11 @@ pub async fn proxy( conn_opts: ConnectOpts, ) -> Result<()> { let ConnectOpts { - timeout, + tcp_timeout: timeout, fast_open, zero_copy, send_through, + .. } = conn_opts; let remote = remote.into_sockaddr().await?; diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 8bce4258..59e14ffc 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -21,7 +21,7 @@ pub async fn proxy( ) -> Result<()> { let ConnectOpts { send_through, - timeout, + udp_timeout: timeout, .. } = conn_opts; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); diff --git a/src/utils/types.rs b/src/utils/types.rs index 6c7cdf5b..33cdcd22 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -3,7 +3,8 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use crate::dns; -pub const TIMEOUT: usize = 15; +pub const TCP_TIMEOUT: usize = 300; +pub const UDP_TIMEOUT: usize = 30; #[derive(Clone)] pub enum RemoteAddr { @@ -15,7 +16,8 @@ pub enum RemoteAddr { pub struct ConnectOpts { pub fast_open: bool, pub zero_copy: bool, - pub timeout: usize, + pub tcp_timeout: usize, + pub udp_timeout: usize, pub send_through: Option, } @@ -64,6 +66,7 @@ impl RemoteAddr { } impl Endpoint { + #[allow(clippy::too_many_arguments)] pub fn new( local: &str, remote: &str, @@ -71,7 +74,8 @@ impl Endpoint { udp: bool, fast_open: bool, zero_copy: bool, - timeout: usize, + tcp_timeout: usize, + udp_timeout: usize, ) -> Self { // check local addr let local = local @@ -111,7 +115,8 @@ impl Endpoint { conn_opts: ConnectOpts { fast_open, zero_copy, - timeout, + tcp_timeout, + udp_timeout, send_through: through, }, } From 7f5d9b66b7c41acb1d171bda8d113de5246ac74d Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 13 Nov 2021 23:19:26 +0900 Subject: [PATCH 056/169] update docs --- readme.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/readme.md b/readme.md index dcb0039b..22cb9267 100644 --- a/readme.md +++ b/readme.md @@ -50,7 +50,8 @@ OPTIONS: -l, --listen listen address -r, --remote remote address -x, --through send through specific ip or address - -t, --timeout set timeout value + --tcp-timeout set timeout value + --udp-timeout set timeout value ``` start from command line arguments: @@ -88,14 +89,12 @@ realm -c config.json "remote": "www.google.com:443", "udp": true, "fast_open": true, - "zero_copy": true, - "timeout": 60 + "zero_copy": true }, { "local": "0.0.0.0:15000", "remote": "www.microsoft.com:443", - "through": "127.0.0.1", - "timeout": 120 + "through": "127.0.0.1" } ] } @@ -130,4 +129,5 @@ On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.c - udp *(true|false, default=false)* - zero_copy *(true|false, default=false)* - fast_open *(true|false, default=false)* -- timeout *(tcp/udp timeout(sec), default=15)* +- tcp_timeout *(tcp timeout(sec), default=300)* +- udp_timeout *(udp timeout(sec), default=30)* From e202c2d9b4ea05894ea3d4cda8f052fe4801f17c Mon Sep 17 00:00:00 2001 From: zephyr <51367850+zephyrchien@users.noreply.github.com> Date: Mon, 22 Nov 2021 13:26:22 +0900 Subject: [PATCH 057/169] docs, custom dns server requires addr instead of ip --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 22cb9267..5dfbec9d 100644 --- a/readme.md +++ b/readme.md @@ -77,7 +77,7 @@ realm -c config.json "dns_mode": { "mode": "ipv4_only", "protocol": "tcp+udp", - "nameservers": ["8.8.8.8", "8.8.4.4"] + "nameservers": ["8.8.8.8:53", "8.8.4.4:53"] }, "endpoints": [ { From 1e45e4d494b4405e5441e71740b0f8d06b8af483 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 23 Nov 2021 22:42:46 +0900 Subject: [PATCH 058/169] avoid realloc --- src/relay/mod.rs | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 298f53ea..8f86d9ac 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -6,10 +6,10 @@ use tcp::TcpListener; use crate::utils::Endpoint; pub async fn run(eps: Vec) { - let mut workers = vec![]; + let mut workers = Vec::with_capacity(compute_workers(&eps)); for ep in eps.into_iter() { #[cfg(feature = "udp")] - if ep.udp { + if ep.opts.use_udp { workers.push(tokio::spawn(proxy_udp(ep.clone()))) } workers.push(tokio::spawn(proxy_tcp(ep))); @@ -21,14 +21,14 @@ async fn proxy_tcp(ep: Endpoint) -> Result<()> { let Endpoint { local, remote, - conn_opts, + opts, .. } = ep; let lis = TcpListener::bind(local) .await .unwrap_or_else(|_| panic!("unable to bind {}", &local)); while let Ok((stream, _)) = lis.accept().await { - tokio::spawn(tcp::proxy(stream, remote.clone(), conn_opts)); + tokio::spawn(tcp::proxy(stream, remote.clone(), opts)); } Ok(()) } @@ -41,8 +41,23 @@ async fn proxy_udp(ep: Endpoint) -> Result<()> { let Endpoint { local, remote, - conn_opts, + opts, .. } = ep; - udp::proxy(local, remote.clone(), conn_opts).await + udp::proxy(local, remote.clone(), opts).await +} + +fn compute_workers(workers: &[Endpoint]) -> usize { + macro_rules! num { + ($x: expr) => { + if !$x { + 1 + } else { + 2 + } + }; + } + workers + .iter() + .fold(0, |total, x| total + num!(x.opts.use_udp)) } From 516c1830f4b5338c182c5a3a4bb5c3629dfc944f Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 23 Nov 2021 22:56:50 +0900 Subject: [PATCH 059/169] remove duplicated clone --- src/relay/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 8f86d9ac..ab7988ce 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -44,7 +44,7 @@ async fn proxy_udp(ep: Endpoint) -> Result<()> { opts, .. } = ep; - udp::proxy(local, remote.clone(), opts).await + udp::proxy(local, remote, opts).await } fn compute_workers(workers: &[Endpoint]) -> usize { From 8ae8544e6633e15e3e3bed1b7f16092eb3bee401 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 00:07:22 +0900 Subject: [PATCH 060/169] better error handle --- Cargo.lock | 1 + Cargo.toml | 1 + src/main.rs | 12 ++++++++-- src/relay/mod.rs | 23 +++++++++++++----- src/relay/tcp.rs | 47 +++++++++++++++++++++--------------- src/relay/udp.rs | 28 ++++++++++++++++++---- src/utils/types.rs | 60 ++++++++++++++++++++++++++++++++++++++++++---- 7 files changed, 135 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 14bcf816..bbf78acb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -559,6 +559,7 @@ dependencies = [ "futures", "lazy_static", "libc", + "log", "serde", "serde_json", "tokio", diff --git a/Cargo.toml b/Cargo.toml index fa18ae12..6e9abaaf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,6 +9,7 @@ edition = "2018" [dependencies] cfg-if = "1" futures = "0.3" +log = "0.4" clap = "2" serde = { version = "1", features = ["derive"] } diff --git a/src/main.rs b/src/main.rs index a7b00cbd..3f3dd6e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,8 +31,16 @@ fn start_from_conf(conf: String) { setup_dns(dns); } - let eps: Vec = - conf.endpoints.into_iter().map(|epc| epc.build()).collect(); + let eps: Vec = conf + .endpoints + .into_iter() + .map(|epc| { + let ep = epc.build(); + println!("inited: {}", &ep); + ep + }) + .collect(); + run_relay(eps); } diff --git a/src/relay/mod.rs b/src/relay/mod.rs index ab7988ce..02a19a16 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -1,4 +1,4 @@ -use std::io::Result; +use log::error; use futures::future::join_all; mod tcp; @@ -17,34 +17,45 @@ pub async fn run(eps: Vec) { join_all(workers).await; } -async fn proxy_tcp(ep: Endpoint) -> Result<()> { +async fn proxy_tcp(ep: Endpoint) { let Endpoint { local, remote, opts, .. } = ep; + let lis = TcpListener::bind(local) .await .unwrap_or_else(|_| panic!("unable to bind {}", &local)); - while let Ok((stream, _)) = lis.accept().await { + + loop { + let (stream, _) = match lis.accept().await { + Ok(x) => x, + Err(ref e) => { + error!("failed to accept tcp connection: {}", e); + continue; + } + }; tokio::spawn(tcp::proxy(stream, remote.clone(), opts)); } - Ok(()) } #[cfg(feature = "udp")] mod udp; #[cfg(feature = "udp")] -async fn proxy_udp(ep: Endpoint) -> Result<()> { +async fn proxy_udp(ep: Endpoint) { let Endpoint { local, remote, opts, .. } = ep; - udp::proxy(local, remote, opts).await + + if let Err(ref e) = udp::proxy(local, remote, opts).await { + panic!("udp forward exit: {}", e); + } } fn compute_workers(workers: &[Endpoint]) -> usize { diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 112b9932..6ec3fdbd 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -78,21 +78,22 @@ pub async fn proxy( let (ro, wo) = outbound.split(); #[cfg(all(target_os = "linux", feature = "zero-copy"))] - if zero_copy { + let res = if zero_copy { use zero_copy::copy; - let _ = try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)); + try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)) } else { use normal_copy::copy; - let _ = try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)); - } + try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)) + }; #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] - { + let res = { use normal_copy::copy; - let _ = try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)); - } + try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)) + }; - Ok(()) + // ignore read/write n bytes + res.map(|_| ()) } mod normal_copy { @@ -148,7 +149,7 @@ mod zero_copy { if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 { return Err(Error::new( - ErrorKind::Unsupported, + ErrorKind::Other, "failed to create a pipe", )); } @@ -209,7 +210,7 @@ mod zero_copy { let mut n: usize = 0; let mut done = false; - 'LOOP: loop { + let res = 'LOOP: loop { // read until the socket buffer is empty // or the pipe is filled timeoutfut(timeout, rx.readable()).await??; @@ -224,7 +225,12 @@ mod zero_copy { clear_readiness(rx, Interest::READABLE); break; } - _ => break 'LOOP, + _ => { + break 'LOOP Err(Error::new( + ErrorKind::Other, + "failed to splice from tcp connection", + )) + } } } // write until the pipe is empty @@ -233,23 +239,26 @@ mod zero_copy { match splice_n(rpipe, wfd, n) { x if x > 0 => n -= x as usize, x if x < 0 && is_wouldblock() => { - clear_readiness(wx, Interest::WRITABLE) + clear_readiness(wx, Interest::WRITABLE); + } + _ => { + break 'LOOP Err(Error::new( + ErrorKind::Other, + "failed to splice to tcp connection", + )) } - _ => break 'LOOP, } } // complete if done { - break; + break Ok(()); } - } + }; if done { w.shutdown().await?; - Ok(()) - } else { - Err(Error::new(ErrorKind::ConnectionReset, "connection reset")) - } + }; + res } } diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 59e14ffc..76e85b26 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -4,6 +4,8 @@ use std::net::SocketAddr; use std::sync::{Arc, RwLock}; use std::collections::HashMap; +use log::error; + use tokio::net::UdpSocket; use tokio::time::timeout as timeoutfut; @@ -25,16 +27,28 @@ pub async fn proxy( .. } = conn_opts; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); - let local_sock = Arc::new(UdpSocket::bind(&local).await.unwrap()); + let local_sock = Arc::new(UdpSocket::bind(&local).await?); let timeout = Duration::from_secs(timeout as u64); let mut buf = vec![0u8; BUFFERSIZE]; loop { - let (n, client_addr) = local_sock.recv_from(&mut buf).await?; + let (n, client_addr) = match local_sock.recv_from(&mut buf).await { + Ok(x) => x, + Err(ref e) => { + error!("failed to recv udp packet: {}", e); + continue; + } + }; - let remote_addr = remote.to_sockaddr().await?; + let remote_addr = match remote.to_sockaddr().await { + Ok(x) => x, + Err(ref e) => { + error!("failed to resolve remote addr: {}", e); + continue; + } + }; - // the socket associated with a unique client + // the old/new socket associated with a unique client let alloc_sock = match get_socket(&sock_map, &client_addr) { Some(x) => x, None => { @@ -50,8 +64,12 @@ pub async fn proxy( } }; - alloc_sock.send_to(&buf[..n], &remote_addr).await?; + if let Err(ref e) = alloc_sock.send_to(&buf[..n], &remote_addr).await { + error!("failed to send udp packet: {}", e); + } } + + // Err(Error::new(ErrorKind::Other, "unknown error")) } async fn send_back( diff --git a/src/utils/types.rs b/src/utils/types.rs index 33cdcd22..54e3d569 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -1,4 +1,5 @@ use std::io::Result; +use std::fmt::{Formatter, Display}; use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use crate::dns; @@ -14,6 +15,7 @@ pub enum RemoteAddr { #[derive(Clone, Copy)] pub struct ConnectOpts { + pub use_udp: bool, pub fast_open: bool, pub zero_copy: bool, pub tcp_timeout: usize, @@ -23,10 +25,9 @@ pub struct ConnectOpts { #[derive(Clone)] pub struct Endpoint { - pub udp: bool, pub local: SocketAddr, pub remote: RemoteAddr, - pub conn_opts: ConnectOpts, + pub opts: ConnectOpts, } impl RemoteAddr { @@ -71,7 +72,7 @@ impl Endpoint { local: &str, remote: &str, through: &str, - udp: bool, + use_udp: bool, fast_open: bool, zero_copy: bool, tcp_timeout: usize, @@ -109,10 +110,10 @@ impl Endpoint { }; Endpoint { - udp, local, remote, - conn_opts: ConnectOpts { + opts: ConnectOpts { + use_udp, fast_open, zero_copy, tcp_timeout, @@ -122,3 +123,52 @@ impl Endpoint { } } } + +impl Display for RemoteAddr { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use RemoteAddr::*; + match self { + SocketAddr(x) => write!(f, "{}", x), + DomainName(addr, port) => write!(f, "{}:{}", addr, port), + } + } +} + +impl Display for ConnectOpts { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + macro_rules! on_off { + ($x: expr) => { + if $x { + "on" + } else { + "off" + } + }; + } + if let Some(send_through) = &self.send_through { + write!(f, "send-through={}, ", send_through)?; + } + write!( + f, + "udp-forward={}, tcp-fast-open={}, tcp-zero-copy={}, ", + on_off!(self.use_udp), + on_off!(self.fast_open), + on_off!(self.zero_copy) + )?; + write!( + f, + "tcp-timeout={}s, udp-timeout={}s", + self.tcp_timeout, self.udp_timeout + ) + } +} + +impl Display for Endpoint { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} -> {}, options: {}", + &self.local, &self.remote, &self.opts + ) + } +} From fcc0415245b6aeaf0f7f0162ebbb8dc18364023c Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 11:39:11 +0900 Subject: [PATCH 061/169] add logger --- Cargo.lock | 117 ++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 9 ++++ src/conf/log.rs | 75 +++++++++++++++++++++++++++++++ src/conf/mod.rs | 10 ++++- src/main.rs | 26 ++++++++++- 5 files changed, 234 insertions(+), 3 deletions(-) create mode 100644 src/conf/log.rs diff --git a/Cargo.lock b/Cargo.lock index bbf78acb..dbe86879 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +dependencies = [ + "memchr", +] + [[package]] name = "ansi_term" version = "0.11.0" @@ -63,6 +72,19 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "libc", + "num-integer", + "num-traits", + "time", + "winapi", +] + [[package]] name = "clap" version = "2.33.3" @@ -106,6 +128,28 @@ dependencies = [ "syn", ] +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fern" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" +dependencies = [ + "log", +] + [[package]] name = "form_urlencoded" version = "1.0.1" @@ -250,6 +294,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "idna" version = "0.2.2" @@ -388,6 +438,25 @@ dependencies = [ "winapi", ] +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + [[package]] name = "num_cpus" version = "1.13.0" @@ -554,8 +623,11 @@ name = "realm" version = "1.5.0" dependencies = [ "cfg-if", + "chrono", "clap", "daemonize", + "env_logger", + "fern", "futures", "lazy_static", "libc", @@ -576,6 +648,23 @@ dependencies = [ "bitflags", ] +[[package]] +name = "regex" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + [[package]] name = "resolv-conf" version = "0.7.0" @@ -679,6 +768,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "textwrap" version = "0.11.0" @@ -708,6 +806,16 @@ dependencies = [ "syn", ] +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "tinyvec" version = "1.2.0" @@ -894,6 +1002,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" diff --git a/Cargo.toml b/Cargo.toml index 6e9abaaf..9c0941f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,11 @@ tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", # zero-copy libc = { version = "0.2", optional = true } +#logger +chrono = "0.4" +fern = "0.6" +env_logger = { version = "0.9", optional = true } + [target.'cfg(unix)'.dependencies] daemonize = "0.4" @@ -42,3 +47,7 @@ udp = [] tfo = ["tokio-tfo"] zero-copy = ["libc"] trust-dns = ["trust-dns-resolver"] +x-debug = ["env_logger"] + +[dev-dependencies] +env_logger = "0.9" diff --git a/src/conf/log.rs b/src/conf/log.rs new file mode 100644 index 00000000..2b49448f --- /dev/null +++ b/src/conf/log.rs @@ -0,0 +1,75 @@ +use serde::{Serialize, Deserialize}; +use log::LevelFilter; + +#[derive(Debug, Serialize, Deserialize)] +pub enum LogLevel { + Off, + Error, + Warn, + Info, + Debug, + Trace, +} + +impl From for LevelFilter { + fn from(x: LogLevel) -> Self { + use LogLevel::*; + match x { + Off => LevelFilter::Off, + Error => LevelFilter::Error, + Warn => LevelFilter::Warn, + Info => LevelFilter::Info, + Debug => LevelFilter::Debug, + Trace => LevelFilter::Trace, + } + } +} + +impl Default for LogLevel { + fn default() -> Self { + LogLevel::Info + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct LogConf { + #[serde(default)] + pub level: LogLevel, + #[serde(default = "output")] + pub output: String, +} + +fn output() -> String { + String::from("stdout") +} + +impl Default for LogConf { + fn default() -> Self { + Self { + level: LogLevel::Info, + output: output(), + } + } +} + +impl From for (LevelFilter, fern::Output) { + fn from(conf: LogConf) -> Self { + use std::io; + use std::fs::OpenOptions; + let LogConf { level, ref output } = conf; + let output: fern::Output = match output.as_str() { + "stdout" => io::stdout().into(), + "stderr" => io::stderr().into(), + output => OpenOptions::new() + .write(true) + .create(true) + .append(true) + .open(output) + .unwrap_or_else(|ref e| { + panic!("failed to open {}: {}", output, e) + }) + .into(), + }; + (level.into(), output) + } +} diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 52f4e640..d632b95d 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -2,16 +2,22 @@ use std::fs; use serde::{Serialize, Deserialize}; -mod endpoint; -pub use endpoint::EndpointConf; +mod log; +pub use self::log::LogConf; #[cfg(feature = "trust-dns")] mod dns; #[cfg(feature = "trust-dns")] pub use dns::CompatibeDnsConf; +mod endpoint; +pub use endpoint::EndpointConf; + #[derive(Debug, Serialize, Deserialize)] pub struct FullConf { + #[serde(default)] + pub log: LogConf, + #[cfg(feature = "trust-dns")] #[serde(default, rename = "dns_mode")] pub dns: CompatibeDnsConf, diff --git a/src/main.rs b/src/main.rs index 3f3dd6e1..d3b7e008 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,7 +36,7 @@ fn start_from_conf(conf: String) { .into_iter() .map(|epc| { let ep = epc.build(); - println!("inited: {}", &ep); + log::info!("inited: {}", &ep); ep }) .collect(); @@ -44,6 +44,30 @@ fn start_from_conf(conf: String) { run_relay(eps); } +fn setup_logger(conf: conf::LogConf) { + #[cfg(feature = "x-debug")] + env_logger::init(); + + #[cfg(not(feature = "x-debug"))] + { + let (level, output) = conf.into(); + fern::Dispatch::new() + .format(|out, message, record| { + out.finish(format_args!( + "{}[{}][{}] {}", + chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), + record.target(), + record.level(), + message + )) + }) + .level(level) + .chain(output) + .apply() + .unwrap_or_else(|ref e| panic!("failed to setup logger: {}", e)) + } +} + #[cfg(feature = "trust-dns")] fn setup_dns(dns: conf::CompatibeDnsConf) { use conf::CompatibeDnsConf::*; From 729c579b515bf32ec7e360ca172c1c8bf5bd7887 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 11:51:34 +0900 Subject: [PATCH 062/169] improve startup steps --- src/main.rs | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index d3b7e008..15968b26 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,19 +20,22 @@ fn main() { } fn start_from_cmd(ep: Endpoint) { - run_relay(vec![ep]) + execute(vec![ep]) } fn start_from_conf(conf: String) { let conf = FullConf::from_config_file(&conf); - #[cfg(feature = "trust-dns")] - { - let FullConf { dns, .. } = conf; - setup_dns(dns); - } - let eps: Vec = conf - .endpoints + let FullConf { + log: log_conf, + dns: dns_conf, + endpoints: eps_conf, + } = conf; + + setup_log(log_conf); + setup_dns(dns_conf); + + let eps: Vec = eps_conf .into_iter() .map(|epc| { let ep = epc.build(); @@ -41,10 +44,10 @@ fn start_from_conf(conf: String) { }) .collect(); - run_relay(eps); + execute(eps); } -fn setup_logger(conf: conf::LogConf) { +fn setup_log(conf: conf::LogConf) { #[cfg(feature = "x-debug")] env_logger::init(); @@ -68,21 +71,23 @@ fn setup_logger(conf: conf::LogConf) { } } -#[cfg(feature = "trust-dns")] fn setup_dns(dns: conf::CompatibeDnsConf) { - use conf::CompatibeDnsConf::*; - match dns { - Dns(conf) => { - let (conf, opts) = conf.into(); - dns::configure(Some(conf), Some(opts)); + #[cfg(feature = "trust-dns")] + { + use conf::CompatibeDnsConf::*; + match dns { + Dns(conf) => { + let (conf, opts) = conf.into(); + dns::configure(Some(conf), Some(opts)); + } + DnsMode(mode) => dns::configure(Option::None, Some(mode.into())), + None => (), } - DnsMode(mode) => dns::configure(Option::None, Some(mode.into())), - None => (), + dns::build(); } - dns::build(); } -fn run_relay(eps: Vec) { +fn execute(eps: Vec) { tokio::runtime::Builder::new_multi_thread() .enable_all() .build() From 8d76f8eed7170d30f620641e10c398dc9769298f Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 15:25:55 +0900 Subject: [PATCH 063/169] docs --- readme.md | 50 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 37 insertions(+), 13 deletions(-) diff --git a/readme.md b/readme.md index 5dfbec9d..d7ea2f0f 100644 --- a/readme.md +++ b/readme.md @@ -10,7 +10,7 @@ realm is a simple, high performance relay server written in rust. ## Features -- Zero configuration. Setup and run in one command. +- ~~Zero configuration.~~ Setup and run in one command. - Concurrency. Bidirectional concurrent traffic leads to high performance. - Low resources cost. @@ -74,6 +74,10 @@ realm -c config.json Example
 {
+	"log": {
+		"level": "warn",
+		"output": "/var/log/realm.log"
+	},
 	"dns_mode": {
 		"mode": "ipv4_only",
 		"protocol": "tcp+udp",
@@ -101,33 +105,53 @@ realm -c config.json
 
-### dns +## global: [log, dns, endpoints] +Note: must provide `endpoint.local` and `endpoint.remote` + +--- +### log: [level, output] + +#### log.level +- off +- error +- info *(default)* +- debug +- trace + +#### log.output +- stdout *(default)* +- stderr +- path, e.g. (`/var/log/realm.log`) + +--- +### dns: [mode, protocol, nameservers] this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lookup priority with `"dns_mode": "ipv4_only"`, which is equal to `"dns_mode": {"mode": "ipv4_only"}` -#### mode +#### dns.mode - ipv4_only - ipv6_only - ipv4_then_ipv6 *(default)* - ipv6_then_ipv4 - ipv4_and_ipv6 -#### protocol +#### dns.protocol - tcp - udp - tcp+udp *(default)* -#### nameservers +#### dns.nameservers format: ["server1", "server2" ...] default: On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). Otherwise use google's public dns as default upstream resolver(`8.8.8.8`, `8.8.4.4` and `2001:4860:4860::8888`, `2001:4860:4860::8844`). +--- ### endpoint objects -- local *(listen address)* -- remote *(remote address)* -- through *(send through specified ip or address, this is optional)* -- udp *(true|false, default=false)* -- zero_copy *(true|false, default=false)* -- fast_open *(true|false, default=false)* -- tcp_timeout *(tcp timeout(sec), default=300)* -- udp_timeout *(udp timeout(sec), default=30)* +- local: listen address +- remote: remote address +- through: send through specified ip or address, this is optional +- udp: true|false, default=false +- zero_copy: true|false, default=false +- fast_open: true|false, default=false +- tcp_timeout: tcp timeout(sec), default=300 +- udp_timeout: udp timeout(sec), default=30 From 11dc3566e4edf07cbb32eb18c2f45a3d90860ad8 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 15:55:10 +0900 Subject: [PATCH 064/169] use snake_case --- src/conf/log.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/conf/log.rs b/src/conf/log.rs index 2b49448f..5cbc67fc 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -2,6 +2,7 @@ use serde::{Serialize, Deserialize}; use log::LevelFilter; #[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "snake_case")] pub enum LogLevel { Off, Error, From cf212cb9dbd160c3b442e47ad27fd33de12628db Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 19:37:07 +0900 Subject: [PATCH 065/169] more log details --- src/conf/mod.rs | 4 ++-- src/relay/mod.rs | 19 +++++++++++-------- src/relay/tcp.rs | 45 ++++++++++++++++++++++++++++++++++++++++++--- src/relay/udp.rs | 46 ++++++++++++++++++++++++++++++++++------------ 4 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/conf/mod.rs b/src/conf/mod.rs index d632b95d..6fdf0b01 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -28,8 +28,8 @@ pub struct FullConf { impl FullConf { pub fn from_config_file(file: &str) -> Self { let config = fs::read_to_string(file) - .unwrap_or_else(|_| panic!("unable to open {}", file)); + .unwrap_or_else(|ref e| panic!("unable to open {}: {}", file, e)); serde_json::from_str(&config) - .unwrap_or_else(|_| panic!("unable to parse {}", file)) + .unwrap_or_else(|ref e| panic!("unable to parse {}: {}", file, e)) } } diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 02a19a16..a7278ead 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -1,4 +1,4 @@ -use log::error; +use log::{info, error}; use futures::future::join_all; mod tcp; @@ -25,18 +25,21 @@ async fn proxy_tcp(ep: Endpoint) { .. } = ep; - let lis = TcpListener::bind(local) + let lis = TcpListener::bind(&local) .await - .unwrap_or_else(|_| panic!("unable to bind {}", &local)); + .unwrap_or_else(|e| panic!("unable to bind {}: {}", &local, &e)); loop { - let (stream, _) = match lis.accept().await { + let (stream, addr) = match lis.accept().await { Ok(x) => x, - Err(ref e) => { - error!("failed to accept tcp connection: {}", e); + Err(e) => { + error!("failed to accept tcp connection: {}", &e); continue; } }; + + info!("new tcp connection from client {}", &addr); + tokio::spawn(tcp::proxy(stream, remote.clone(), opts)); } } @@ -53,8 +56,8 @@ async fn proxy_udp(ep: Endpoint) { .. } = ep; - if let Err(ref e) = udp::proxy(local, remote, opts).await { - panic!("udp forward exit: {}", e); + if let Err(e) = udp::proxy(local, remote, opts).await { + panic!("udp forward exit: {}", &e); } } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 6ec3fdbd..8b7b6ada 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -21,15 +21,33 @@ cfg_if! { } use std::io::Result; +use std::fmt::{Display, Formatter}; use std::net::SocketAddr; use std::time::Duration; use futures::try_join; +use log::{debug, info}; + use tokio::net::TcpSocket; use tokio::time::timeout as timeoutfut; use crate::utils::{RemoteAddr, ConnectOpts}; +pub enum TcpDirection { + Forward, + Reverse, +} + +impl Display for TcpDirection { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use TcpDirection::*; + match self { + Forward => write!(f, "forward"), + Reverse => write!(f, "reverse"), + } + } +} + #[allow(unused_variables)] pub async fn proxy( mut inbound: TcpStream, @@ -71,27 +89,42 @@ pub async fn proxy( None => TcpStream::connect(remote).await?, }; + info!("new tcp connection to remote {}", &remote); + inbound.set_nodelay(true)?; outbound.set_nodelay(true)?; let (ri, wi) = inbound.split(); let (ro, wo) = outbound.split(); + use TcpDirection::{Forward, Reverse}; + #[cfg(all(target_os = "linux", feature = "zero-copy"))] let res = if zero_copy { use zero_copy::copy; - try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)) + try_join!( + copy(ri, wo, timeout, Forward), + copy(ro, wi, timeout, Reverse) + ) } else { use normal_copy::copy; - try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)) + try_join!( + copy(ri, wo, timeout, Forward), + copy(ro, wi, timeout, Reverse) + ) }; #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] let res = { use normal_copy::copy; - try_join!(copy(ri, wo, timeout), copy(ro, wi, timeout)) + try_join!( + copy(ri, wo, timeout, Forward), + copy(ro, wi, timeout, Reverse) + ) }; + info!("tcp forward compelete or abort, close these 2 connection"); + // ignore read/write n bytes res.map(|_| ()) } @@ -104,6 +137,7 @@ mod normal_copy { mut r: ReadHalf<'_>, mut w: WriteHalf<'_>, timeout: Duration, + direction: TcpDirection, ) -> Result<()> where { use tokio::io::{AsyncReadExt, AsyncWriteExt}; @@ -119,6 +153,9 @@ where { w.flush().await?; } w.shutdown().await?; + + debug!("tcp forward half-complete, direction: {}", direction); + Ok(()) } } @@ -194,6 +231,7 @@ mod zero_copy { r: ReadHalf<'_>, mut w: WriteHalf<'_>, timeout: Duration, + direction: TcpDirection, ) -> Result<()> { use std::os::unix::io::AsRawFd; use tokio::io::AsyncWriteExt; @@ -257,6 +295,7 @@ mod zero_copy { if done { w.shutdown().await?; + debug!("tcp forward half-complete, direction: {}", direction); }; res } diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 76e85b26..abb925c8 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -4,7 +4,7 @@ use std::net::SocketAddr; use std::sync::{Arc, RwLock}; use std::collections::HashMap; -use log::error; +use log::{debug, info, error}; use tokio::net::UdpSocket; use tokio::time::timeout as timeoutfut; @@ -34,16 +34,18 @@ pub async fn proxy( loop { let (n, client_addr) = match local_sock.recv_from(&mut buf).await { Ok(x) => x, - Err(ref e) => { - error!("failed to recv udp packet: {}", e); + Err(e) => { + error!("failed to recv udp packet from client: {}", &e); continue; } }; + debug!("recv udp packet from {}", &client_addr); + let remote_addr = match remote.to_sockaddr().await { Ok(x) => x, - Err(ref e) => { - error!("failed to resolve remote addr: {}", e); + Err(e) => { + error!("failed to resolve remote addr: {}", &e); continue; } }; @@ -52,6 +54,7 @@ pub async fn proxy( let alloc_sock = match get_socket(&sock_map, &client_addr) { Some(x) => x, None => { + info!("new udp association for client {}", &client_addr); alloc_new_socket( &sock_map, client_addr, @@ -64,8 +67,8 @@ pub async fn proxy( } }; - if let Err(ref e) = alloc_sock.send_to(&buf[..n], &remote_addr).await { - error!("failed to send udp packet: {}", e); + if let Err(e) = alloc_sock.send_to(&buf[..n], &remote_addr).await { + error!("failed to send udp packet to remote: {}", &e); } } @@ -81,15 +84,34 @@ async fn send_back( ) { let mut buf = vec![0u8; BUFFERSIZE]; - while let Ok(Ok((n, _))) = - timeoutfut(timeout, alloc_sock.recv_from(&mut buf)).await - { - if local_sock.send_to(&buf[..n], &client_addr).await.is_err() { - break; + loop { + let res = + match timeoutfut(timeout, alloc_sock.recv_from(&mut buf)).await { + Ok(x) => x, + Err(_) => { + info!("udp association for {} timeout", &client_addr); + break; + } + }; + + let (n, remote_addr) = match res { + Ok(x) => x, + Err(e) => { + error!("failed to recv udp packet from remote: {}", &e); + continue; + } + }; + + debug!("recv udp packet from remote: {}", &remote_addr); + + if let Err(e) = local_sock.send_to(&buf[..n], &client_addr).await { + error!("failed to send udp packet back to client: {}", &e); + continue; } } sock_map.write().unwrap().remove(&client_addr); + info!("remove udp association for {}", &client_addr); } #[inline] From 9567f010e6b38c9605744914c471c9c7460bd88f Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 19:49:07 +0900 Subject: [PATCH 066/169] fix compile error if disable trust-dns --- src/conf/dns.rs | 17 ++++++++++++----- src/conf/mod.rs | 3 --- src/main.rs | 1 + 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/conf/dns.rs b/src/conf/dns.rs index 475c86ef..c9f2e3f8 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -1,10 +1,14 @@ +use cfg_if::cfg_if; use serde::{Serialize, Deserialize}; -use std::net::ToSocketAddrs; - -use trust_dns_resolver as resolver; -use resolver::config::{LookupIpStrategy, NameServerConfig, Protocol}; -use resolver::config::{ResolverConfig, ResolverOpts}; +cfg_if! { + if #[cfg(feature = "trust-dns")] { + use std::net::ToSocketAddrs; + use trust_dns_resolver as resolver; + use resolver::config::{LookupIpStrategy, NameServerConfig, Protocol}; + use resolver::config::{ResolverConfig, ResolverOpts}; + } +} // dns mode #[derive(Debug, Serialize, Deserialize)] @@ -23,6 +27,7 @@ impl Default for DnsMode { } } +#[cfg(feature = "trust-dns")] impl From for ResolverOpts { fn from(mode: DnsMode) -> Self { let ip_strategy = match mode { @@ -49,6 +54,7 @@ pub struct DnsConf { pub nameservers: Vec, } +#[cfg(feature = "trust-dns")] fn read_protocol(net: &str) -> Vec { match net.to_ascii_lowercase().as_str() { "tcp" => vec![Protocol::Tcp], @@ -57,6 +63,7 @@ fn read_protocol(net: &str) -> Vec { } } +#[cfg(feature = "trust-dns")] impl From for (ResolverConfig, ResolverOpts) { fn from(config: DnsConf) -> Self { if config.nameservers.is_empty() { diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 6fdf0b01..b63686e7 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -5,9 +5,7 @@ use serde::{Serialize, Deserialize}; mod log; pub use self::log::LogConf; -#[cfg(feature = "trust-dns")] mod dns; -#[cfg(feature = "trust-dns")] pub use dns::CompatibeDnsConf; mod endpoint; @@ -18,7 +16,6 @@ pub struct FullConf { #[serde(default)] pub log: LogConf, - #[cfg(feature = "trust-dns")] #[serde(default, rename = "dns_mode")] pub dns: CompatibeDnsConf, diff --git a/src/main.rs b/src/main.rs index 15968b26..13c47693 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,6 +71,7 @@ fn setup_log(conf: conf::LogConf) { } } +#[allow(unused_variables)] fn setup_dns(dns: conf::CompatibeDnsConf) { #[cfg(feature = "trust-dns")] { From f3f09b3f567206920ec541e570000f6eddab86a1 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 20:08:21 +0900 Subject: [PATCH 067/169] remove ref binding --- src/conf/log.rs | 6 ++---- src/conf/mod.rs | 4 ++-- src/main.rs | 2 +- src/relay/tcp.rs | 1 + 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/conf/log.rs b/src/conf/log.rs index 5cbc67fc..2ddbbf30 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -57,7 +57,7 @@ impl From for (LevelFilter, fern::Output) { fn from(conf: LogConf) -> Self { use std::io; use std::fs::OpenOptions; - let LogConf { level, ref output } = conf; + let LogConf { level, output } = conf; let output: fern::Output = match output.as_str() { "stdout" => io::stdout().into(), "stderr" => io::stderr().into(), @@ -66,9 +66,7 @@ impl From for (LevelFilter, fern::Output) { .create(true) .append(true) .open(output) - .unwrap_or_else(|ref e| { - panic!("failed to open {}: {}", output, e) - }) + .unwrap_or_else(|e| panic!("failed to open {}: {}", output, &e)) .into(), }; (level.into(), output) diff --git a/src/conf/mod.rs b/src/conf/mod.rs index b63686e7..beb390b2 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -25,8 +25,8 @@ pub struct FullConf { impl FullConf { pub fn from_config_file(file: &str) -> Self { let config = fs::read_to_string(file) - .unwrap_or_else(|ref e| panic!("unable to open {}: {}", file, e)); + .unwrap_or_else(|e| panic!("unable to open {}: {}", file, &e)); serde_json::from_str(&config) - .unwrap_or_else(|ref e| panic!("unable to parse {}: {}", file, e)) + .unwrap_or_else(|e| panic!("unable to parse {}: {}", file, &e)) } } diff --git a/src/main.rs b/src/main.rs index 13c47693..1bafc4b9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -67,7 +67,7 @@ fn setup_log(conf: conf::LogConf) { .level(level) .chain(output) .apply() - .unwrap_or_else(|ref e| panic!("failed to setup logger: {}", e)) + .unwrap_or_else(|e| panic!("failed to setup logger: {}", &e)) } } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 8b7b6ada..4e9dbdb3 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -33,6 +33,7 @@ use tokio::time::timeout as timeoutfut; use crate::utils::{RemoteAddr, ConnectOpts}; +#[derive(Clone, Copy)] pub enum TcpDirection { Forward, Reverse, From 1ac77deb2bd3114c509212c9c5c7d3401bb82156 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 21:26:14 +0900 Subject: [PATCH 068/169] clippy --- src/relay/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index a7278ead..23d169c7 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -25,7 +25,7 @@ async fn proxy_tcp(ep: Endpoint) { .. } = ep; - let lis = TcpListener::bind(&local) + let lis = TcpListener::bind(local) .await .unwrap_or_else(|e| panic!("unable to bind {}: {}", &local, &e)); From 55777a1e3a4720518f82cfd303d6e2f1a0b57924 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 24 Nov 2021 21:42:15 +0900 Subject: [PATCH 069/169] remove subcommands --- src/cmd/mod.rs | 21 ++++++--------------- src/cmd/nav.rs | 3 --- src/main.rs | 11 ++--------- 3 files changed, 8 insertions(+), 27 deletions(-) delete mode 100644 src/cmd/nav.rs diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index b97094ab..0293fabe 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,21 +1,18 @@ -use clap::{Arg, App, SubCommand}; +use clap::{Arg, App, ArgMatches, AppSettings}; use super::Endpoint; use crate::utils::TCP_TIMEOUT; use crate::utils::UDP_TIMEOUT; -mod nav; -pub use nav::run_navigator; - pub enum CmdInput { Config(String), Endpoint(Endpoint), - Navigate, None, } pub fn scan() -> CmdInput { let matches = App::new("Realm") + .setting(AppSettings::ArgRequiredElseHelp) .version(super::VERSION) .about("A high efficiency proxy tool") .arg( @@ -88,14 +85,12 @@ pub fn scan() -> CmdInput { .long("daemon") .help("daemonize"), ) - .subcommand( - SubCommand::with_name("nav") - .about("An Interactive configuration editor") - .version("0.1.0") - .author("zephyr "), - ) .get_matches(); + parse_matches(matches) +} + +fn parse_matches(matches: ArgMatches) -> CmdInput { #[cfg(unix)] if matches.is_present("daemon") { crate::utils::daemonize(); @@ -126,9 +121,5 @@ pub fn scan() -> CmdInput { )); } - if matches.subcommand_matches("nav").is_some() { - return CmdInput::Navigate; - } - CmdInput::None } diff --git a/src/cmd/nav.rs b/src/cmd/nav.rs deleted file mode 100644 index 297cac73..00000000 --- a/src/cmd/nav.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub fn run_navigator() { - todo!() -} diff --git a/src/main.rs b/src/main.rs index 1bafc4b9..f95d18f0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,25 +12,18 @@ const VERSION: &str = "1.5.0-rc5"; fn main() { match cmd::scan() { - CmdInput::Endpoint(ep) => start_from_cmd(ep), + CmdInput::Endpoint(ep) => execute(vec![ep]), CmdInput::Config(conf) => start_from_conf(conf), - CmdInput::Navigate => cmd::run_navigator(), CmdInput::None => {} } } -fn start_from_cmd(ep: Endpoint) { - execute(vec![ep]) -} - fn start_from_conf(conf: String) { - let conf = FullConf::from_config_file(&conf); - let FullConf { log: log_conf, dns: dns_conf, endpoints: eps_conf, - } = conf; + } = FullConf::from_config_file(&conf); setup_log(log_conf); setup_dns(dns_conf); From 369f0c4392a26dd8b1735c5dbc8ef6fe06161811 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 00:23:50 +0900 Subject: [PATCH 070/169] switch to clapv3.0 --- Cargo.lock | 119 ++++++++++++++++++++++++++++++++++----------- Cargo.toml | 2 +- readme.md | 29 ++++++----- src/cmd/mod.rs | 128 +++++++++++++++++++++++++------------------------ 4 files changed, 172 insertions(+), 106 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dbe86879..e32bbc55 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,15 +11,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "ansi_term" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" -dependencies = [ - "winapi", -] - [[package]] name = "async-trait" version = "0.1.48" @@ -87,17 +78,33 @@ dependencies = [ [[package]] name = "clap" -version = "2.33.3" +version = "3.0.0-beta.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" dependencies = [ - "ansi_term", "atty", "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", "strsim", + "termcolor", "textwrap", - "unicode-width", - "vec_map", + "unicase", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -265,6 +272,12 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + [[package]] name = "heck" version = "0.3.2" @@ -311,6 +324,16 @@ dependencies = [ "unicode-normalization", ] +[[package]] +name = "indexmap" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +dependencies = [ + "autocfg", + "hashbrown", +] + [[package]] name = "instant" version = "0.1.9" @@ -403,9 +426,9 @@ checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" -version = "2.3.4" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "mio" @@ -473,6 +496,15 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +[[package]] +name = "os_str_bytes" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" +dependencies = [ + "memchr", +] + [[package]] name = "parking_lot" version = "0.11.1" @@ -542,6 +574,30 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -556,9 +612,9 @@ checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" [[package]] name = "proc-macro2" -version = "1.0.26" +version = "1.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152013215dca273577e18d2bf00fa862b89b24169fb78c4c95aeb07992c9cec" +checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" dependencies = [ "unicode-xid", ] @@ -753,15 +809,15 @@ dependencies = [ [[package]] name = "strsim" -version = "0.8.0" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.68" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ce15dd3ed8aa2f8eeac4716d6ef5ab58b6b9256db41d7e1a0224c2788e8fd87" +checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" dependencies = [ "proc-macro2", "quote", @@ -779,9 +835,9 @@ dependencies = [ [[package]] name = "textwrap" -version = "0.11.0" +version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" dependencies = [ "unicode-width", ] @@ -920,6 +976,15 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.5" @@ -969,10 +1034,10 @@ dependencies = [ ] [[package]] -name = "vec_map" -version = "0.8.2" +name = "version_check" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" [[package]] name = "wasi" diff --git a/Cargo.toml b/Cargo.toml index 9c0941f3..8c9d2ac9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,7 +11,7 @@ cfg-if = "1" futures = "0.3" log = "0.4" -clap = "2" +clap = "3.0.0-beta.5" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/readme.md b/readme.md index d7ea2f0f..72ef072b 100644 --- a/readme.md +++ b/readme.md @@ -31,27 +31,26 @@ cargo build --release --no-default-features --features udp, tfo, zero-copy, trus ## Usage ```shell -Realm 1.x -A high efficiency proxy tool +Realm 1.5.0-rc5 + +A high efficiency relay tool USAGE: - realm [FLAGS] [OPTIONS] [SUBCOMMAND] + realm [FLAGS] [OPTIONS] FLAGS: - -d, --daemon daemonize - -f, --tfo enable tfo - -u, --udp enable udp - -z, --zero-copy enable tcp zero-copy - -h, --help Prints help information - -V, --version Prints version information + -u, --udp enable udp forward + -f, --tfo enable tcp fast open + -z, --splice enable tcp zero copy + -d, --daemon run as a unix daemon OPTIONS: - -c, --config use config file - -l, --listen listen address - -r, --remote remote address - -x, --through send through specific ip or address - --tcp-timeout set timeout value - --udp-timeout set timeout value + -c, --config use config file + -l, --listen listen address + -r, --remote remote address + -x, --through send through ip or address + --tcp-timeout set timeout value for tcp + --udp-timeout set timeout value for udp ``` start from command line arguments: diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 0293fabe..079d7a19 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,4 +1,4 @@ -use clap::{Arg, App, ArgMatches, AppSettings}; +use clap::{App, Arg, ArgMatches, AppSettings}; use super::Endpoint; use crate::utils::TCP_TIMEOUT; @@ -12,79 +12,81 @@ pub enum CmdInput { pub fn scan() -> CmdInput { let matches = App::new("Realm") - .setting(AppSettings::ArgRequiredElseHelp) + .about("A high efficiency relay tool") .version(super::VERSION) - .about("A high efficiency proxy tool") - .arg( - Arg::with_name("config") - .short("c") + .license("MIT") + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::DisableVersionFlag) + .setting( + AppSettings::DisableHelpFlag | AppSettings::DisableHelpSubcommand, + ) + .override_usage("realm [FLAGS] [OPTIONS]") + .help_heading("FLAGS") + .args(&[ + Arg::new("udp") + .short('u') + .long("udp") + .about("enable udp forward") + .display_order(0), + Arg::new("fast_open") + .short('f') + .long("tfo") + .about("enable tcp fast open") + .display_order(1), + Arg::new("zero_copy") + .short('z') + .long("splice") + .about("enable tcp zero copy") + .display_order(2), + Arg::new("daemon") + .short('d') + .long("daemon") + .about("run as a unix daemon") + .display_order(3), + ]) + .help_heading("OPTIONS") + .args(&[ + Arg::new("config") + .short('c') .long("config") - .help("use config file") + .about("use config file") .value_name("path") - .takes_value(true), - ) - .arg( - Arg::with_name("local") - .short("l") + .takes_value(true) + .display_order(0), + Arg::new("local") + .short('l') .long("listen") - .help("listen address") + .about("listen address") .value_name("addr") - .takes_value(true), - ) - .arg( - Arg::with_name("remote") - .short("r") + .takes_value(true) + .display_order(1), + Arg::new("remote") + .short('r') .long("remote") - .help("remote address") + .about("remote address") .value_name("addr") - .takes_value(true), - ) - .arg( - Arg::with_name("tcp_timeout") + .takes_value(true) + .display_order(2), + Arg::new("through") + .short('x') + .long("through") + .about("send through ip or address") + .value_name("addr") + .takes_value(true) + .display_order(3), + Arg::new("tcp_timeout") .long("tcp-timeout") - .help("set timeout value for tcp") + .about("set timeout value for tcp") .value_name("second") - .takes_value(true), - ) - .arg( - Arg::with_name("udp_timeout") + .takes_value(true) + .display_order(4), + Arg::new("udp_timeout") .long("udp-timeout") - .help("set timeout value for udp") + .about("set timeout value for udp") .value_name("second") - .takes_value(true), - ) - .arg( - Arg::with_name("through") - .short("x") - .long("through") - .help("send through ip or address") - .value_name("addr") - .takes_value(true), - ) - .arg( - Arg::with_name("udp") - .short("u") - .long("udp") - .help("enable udp"), - ) - .arg( - Arg::with_name("fast_open") - .short("f") - .long("tfo") - .help("enable tfo"), - ) - .arg( - Arg::with_name("zero_copy") - .short("z") - .long("zero-copy") - .help("enable tcp zero-copy"), - ) - .arg( - Arg::with_name("daemon") - .short("d") - .long("daemon") - .help("daemonize"), - ) + .takes_value(true) + .display_order(5), + ]) .get_matches(); parse_matches(matches) From 2cd0344b72351bb9f6847c0ac0f9ae5e08aa596c Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 00:28:55 +0900 Subject: [PATCH 071/169] set release profile, panic=abort --- Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/Cargo.toml b/Cargo.toml index 8c9d2ac9..49d836e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ daemonize = "0.4" [profile.release] opt-level = 3 lto = true +panic = "abort" [profile.dev] opt-level = 0 From e5d2cfeb14bdc8a58d3c6556d425165c00ae28bc Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 16:03:31 +0900 Subject: [PATCH 072/169] adjust buffer size --- src/relay/tcp.rs | 10 +++++----- src/relay/udp.rs | 7 ++++--- src/utils/consts.rs | 9 +++++++++ src/utils/mod.rs | 4 +++- 4 files changed, 21 insertions(+), 9 deletions(-) create mode 100644 src/utils/consts.rs diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 4e9dbdb3..e62530a2 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -14,9 +14,9 @@ cfg_if! { cfg_if! { if #[cfg(all(target_os = "linux", feature = "zero-copy"))] { - const BUFFER_SIZE: usize = 0x10000; + const BUF_SIZE: usize = crate::utils::DEFAULT_PIPE_CAP; } else { - const BUFFER_SIZE: usize = 0x4000; + const BUF_SIZE: usize = crate::utils::DEFAULT_BUF_SIZE; } } @@ -143,7 +143,7 @@ mod normal_copy { where { use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let mut buf = vec![0u8; BUFFER_SIZE]; + let mut buf = vec![0u8; BUF_SIZE]; let mut n: usize; loop { n = timeoutfut(timeout, r.read(&mut buf)).await??; @@ -253,8 +253,8 @@ mod zero_copy { // read until the socket buffer is empty // or the pipe is filled timeoutfut(timeout, rx.readable()).await??; - while n < BUFFER_SIZE { - match splice_n(rfd, wpipe, BUFFER_SIZE - n) { + while n < BUF_SIZE { + match splice_n(rfd, wpipe, BUF_SIZE - n) { x if x > 0 => n += x as usize, x if x == 0 => { done = true; diff --git a/src/relay/udp.rs b/src/relay/udp.rs index abb925c8..b8b4bee8 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -9,12 +9,13 @@ use log::{debug, info, error}; use tokio::net::UdpSocket; use tokio::time::timeout as timeoutfut; +use crate::utils::DEFAULT_BUF_SIZE; use crate::utils::{RemoteAddr, ConnectOpts}; use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; // client <--> allocated socket type SockMap = Arc>>>; -const BUFFERSIZE: usize = 0x1000; +const BUF_SIZE: usize = DEFAULT_BUF_SIZE; pub async fn proxy( local: SocketAddr, @@ -29,7 +30,7 @@ pub async fn proxy( let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let local_sock = Arc::new(UdpSocket::bind(&local).await?); let timeout = Duration::from_secs(timeout as u64); - let mut buf = vec![0u8; BUFFERSIZE]; + let mut buf = vec![0u8; BUF_SIZE]; loop { let (n, client_addr) = match local_sock.recv_from(&mut buf).await { @@ -82,7 +83,7 @@ async fn send_back( alloc_sock: Arc, timeout: Duration, ) { - let mut buf = vec![0u8; BUFFERSIZE]; + let mut buf = vec![0u8; BUF_SIZE]; loop { let res = diff --git a/src/utils/consts.rs b/src/utils/consts.rs new file mode 100644 index 00000000..9abfb228 --- /dev/null +++ b/src/utils/consts.rs @@ -0,0 +1,9 @@ +// https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1 +pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { + 512 +} else { + 8 * 1024 +}; + +// Since Linux 2.6.11, the pipe capacity is 16 pages +pub const DEFAULT_PIPE_CAP: usize = 16 * 4096; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b66a16f6..cedd490b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,7 +1,9 @@ mod types; - pub use types::*; +mod consts; +pub use consts::*; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; #[allow(unused)] From d28c1eb4287e65e596e2d1a090a5f051f6a3d63b Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 16:31:51 +0900 Subject: [PATCH 073/169] use mimalloc --- Cargo.lock | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 13 +++++++++++-- readme.md | 2 ++ src/main.rs | 17 +++++++++++++++++ 4 files changed, 83 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e32bbc55..d60a7ac6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -57,6 +57,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cc" +version = "1.0.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22a9137b95ea06864e018375b72adfb7db6e6f68cfc8df5a04d00288050485ee" + [[package]] name = "cfg-if" version = "1.0.0" @@ -167,6 +173,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_extra" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" + [[package]] name = "futures" version = "0.3.15" @@ -367,6 +379,27 @@ version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +[[package]] +name = "jemalloc-sys" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d3b9f3f5c9b31aa0f5ed3260385ac205db665baa41d49bb8338008ae94ede45" +dependencies = [ + "cc", + "fs_extra", + "libc", +] + +[[package]] +name = "jemallocator" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43ae63fcfc45e99ab3d1b29a46782ad679e98436c3169d15a167a1108a724b69" +dependencies = [ + "jemalloc-sys", + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -379,6 +412,15 @@ version = "0.2.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" +[[package]] +name = "libmimalloc-sys" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9636c194f9db483f4d0adf2f99a65011a99f904bd222bbd67fb4df4f37863c30" +dependencies = [ + "cc", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -430,6 +472,15 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" +[[package]] +name = "mimalloc" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf5f78c1d9892fb5677a8b2f543f967ab891ac0f71feecd961435b74f877283a" +dependencies = [ + "libmimalloc-sys", +] + [[package]] name = "mio" version = "0.7.11" @@ -685,9 +736,11 @@ dependencies = [ "env_logger", "fern", "futures", + "jemallocator", "lazy_static", "libc", "log", + "mimalloc", "serde", "serde_json", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 49d836e0..00d75fd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,11 +26,18 @@ tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", # zero-copy libc = { version = "0.2", optional = true } -#logger +# logger chrono = "0.4" fern = "0.6" env_logger = { version = "0.9", optional = true } +# malloc +mimalloc = { version = "0.1", optional = true } + +[target.'cfg(not(target_env = "msvc"))'.dependencies] +jemallocator = {version = "0.3", optional = true } + +# deamon [target.'cfg(unix)'.dependencies] daemonize = "0.4" @@ -43,11 +50,13 @@ panic = "abort" opt-level = 0 [features] -default = ["udp", "zero-copy", "trust-dns"] +default = ["udp", "zero-copy", "trust-dns", "mi-malloc"] udp = [] tfo = ["tokio-tfo"] zero-copy = ["libc"] trust-dns = ["trust-dns-resolver"] +jemalloc = ["jemallocator"] +mi-malloc = ["mimalloc"] x-debug = ["env_logger"] [dev-dependencies] diff --git a/readme.md b/readme.md index 72ef072b..480108ec 100644 --- a/readme.md +++ b/readme.md @@ -20,6 +20,8 @@ available Options: - trust-dns *(enabled)* - zero-copy *(enabled on linux)* - tfo *(disabled)* +- mi-malloc *(enabled)* +- jemalloc ```shell # simple tcp diff --git a/src/main.rs b/src/main.rs index f95d18f0..e6b99a70 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,12 +4,29 @@ mod conf; mod utils; mod relay; +use cfg_if::cfg_if; use cmd::CmdInput; use conf::FullConf; use utils::Endpoint; const VERSION: &str = "1.5.0-rc5"; +cfg_if! { + if #[cfg(all(feature = "mi-malloc"))] { + use mimalloc::MiMalloc; + #[global_allocator] + static GLOBAL: MiMalloc = MiMalloc; + } +} + +cfg_if! { + if #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))] { + use jemallocator::Jemalloc; + #[global_allocator] + static GLOBAL: Jemalloc = Jemalloc; + } +} + fn main() { match cmd::scan() { CmdInput::Endpoint(ep) => execute(vec![ep]), From 78f821cc50028a1a39124406d840b8fb0de32d36 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 17:14:52 +0900 Subject: [PATCH 074/169] show compiled features --- src/cmd/mod.rs | 4 +++- src/main.rs | 2 +- src/utils/consts.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 079d7a19..b572f336 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,6 +1,8 @@ use clap::{App, Arg, ArgMatches, AppSettings}; use super::Endpoint; +use super::VERSION; +use crate::utils::FEATURES; use crate::utils::TCP_TIMEOUT; use crate::utils::UDP_TIMEOUT; @@ -13,7 +15,7 @@ pub enum CmdInput { pub fn scan() -> CmdInput { let matches = App::new("Realm") .about("A high efficiency relay tool") - .version(super::VERSION) + .version(format!("{} {}", VERSION, FEATURES).as_str()) .license("MIT") .setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::DisableVersionFlag) diff --git a/src/main.rs b/src/main.rs index e6b99a70..5c061b47 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use cmd::CmdInput; use conf::FullConf; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc5"; +const VERSION: &str = "1.5.0-rc6"; cfg_if! { if #[cfg(all(feature = "mi-malloc"))] { diff --git a/src/utils/consts.rs b/src/utils/consts.rs index 9abfb228..38ff5155 100644 --- a/src/utils/consts.rs +++ b/src/utils/consts.rs @@ -1,3 +1,5 @@ +use std::fmt::{Display, Formatter}; + // https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1 pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { 512 @@ -6,4 +8,56 @@ pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { }; // Since Linux 2.6.11, the pipe capacity is 16 pages +#[cfg(all(target_os = "linux", feature = "zero-copy"))] pub const DEFAULT_PIPE_CAP: usize = 16 * 4096; + +// features +macro_rules! def_feat { + ($fet: ident, $name: expr) => { + pub const $fet: bool = if cfg!(feature = $name) { true } else { false }; + }; +} + +def_feat!(FEATURE_UDP, "udp"); +def_feat!(FEATURE_TFO, "tfo"); +def_feat!(FEATURE_ZERO_COPY, "zero-copy"); +def_feat!(FEATURE_TRUST_DNS, "trust-dns"); +def_feat!(FEATURE_MIMALLOC, "mi-malloc"); +def_feat!(FEATURE_JEMALLOC, "jemalloc"); + +pub struct Features { + pub udp: bool, + pub tfo: bool, + pub zero_copy: bool, + pub trust_dns: bool, + pub mimalloc: bool, + pub jemalloc: bool, +} + +pub const FEATURES: Features = Features { + udp: FEATURE_UDP, + tfo: FEATURE_TFO, + zero_copy: FEATURE_ZERO_COPY, + trust_dns: FEATURE_TRUST_DNS, + mimalloc: FEATURE_MIMALLOC, + jemalloc: FEATURE_JEMALLOC, +}; + +impl Display for Features { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + macro_rules! disp_feat { + ($field: ident, $show: expr) => { + if self.$field { + write!(f, "[{}]", $show)?; + } + }; + } + disp_feat!(udp, "udp"); + disp_feat!(tfo, "tfo"); + disp_feat!(zero_copy, "zero-copy"); + disp_feat!(trust_dns, "trust-dns"); + disp_feat!(mimalloc, "mimalloc"); + disp_feat!(jemalloc, "jemalloc"); + Ok(()) + } +} From 3c84cace062371265b448c5b17fc59b7cde528c9 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 17:27:40 +0900 Subject: [PATCH 075/169] pass ci && update workflow --- .github/ISSUE_TEMPLATE/bug_report.md | 38 ---------------------------- .github/workflows/cross_compile.yml | 2 +- .github/workflows/release.yml | 2 +- src/main.rs | 6 +---- 4 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index dd84ea78..00000000 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: '' -assignees: '' - ---- - -**Describe the bug** -A clear and concise description of what the bug is. - -**To Reproduce** -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -**Expected behavior** -A clear and concise description of what you expected to happen. - -**Screenshots** -If applicable, add screenshots to help explain your problem. - -**Desktop (please complete the following information):** - - OS: [e.g. iOS] - - Browser [e.g. chrome, safari] - - Version [e.g. 22] - -**Smartphone (please complete the following information):** - - Device: [e.g. iPhone6] - - OS: [e.g. iOS8.1] - - Browser [e.g. stock browser, safari] - - Version [e.g. 22] - -**Additional context** -Add any other context about the problem here. diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index a0337549..b2f61c0a 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -29,7 +29,7 @@ jobs: - name: install toolchain uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly target: ${{ matrix.target }} override: true - name: compile diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7fcf7104..47858222 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: - name: install toolchain uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly target: ${{ matrix.target }} override: true - name: compile diff --git a/src/main.rs b/src/main.rs index 5c061b47..b7b40f40 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,11 +16,7 @@ cfg_if! { use mimalloc::MiMalloc; #[global_allocator] static GLOBAL: MiMalloc = MiMalloc; - } -} - -cfg_if! { - if #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))] { + } else if #[cfg(all(feature = "jemalloc", not(target_env = "msvc")))] { use jemallocator::Jemalloc; #[global_allocator] static GLOBAL: Jemalloc = Jemalloc; From 858e61232a76d1a087fe0ab0821113c1bd1ff3c2 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 18:55:49 +0900 Subject: [PATCH 076/169] use libc's malloc by default --- Cargo.toml | 2 +- build.rs | 1 + readme.md | 10 ++++++---- 3 files changed, 8 insertions(+), 5 deletions(-) create mode 100644 build.rs diff --git a/Cargo.toml b/Cargo.toml index 00d75fd2..a47459cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,7 +50,7 @@ panic = "abort" opt-level = 0 [features] -default = ["udp", "zero-copy", "trust-dns", "mi-malloc"] +default = ["udp", "zero-copy", "trust-dns"] udp = [] tfo = ["tokio-tfo"] zero-copy = ["libc"] diff --git a/build.rs b/build.rs new file mode 100644 index 00000000..f328e4d9 --- /dev/null +++ b/build.rs @@ -0,0 +1 @@ +fn main() {} diff --git a/readme.md b/readme.md index 480108ec..7aa4a253 100644 --- a/readme.md +++ b/readme.md @@ -16,13 +16,15 @@ realm is a simple, high performance relay server written in rust. ## Custom Build available Options: -- udp *(enabled)* -- trust-dns *(enabled)* +- udp *(enabled by default)* +- trust-dns *(enabled by default)* - zero-copy *(enabled on linux)* -- tfo *(disabled)* -- mi-malloc *(enabled)* +- tfo +- mi-malloc - jemalloc +see also: `Cargo.toml` + ```shell # simple tcp cargo build --release --no-default-features From 4020428717a10b9498c25c3fd9c33a0db08692f5 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 25 Nov 2021 22:51:19 +0900 Subject: [PATCH 077/169] add -h and -v; #1 --- src/cmd/mod.rs | 167 ++++++++++++++++++++++++++++--------------------- 1 file changed, 96 insertions(+), 71 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index b572f336..80fa9a2c 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -13,83 +13,108 @@ pub enum CmdInput { } pub fn scan() -> CmdInput { - let matches = App::new("Realm") + let version = format!("{} {}", VERSION, FEATURES); + let app = App::new("Realm") .about("A high efficiency relay tool") - .version(format!("{} {}", VERSION, FEATURES).as_str()) - .license("MIT") + .version(version.as_str()) + .license("MIT"); + + let app = app .setting(AppSettings::ArgRequiredElseHelp) .setting(AppSettings::DisableVersionFlag) .setting( AppSettings::DisableHelpFlag | AppSettings::DisableHelpSubcommand, ) - .override_usage("realm [FLAGS] [OPTIONS]") - .help_heading("FLAGS") - .args(&[ - Arg::new("udp") - .short('u') - .long("udp") - .about("enable udp forward") - .display_order(0), - Arg::new("fast_open") - .short('f') - .long("tfo") - .about("enable tcp fast open") - .display_order(1), - Arg::new("zero_copy") - .short('z') - .long("splice") - .about("enable tcp zero copy") - .display_order(2), - Arg::new("daemon") - .short('d') - .long("daemon") - .about("run as a unix daemon") - .display_order(3), - ]) - .help_heading("OPTIONS") - .args(&[ - Arg::new("config") - .short('c') - .long("config") - .about("use config file") - .value_name("path") - .takes_value(true) - .display_order(0), - Arg::new("local") - .short('l') - .long("listen") - .about("listen address") - .value_name("addr") - .takes_value(true) - .display_order(1), - Arg::new("remote") - .short('r') - .long("remote") - .about("remote address") - .value_name("addr") - .takes_value(true) - .display_order(2), - Arg::new("through") - .short('x') - .long("through") - .about("send through ip or address") - .value_name("addr") - .takes_value(true) - .display_order(3), - Arg::new("tcp_timeout") - .long("tcp-timeout") - .about("set timeout value for tcp") - .value_name("second") - .takes_value(true) - .display_order(4), - Arg::new("udp_timeout") - .long("udp-timeout") - .about("set timeout value for udp") - .value_name("second") - .takes_value(true) - .display_order(5), - ]) - .get_matches(); + .override_usage("realm [FLAGS] [OPTIONS]"); + + let app = app.help_heading("FLAGS").args(&[ + Arg::new("udp") + .short('u') + .long("udp") + .about("enable udp forward") + .display_order(0), + Arg::new("fast_open") + .short('f') + .long("tfo") + .about("enable tcp fast open") + .display_order(1), + Arg::new("zero_copy") + .short('z') + .long("splice") + .about("enable tcp zero copy") + .display_order(2), + Arg::new("daemon") + .short('d') + .long("daemon") + .about("run as a unix daemon") + .display_order(3), + ]); + + let app = app.help_heading("OPTIONS").args(&[ + Arg::new("help") + .short('h') + .long("help") + .about("show help") + .display_order(0), + Arg::new("version") + .short('v') + .long("version") + .about("show version") + .display_order(1), + Arg::new("config") + .short('c') + .long("config") + .about("use config file") + .value_name("path") + .takes_value(true) + .display_order(2), + Arg::new("local") + .short('l') + .long("listen") + .about("listen address") + .value_name("addr") + .takes_value(true) + .display_order(3), + Arg::new("remote") + .short('r') + .long("remote") + .about("remote address") + .value_name("addr") + .takes_value(true) + .display_order(4), + Arg::new("through") + .short('x') + .long("through") + .about("send through ip or address") + .value_name("addr") + .takes_value(true) + .display_order(5), + Arg::new("tcp_timeout") + .long("tcp-timeout") + .about("set timeout value for tcp") + .value_name("second") + .takes_value(true) + .display_order(6), + Arg::new("udp_timeout") + .long("udp-timeout") + .about("set timeout value for udp") + .value_name("second") + .takes_value(true) + .display_order(7), + ]); + + let mut xapp = app.clone(); + let matches = app.get_matches(); + + if matches.is_present("help") { + xapp.print_help().unwrap(); + return CmdInput::None; + } + + if matches.is_present("version") { + print!("{}", xapp.render_version()); + return CmdInput::None; + } parse_matches(matches) } From a0b1a189aab4af1b982fc45a41bf3cc28e30e93f Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 28 Nov 2021 00:20:20 +0900 Subject: [PATCH 078/169] default value for each field of dns conf --- src/conf/dns.rs | 29 +++++++++++++++++++++++------ src/dns/trust_dns.rs | 4 ++-- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/conf/dns.rs b/src/conf/dns.rs index c9f2e3f8..a404a6a9 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -49,8 +49,11 @@ impl From for ResolverOpts { pub struct DnsConf { #[serde(default)] pub mode: DnsMode, + #[serde(default)] pub protocol: String, + + #[serde(default)] pub nameservers: Vec, } @@ -66,14 +69,28 @@ fn read_protocol(net: &str) -> Vec { #[cfg(feature = "trust-dns")] impl From for (ResolverConfig, ResolverOpts) { fn from(config: DnsConf) -> Self { - if config.nameservers.is_empty() { - panic!("no nameserver provided"); - } let opts = config.mode.into(); - let mut conf = ResolverConfig::new(); + let protocols = read_protocol(&config.protocol); - for addr in config.nameservers { - let socket_addr = addr.to_socket_addrs().unwrap().next().unwrap(); + + let nameservers = if config.nameservers.is_empty() { + use crate::dns::DnsConf as XdnsConf; + let XdnsConf { conf, .. } = XdnsConf::default(); + let mut addrs: Vec = + conf.name_servers().iter().map(|x| x.socket_addr).collect(); + addrs.dedup(); + addrs + } else { + config + .nameservers + .iter() + .map(|x| x.to_socket_addrs().unwrap().next().unwrap()) + .collect() + }; + + let mut conf = ResolverConfig::new(); + + for socket_addr in nameservers { for protocol in protocols.clone() { conf.add_name_server(NameServerConfig { socket_addr, diff --git a/src/dns/trust_dns.rs b/src/dns/trust_dns.rs index f45a4e19..f55ac76c 100644 --- a/src/dns/trust_dns.rs +++ b/src/dns/trust_dns.rs @@ -13,8 +13,8 @@ use lazy_static::lazy_static; #[derive(Clone)] pub struct DnsConf { - conf: ResolverConfig, - opts: ResolverOpts, + pub conf: ResolverConfig, + pub opts: ResolverOpts, } impl Default for DnsConf { From 0b7143c9c5acc5d9200d054185df000c57a6bc63 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 28 Nov 2021 17:43:52 +0900 Subject: [PATCH 079/169] support cmd arguments override --- src/cmd/mod.rs | 170 ++++++++++++++++++++++++++++++++----------- src/conf/dns.rs | 108 +++++++++++++++++++++------ src/conf/endpoint.rs | 16 ++-- src/conf/log.rs | 17 ++++- src/conf/mod.rs | 72 +++++++++++++++++- src/main.rs | 30 +++++--- 6 files changed, 327 insertions(+), 86 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 80fa9a2c..ec805f91 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,33 +1,22 @@ -use clap::{App, Arg, ArgMatches, AppSettings}; +use clap::{App, AppSettings}; +use clap::{Arg, ArgMatches}; + +use crate::conf::GlobalOpts; +use crate::conf::EndpointConf; -use super::Endpoint; use super::VERSION; use crate::utils::FEATURES; use crate::utils::TCP_TIMEOUT; use crate::utils::UDP_TIMEOUT; pub enum CmdInput { - Config(String), - Endpoint(Endpoint), + Config(String, GlobalOpts), + Endpoint(EndpointConf, GlobalOpts), None, } -pub fn scan() -> CmdInput { - let version = format!("{} {}", VERSION, FEATURES); - let app = App::new("Realm") - .about("A high efficiency relay tool") - .version(version.as_str()) - .license("MIT"); - - let app = app - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::DisableVersionFlag) - .setting( - AppSettings::DisableHelpFlag | AppSettings::DisableHelpSubcommand, - ) - .override_usage("realm [FLAGS] [OPTIONS]"); - - let app = app.help_heading("FLAGS").args(&[ +fn add_flags(app: App) -> App { + app.help_heading("FLAGS").args(&[ Arg::new("udp") .short('u') .long("udp") @@ -48,9 +37,11 @@ pub fn scan() -> CmdInput { .long("daemon") .about("run as a unix daemon") .display_order(3), - ]); + ]) +} - let app = app.help_heading("OPTIONS").args(&[ +fn add_options(app: App) -> App { + app.help_heading("OPTIONS").args(&[ Arg::new("help") .short('h') .long("help") @@ -101,7 +92,62 @@ pub fn scan() -> CmdInput { .value_name("second") .takes_value(true) .display_order(7), - ]); + ]) +} + +fn add_global_options(app: App) -> App { + app.help_heading("GLOBAL OPTIONS").args(&[ + Arg::new("log_level") + .long("log-level") + .about("override log level") + .value_name("level") + .takes_value(true) + .display_order(0), + Arg::new("log_output") + .long("log-output") + .about("override log output") + .value_name("path") + .takes_value(true) + .display_order(1), + Arg::new("dns_mode") + .long("dns-mode") + .about("override dns mode") + .value_name("mode") + .takes_value(true) + .display_order(2), + Arg::new("dns_protocol") + .long("dns-protocol") + .about("override dns protocol") + .value_name("protocol") + .takes_value(true) + .display_order(3), + Arg::new("dns_servers") + .long("dns-servers") + .about("override dns servers") + .value_name("protocol") + .takes_value(true) + .display_order(4), + ]) +} + +pub fn scan() -> CmdInput { + let version = format!("{} {}", VERSION, FEATURES); + let app = App::new("Realm") + .about("A high efficiency relay tool") + .version(version.as_str()) + .license("MIT"); + + let app = app + .setting(AppSettings::ArgRequiredElseHelp) + .setting(AppSettings::DisableVersionFlag) + .setting( + AppSettings::DisableHelpFlag | AppSettings::DisableHelpSubcommand, + ) + .override_usage("realm [FLAGS] [OPTIONS]"); + + let app = add_flags(app); + let app = add_options(app); + let app = add_global_options(app); let mut xapp = app.clone(); let matches = app.get_matches(); @@ -125,30 +171,70 @@ fn parse_matches(matches: ArgMatches) -> CmdInput { crate::utils::daemonize(); } + let opts = parse_global_opts(&matches); + if let Some(config) = matches.value_of("config") { - return CmdInput::Config(config.to_string()); + return CmdInput::Config(String::from(config), opts); } - if let (Some(local), Some(remote)) = - (matches.value_of("local"), matches.value_of("remote")) + if matches.value_of("local").is_some() + && matches.value_of("remote").is_some() { - let tcp_timeout = matches - .value_of("tcp_timeout") - .map_or(TCP_TIMEOUT, |t| t.parse::().unwrap_or(TCP_TIMEOUT)); - let udp_timeout = matches - .value_of("udp_timeout") - .map_or(UDP_TIMEOUT, |t| t.parse::().unwrap_or(UDP_TIMEOUT)); - return CmdInput::Endpoint(Endpoint::new( - local, - remote, - matches.value_of("through").unwrap_or(""), - matches.is_present("udp"), - matches.is_present("fast_open"), - matches.is_present("zero_copy"), - tcp_timeout, - udp_timeout, - )); + let ep = parse_single_ep(&matches); + return CmdInput::Endpoint(ep, opts); } CmdInput::None } + +fn parse_single_ep(matches: &ArgMatches) -> EndpointConf { + let udp = matches.is_present("udp"); + let fast_open = matches.is_present("fast_open"); + let zero_copy = matches.is_present("zero_copy"); + + let local = matches.value_of("local").unwrap().to_string(); + let remote = matches.value_of("remote").unwrap().to_string(); + let through = matches + .value_of("through") + .map_or(String::new(), String::from); + + let tcp_timeout = matches + .value_of("tcp_timeout") + .map_or(TCP_TIMEOUT, |t| t.parse::().unwrap_or(TCP_TIMEOUT)); + let udp_timeout = matches + .value_of("udp_timeout") + .map_or(UDP_TIMEOUT, |t| t.parse::().unwrap_or(UDP_TIMEOUT)); + + EndpointConf { + udp, + fast_open, + zero_copy, + local, + remote, + through, + tcp_timeout, + udp_timeout, + } +} + +fn parse_global_opts(matches: &ArgMatches) -> GlobalOpts { + let log_level = matches + .value_of("log_level") + .map(|x| String::from(x).into()); + let log_output = matches.value_of("log_output").map(String::from); + let dns_mode = matches.value_of("dns_mode").map(|x| String::from(x).into()); + let dns_protocol = matches + .value_of("dns_protocol") + .map(|x| String::from(x).into()); + let dns_servers = matches + .value_of("dns_servers") + .map(|x| x.split(',').map(String::from).collect()); + + GlobalOpts { + log_level, + log_output, + dns_mode, + dns_protocol, + dns_servers, + } +} diff --git a/src/conf/dns.rs b/src/conf/dns.rs index a404a6a9..e9af7813 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -11,7 +11,7 @@ cfg_if! { } // dns mode -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[serde(rename_all = "snake_case")] pub enum DnsMode { Ipv4Only, @@ -27,6 +27,20 @@ impl Default for DnsMode { } } +impl From for DnsMode { + fn from(s: String) -> Self { + use DnsMode::*; + match s.to_ascii_lowercase().as_str() { + "ipv4_only" => Ipv4Only, + "ipv6_only" => Ipv6Only, + "ipv4_and_ipv6" => Ipv4AndIpv6, + "ipv4_then_ipv6" => Ipv4ThenIpv6, + "ipv6_then_ipv4" => Ipv6ThenIpv4, + _ => Self::default(), + } + } +} + #[cfg(feature = "trust-dns")] impl From for ResolverOpts { fn from(mode: DnsMode) -> Self { @@ -44,36 +58,71 @@ impl From for ResolverOpts { } } +// dns protocol +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[serde(rename_all = "snake_case")] +pub enum DnsProtocol { + Tcp, + Udp, + TcpAndUdp, +} + +impl Default for DnsProtocol { + fn default() -> Self { + Self::TcpAndUdp + } +} + +impl From for DnsProtocol { + fn from(s: String) -> Self { + use DnsProtocol::*; + match s.to_ascii_lowercase().as_str() { + "tcp" => Tcp, + "udp" => Udp, + _ => TcpAndUdp, + } + } +} + +#[cfg(feature = "trust-dns")] +impl From for Vec { + fn from(x: DnsProtocol) -> Self { + use DnsProtocol::*; + match x { + Tcp => vec![Protocol::Tcp], + Udp => vec![Protocol::Udp], + TcpAndUdp => vec![Protocol::Tcp, Protocol::Udp], + } + } +} + // dns config -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Default, Serialize, Deserialize)] pub struct DnsConf { #[serde(default)] pub mode: DnsMode, #[serde(default)] - pub protocol: String, + pub protocol: DnsProtocol, #[serde(default)] pub nameservers: Vec, } -#[cfg(feature = "trust-dns")] -fn read_protocol(net: &str) -> Vec { - match net.to_ascii_lowercase().as_str() { - "tcp" => vec![Protocol::Tcp], - "udp" => vec![Protocol::Udp], - _ => vec![Protocol::Tcp, Protocol::Udp], - } -} - #[cfg(feature = "trust-dns")] impl From for (ResolverConfig, ResolverOpts) { - fn from(config: DnsConf) -> Self { - let opts = config.mode.into(); + fn from(conf: DnsConf) -> Self { + let DnsConf { + mode, + protocol, + nameservers, + } = conf; + + let opts = mode.into(); - let protocols = read_protocol(&config.protocol); + let protocols: Vec = protocol.into(); - let nameservers = if config.nameservers.is_empty() { + let nameservers = if nameservers.is_empty() { use crate::dns::DnsConf as XdnsConf; let XdnsConf { conf, .. } = XdnsConf::default(); let mut addrs: Vec = @@ -81,8 +130,7 @@ impl From for (ResolverConfig, ResolverOpts) { addrs.dedup(); addrs } else { - config - .nameservers + nameservers .iter() .map(|x| x.to_socket_addrs().unwrap().next().unwrap()) .collect() @@ -107,14 +155,32 @@ impl From for (ResolverConfig, ResolverOpts) { // compatible dns config #[derive(Debug, Serialize, Deserialize)] #[serde(untagged, rename_all = "snake_case")] -pub enum CompatibeDnsConf { - Dns(DnsConf), +pub enum CompatibleDnsConf { + DnsConf(DnsConf), DnsMode(DnsMode), None, } -impl Default for CompatibeDnsConf { +impl Default for CompatibleDnsConf { fn default() -> Self { Self::None } } + +impl AsRef for CompatibleDnsConf { + fn as_ref(&self) -> &DnsConf { + match self { + CompatibleDnsConf::DnsConf(x) => x, + _ => unreachable!(), + } + } +} + +impl AsMut for CompatibleDnsConf { + fn as_mut(&mut self) -> &mut DnsConf { + match self { + CompatibleDnsConf::DnsConf(x) => x, + _ => unreachable!(), + } + } +} diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index 17c2292e..765c08e7 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -4,26 +4,26 @@ use crate::utils::Endpoint; #[derive(Debug, Serialize, Deserialize)] pub struct EndpointConf { #[serde(default)] - udp: bool, + pub udp: bool, #[serde(default)] - fast_open: bool, + pub fast_open: bool, #[serde(default)] - zero_copy: bool, + pub zero_copy: bool, #[serde(default = "tcp_timeout")] - tcp_timeout: usize, + pub tcp_timeout: usize, #[serde(default = "udp_timeout")] - udp_timeout: usize, + pub udp_timeout: usize, - local: String, + pub local: String, - remote: String, + pub remote: String, #[serde(default)] - through: String, + pub through: String, } const fn tcp_timeout() -> usize { diff --git a/src/conf/log.rs b/src/conf/log.rs index 2ddbbf30..222e0781 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -1,7 +1,7 @@ use serde::{Serialize, Deserialize}; use log::LevelFilter; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] #[serde(rename_all = "snake_case")] pub enum LogLevel { Off, @@ -12,6 +12,21 @@ pub enum LogLevel { Trace, } +impl From for LogLevel { + fn from(x: String) -> Self { + use LogLevel::*; + match x.to_ascii_lowercase().as_str() { + "off" => Off, + "error" => Error, + "warn" => Warn, + "info" => Info, + "debug" => Debug, + "trace" => Trace, + _ => Self::default(), + } + } +} + impl From for LevelFilter { fn from(x: LogLevel) -> Self { use LogLevel::*; diff --git a/src/conf/mod.rs b/src/conf/mod.rs index beb390b2..d1767021 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -3,30 +3,94 @@ use std::fs; use serde::{Serialize, Deserialize}; mod log; -pub use self::log::LogConf; +pub use self::log::{LogLevel, LogConf}; mod dns; -pub use dns::CompatibeDnsConf; +pub use dns::{DnsMode, DnsProtocol, DnsConf, CompatibleDnsConf}; mod endpoint; pub use endpoint::EndpointConf; -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Default)] +pub struct GlobalOpts { + pub log_level: Option, + pub log_output: Option, + pub dns_mode: Option, + pub dns_protocol: Option, + pub dns_servers: Option>, +} + +#[derive(Debug, Default, Serialize, Deserialize)] pub struct FullConf { #[serde(default)] pub log: LogConf, #[serde(default, rename = "dns_mode")] - pub dns: CompatibeDnsConf, + pub dns: CompatibleDnsConf, pub endpoints: Vec, } impl FullConf { + #[allow(unused)] + pub fn new( + log: LogConf, + dns: DnsConf, + endpoints: Vec, + ) -> Self { + FullConf { + log, + dns: CompatibleDnsConf::DnsConf(dns), + endpoints, + } + } + pub fn from_config_file(file: &str) -> Self { let config = fs::read_to_string(file) .unwrap_or_else(|e| panic!("unable to open {}: {}", file, &e)); serde_json::from_str(&config) .unwrap_or_else(|e| panic!("unable to parse {}: {}", file, &e)) } + + pub fn add_endpoint(&mut self, endpoint: EndpointConf) -> &mut Self { + self.endpoints.push(endpoint); + self + } + + // move CompatibleDnsConf::DnsMode into CompatibleDnsConf::DnsConf + pub fn resolve_dns_conf(&mut self) -> &mut Self { + if let CompatibleDnsConf::DnsMode(mode) = self.dns { + let conf = DnsConf { + mode, + ..Default::default() + }; + self.dns = CompatibleDnsConf::DnsConf(conf); + } + self + } + + pub fn apply_global_opts(&mut self, opts: GlobalOpts) -> &mut Self { + let GlobalOpts { + log_level, + log_output, + dns_mode, + dns_protocol, + dns_servers, + } = opts; + + macro_rules! reset { + ($res: expr, $field: ident) => { + if let Some($field) = $field { + $res = $field + } + }; + } + reset!(self.log.level, log_level); + reset!(self.log.output, log_output); + reset!(self.dns.as_mut().mode, dns_mode); + reset!(self.dns.as_mut().protocol, dns_protocol); + reset!(self.dns.as_mut().nameservers, dns_servers); + + self + } } diff --git a/src/main.rs b/src/main.rs index b7b40f40..bc738bf4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,19 +24,29 @@ cfg_if! { } fn main() { - match cmd::scan() { - CmdInput::Endpoint(ep) => execute(vec![ep]), - CmdInput::Config(conf) => start_from_conf(conf), - CmdInput::None => {} - } + let conf = match cmd::scan() { + CmdInput::Endpoint(ep, opts) => { + let mut conf = FullConf::default(); + conf.add_endpoint(ep).apply_global_opts(opts); + conf + } + CmdInput::Config(conf, opts) => { + let mut conf = FullConf::from_config_file(&conf); + conf.resolve_dns_conf().apply_global_opts(opts); + conf + } + CmdInput::None => std::process::exit(0), + }; + + start_from_conf(conf); } -fn start_from_conf(conf: String) { +fn start_from_conf(conf: FullConf) { let FullConf { log: log_conf, dns: dns_conf, endpoints: eps_conf, - } = FullConf::from_config_file(&conf); + } = conf; setup_log(log_conf); setup_dns(dns_conf); @@ -78,12 +88,12 @@ fn setup_log(conf: conf::LogConf) { } #[allow(unused_variables)] -fn setup_dns(dns: conf::CompatibeDnsConf) { +fn setup_dns(dns: conf::CompatibleDnsConf) { #[cfg(feature = "trust-dns")] { - use conf::CompatibeDnsConf::*; + use conf::CompatibleDnsConf::*; match dns { - Dns(conf) => { + DnsConf(conf) => { let (conf, opts) = conf.into(); dns::configure(Some(conf), Some(opts)); } From 7f6c9b562917104ab567737128bf8e754502d74e Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 28 Nov 2021 18:01:28 +0900 Subject: [PATCH 080/169] move dns conf when necessary --- src/conf/dns.rs | 2 +- src/conf/log.rs | 2 +- src/conf/mod.rs | 11 ++++++++++- src/main.rs | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/conf/dns.rs b/src/conf/dns.rs index e9af7813..7123a68c 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -23,7 +23,7 @@ pub enum DnsMode { impl Default for DnsMode { fn default() -> Self { - Self::Ipv4ThenIpv6 + Self::Ipv4AndIpv6 } } diff --git a/src/conf/log.rs b/src/conf/log.rs index 222e0781..1f1c39b9 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -43,7 +43,7 @@ impl From for LevelFilter { impl Default for LogLevel { fn default() -> Self { - LogLevel::Info + LogLevel::Off } } diff --git a/src/conf/mod.rs b/src/conf/mod.rs index d1767021..15e4b35c 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -58,7 +58,11 @@ impl FullConf { } // move CompatibleDnsConf::DnsMode into CompatibleDnsConf::DnsConf - pub fn resolve_dns_conf(&mut self) -> &mut Self { + pub fn move_dns_conf(&mut self) -> &mut Self { + if let CompatibleDnsConf::None = self.dns { + let conf = DnsConf::default(); + self.dns = CompatibleDnsConf::DnsConf(conf); + } if let CompatibleDnsConf::DnsMode(mode) = self.dns { let conf = DnsConf { mode, @@ -78,6 +82,11 @@ impl FullConf { dns_servers, } = opts; + if dns_mode.is_some() || dns_protocol.is_some() || dns_servers.is_some() + { + self.move_dns_conf(); + } + macro_rules! reset { ($res: expr, $field: ident) => { if let Some($field) = $field { diff --git a/src/main.rs b/src/main.rs index bc738bf4..99ce35da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -32,7 +32,7 @@ fn main() { } CmdInput::Config(conf, opts) => { let mut conf = FullConf::from_config_file(&conf); - conf.resolve_dns_conf().apply_global_opts(opts); + conf.apply_global_opts(opts); conf } CmdInput::None => std::process::exit(0), From ca4a99d48e9d06338b06063d1397ec1b87ba581d Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 28 Nov 2021 18:45:59 +0900 Subject: [PATCH 081/169] display log and dns message when start up --- src/conf/dns.rs | 61 +++++++++++++++++++++++++++++++++++++++++++++++++ src/conf/log.rs | 22 ++++++++++++++++++ src/main.rs | 6 ++++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/src/conf/dns.rs b/src/conf/dns.rs index 7123a68c..2d14ad83 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -1,4 +1,5 @@ use cfg_if::cfg_if; +use std::fmt::{Formatter, Display}; use serde::{Serialize, Deserialize}; cfg_if! { @@ -27,6 +28,20 @@ impl Default for DnsMode { } } +impl Display for DnsMode { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use DnsMode::*; + let s = match self { + Ipv4Only => "ipv4_only", + Ipv6Only => "ipv6_only", + Ipv4AndIpv6 => "ipv4_and_ipv6", + Ipv4ThenIpv6 => "ipv4_then_ipv6", + Ipv6ThenIpv4 => "ipv6_then_ipv4", + }; + write!(f, "{}", s) + } +} + impl From for DnsMode { fn from(s: String) -> Self { use DnsMode::*; @@ -73,6 +88,18 @@ impl Default for DnsProtocol { } } +impl Display for DnsProtocol { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use DnsProtocol::*; + let s = match self { + Tcp => "tcp", + Udp => "udp", + TcpAndUdp => "tcp+udp", + }; + write!(f, "{}", s) + } +} + impl From for DnsProtocol { fn from(s: String) -> Self { use DnsProtocol::*; @@ -109,6 +136,19 @@ pub struct DnsConf { pub nameservers: Vec, } +impl Display for DnsConf { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let servers = if self.nameservers.is_empty() { + String::from("system") + } else { + self.nameservers.join(", ") + }; + + write!(f, "mode={}, protocol={}, ", self.mode, self.protocol).unwrap(); + write!(f, "servers={}", &servers) + } +} + #[cfg(feature = "trust-dns")] impl From for (ResolverConfig, ResolverOpts) { fn from(conf: DnsConf) -> Self { @@ -184,3 +224,24 @@ impl AsMut for CompatibleDnsConf { } } } + +impl Display for CompatibleDnsConf { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use CompatibleDnsConf::*; + match self { + DnsConf(conf) => write!(f, "{}", conf), + DnsMode(mode) => write!( + f, + "{}, protocol={}, servers=system", + mode, + DnsProtocol::default(), + ), + None => write!( + f, + "mode={}, protocol={}, servers=system", + self::DnsMode::default(), + DnsProtocol::default(), + ), + } + } +} diff --git a/src/conf/log.rs b/src/conf/log.rs index 1f1c39b9..553630b6 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -1,3 +1,4 @@ +use std::fmt::{Formatter, Display}; use serde::{Serialize, Deserialize}; use log::LevelFilter; @@ -47,6 +48,21 @@ impl Default for LogLevel { } } +impl Display for LogLevel { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + use LogLevel::*; + let s = match self { + Off => "off", + Error => "error", + Warn => "warn", + Info => "info", + Debug => "debug", + Trace => "trace", + }; + write!(f, "{}", s) + } +} + #[derive(Debug, Serialize, Deserialize)] pub struct LogConf { #[serde(default)] @@ -87,3 +103,9 @@ impl From for (LevelFilter, fern::Output) { (level.into(), output) } } + +impl Display for LogConf { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "level={}, output={}", self.level, &self.output) + } +} diff --git a/src/main.rs b/src/main.rs index 99ce35da..64eb75d4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -55,7 +55,7 @@ fn start_from_conf(conf: FullConf) { .into_iter() .map(|epc| { let ep = epc.build(); - log::info!("inited: {}", &ep); + println!("inited: {}", &ep); ep }) .collect(); @@ -64,6 +64,8 @@ fn start_from_conf(conf: FullConf) { } fn setup_log(conf: conf::LogConf) { + println!("log: {}", &conf); + #[cfg(feature = "x-debug")] env_logger::init(); @@ -89,6 +91,8 @@ fn setup_log(conf: conf::LogConf) { #[allow(unused_variables)] fn setup_dns(dns: conf::CompatibleDnsConf) { + println!("dns: {}", &dns); + #[cfg(feature = "trust-dns")] { use conf::CompatibleDnsConf::*; From 9f3239e163733d278f07dc60d5d5f4c1fdcfd4ff Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 28 Nov 2021 18:48:25 +0900 Subject: [PATCH 082/169] docs --- readme.md | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/readme.md b/readme.md index 7aa4a253..926ed513 100644 --- a/readme.md +++ b/readme.md @@ -35,7 +35,7 @@ cargo build --release --no-default-features --features udp, tfo, zero-copy, trus ## Usage ```shell -Realm 1.5.0-rc5 +Realm 1.5.0-rc6 [udp][zero-copy][trust-dns] A high efficiency relay tool @@ -49,12 +49,21 @@ FLAGS: -d, --daemon run as a unix daemon OPTIONS: + -h, --help show help + -v, --version show version -c, --config use config file -l, --listen listen address -r, --remote remote address -x, --through send through ip or address --tcp-timeout set timeout value for tcp --udp-timeout set timeout value for udp + +GLOBAL OPTIONS: + --log-level override log level + --log-output override log output + --dns-mode override dns mode + --dns-protocol override dns protocol + --dns-servers override dns servers ``` start from command line arguments: @@ -115,9 +124,9 @@ Note: must provide `endpoint.local` and `endpoint.remote` ### log: [level, output] #### log.level -- off +- off *(default)* - error -- info *(default)* +- info - debug - trace @@ -133,9 +142,9 @@ this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lo #### dns.mode - ipv4_only - ipv6_only -- ipv4_then_ipv6 *(default)* +- ipv4_then_ipv6 - ipv6_then_ipv4 -- ipv4_and_ipv6 +- ipv4_and_ipv6 *(default)* #### dns.protocol - tcp From cf414b1988479a0c3279579af7320e6c2d27c27b Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 28 Nov 2021 21:04:11 +0900 Subject: [PATCH 083/169] support toml --- Cargo.lock | 10 ++++++++++ Cargo.toml | 1 + readme.md | 45 ++++++++++++++++++++++++++++++++++----------- src/conf/mod.rs | 14 ++++++++++++-- 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d60a7ac6..cb6078dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -745,6 +745,7 @@ dependencies = [ "serde_json", "tokio", "tokio-tfo", + "toml", "trust-dns-resolver", ] @@ -984,6 +985,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "toml" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" +dependencies = [ + "serde", +] + [[package]] name = "trust-dns-proto" version = "0.20.1" diff --git a/Cargo.toml b/Cargo.toml index a47459cd..a6e765ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,7 @@ futures = "0.3" log = "0.4" clap = "3.0.0-beta.5" +toml = "0.5" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/readme.md b/readme.md index 926ed513..d3d0e9a1 100644 --- a/readme.md +++ b/readme.md @@ -81,9 +81,34 @@ realm -c config.json ``` ## Configuration +TOML Example +```toml +[log] +level = "warn" +output = "/var/log/realm.log" + +[dns_mode] +mode = "ipv4_only" +protocol = "tcp_and_udp" +nameservers = ["8.8.8.8:53", "8.8.4.4:53"] + +[[endpoints]] +local = "0.0.0.0:5000" +remote = "1.1.1.1:443" + +[[endpoints]] +udp = true +fast_open = true +zero_copy = false +tcp_timeout = 300 +udp_timeout = 30 +local = "0.0.0.0:10000" +remote = "www.google.com:443" +through = "0.0.0.0" +```
-Example +JSON Example
 {
 	"log": {
@@ -92,7 +117,7 @@ realm -c config.json
 	},
 	"dns_mode": {
 		"mode": "ipv4_only",
-		"protocol": "tcp+udp",
+		"protocol": "tcp_and_udp",
 		"nameservers": ["8.8.8.8:53", "8.8.4.4:53"]
 	},
 	"endpoints": [
@@ -101,16 +126,14 @@ realm -c config.json
 			"remote": "1.1.1.1:443"
 		},
 		{
-			"local": "0.0.0.0:10000",
-			"remote": "www.google.com:443",
 			"udp": true,
 			"fast_open": true,
-			"zero_copy": true
-		},
-		{
-			"local": "0.0.0.0:15000",
-			"remote": "www.microsoft.com:443",
-			"through": "127.0.0.1"
+			"zero_copy": true,
+			"tcp_timeout": 300,
+			"udp_timeout": 30,
+			"local": "0.0.0.0:10000",
+			"remote": "www.google.com:443",
+			"through": "0.0.0.0"
 		}
 	]
 }
@@ -149,7 +172,7 @@ this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lo
 #### dns.protocol
 - tcp
 - udp
-- tcp+udp *(default)*
+- tcp_and_udp *(default)*
 
 #### dns.nameservers
 format: ["server1", "server2" ...]
diff --git a/src/conf/mod.rs b/src/conf/mod.rs
index 15e4b35c..2c811dc1 100644
--- a/src/conf/mod.rs
+++ b/src/conf/mod.rs
@@ -48,8 +48,18 @@ impl FullConf {
     pub fn from_config_file(file: &str) -> Self {
         let config = fs::read_to_string(file)
             .unwrap_or_else(|e| panic!("unable to open {}: {}", file, &e));
-        serde_json::from_str(&config)
-            .unwrap_or_else(|e| panic!("unable to parse {}: {}", file, &e))
+        let toml_err = match toml::from_str(&config) {
+            Ok(x) => return x,
+            Err(e) => e,
+        };
+        let json_err = match serde_json::from_str(&config) {
+            Ok(x) => return x,
+            Err(e) => e,
+        };
+        panic!(
+            "parse {0} as toml: {1}; parse {0} as json: {2}",
+            file, &toml_err, &json_err
+        );
     }
 
     pub fn add_endpoint(&mut self, endpoint: EndpointConf) -> &mut Self {

From 04a3e5f1e1c979cba61eeeb787b8b834dc49f5b6 Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Mon, 29 Nov 2021 21:19:58 +0900
Subject: [PATCH 084/169] typo

---
 readme.md      | 2 +-
 src/cmd/mod.rs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/readme.md b/readme.md
index d3d0e9a1..740b1be8 100644
--- a/readme.md
+++ b/readme.md
@@ -63,7 +63,7 @@ GLOBAL OPTIONS:
         --log-output           override log output
         --dns-mode             override dns mode
         --dns-protocol     override dns protocol
-        --dns-servers      override dns servers
+        --dns-servers      override dns servers
 ```
 
 start from command line arguments:
diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs
index ec805f91..43dcedf2 100644
--- a/src/cmd/mod.rs
+++ b/src/cmd/mod.rs
@@ -124,7 +124,7 @@ fn add_global_options(app: App) -> App {
         Arg::new("dns_servers")
             .long("dns-servers")
             .about("override dns servers")
-            .value_name("protocol")
+            .value_name("servers")
             .takes_value(true)
             .display_order(4),
     ])

From 4b26247615d34a39ee209134e05bc8320bdd3620 Mon Sep 17 00:00:00 2001
From: zephyr <51367850+zephyrchien@users.noreply.github.com>
Date: Mon, 29 Nov 2021 21:21:36 +0900
Subject: [PATCH 085/169] indent

---
 readme.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/readme.md b/readme.md
index 740b1be8..a3c7449a 100644
--- a/readme.md
+++ b/readme.md
@@ -63,7 +63,7 @@ GLOBAL OPTIONS:
         --log-output           override log output
         --dns-mode             override dns mode
         --dns-protocol     override dns protocol
-        --dns-servers      override dns servers
+        --dns-servers       override dns servers
 ```
 
 start from command line arguments:

From 04432fc7647891475623f893685c972bb9c4796d Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Fri, 3 Dec 2021 16:00:15 +0900
Subject: [PATCH 086/169] remove env logger

---
 Cargo.toml  |  2 --
 src/main.rs | 36 +++++++++++++++---------------------
 2 files changed, 15 insertions(+), 23 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index a6e765ed..b6837ecd 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -30,7 +30,6 @@ libc = { version = "0.2", optional = true }
 # logger
 chrono = "0.4"
 fern = "0.6"
-env_logger = { version = "0.9", optional = true }
 
 # malloc
 mimalloc = { version = "0.1", optional = true }
@@ -58,7 +57,6 @@ zero-copy = ["libc"]
 trust-dns = ["trust-dns-resolver"]
 jemalloc = ["jemallocator"]
 mi-malloc = ["mimalloc"]
-x-debug = ["env_logger"]
 
 [dev-dependencies]
 env_logger = "0.9"
diff --git a/src/main.rs b/src/main.rs
index 64eb75d4..16b12772 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -66,27 +66,21 @@ fn start_from_conf(conf: FullConf) {
 fn setup_log(conf: conf::LogConf) {
     println!("log: {}", &conf);
 
-    #[cfg(feature = "x-debug")]
-    env_logger::init();
-
-    #[cfg(not(feature = "x-debug"))]
-    {
-        let (level, output) = conf.into();
-        fern::Dispatch::new()
-            .format(|out, message, record| {
-                out.finish(format_args!(
-                    "{}[{}][{}] {}",
-                    chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
-                    record.target(),
-                    record.level(),
-                    message
-                ))
-            })
-            .level(level)
-            .chain(output)
-            .apply()
-            .unwrap_or_else(|e| panic!("failed to setup logger: {}", &e))
-    }
+    let (level, output) = conf.into();
+    fern::Dispatch::new()
+        .format(|out, message, record| {
+            out.finish(format_args!(
+                "{}[{}][{}] {}",
+                chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"),
+                record.target(),
+                record.level(),
+                message
+            ))
+        })
+        .level(level)
+        .chain(output)
+        .apply()
+        .unwrap_or_else(|e| panic!("failed to setup logger: {}", &e))
 }
 
 #[allow(unused_variables)]

From 50e451fa436c2b98bcfec228ea13d4692d97a910 Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Fri, 3 Dec 2021 16:12:57 +0900
Subject: [PATCH 087/169] rename dns field

---
 readme.md       | 8 ++++----
 src/conf/mod.rs | 2 +-
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/readme.md b/readme.md
index a3c7449a..a8626973 100644
--- a/readme.md
+++ b/readme.md
@@ -87,7 +87,7 @@ TOML Example
 level = "warn"
 output = "/var/log/realm.log"
 
-[dns_mode]
+[dns]
 mode = "ipv4_only"
 protocol = "tcp_and_udp"
 nameservers = ["8.8.8.8:53", "8.8.4.4:53"]
@@ -115,7 +115,7 @@ through = "0.0.0.0"
 		"level": "warn",
 		"output": "/var/log/realm.log"
 	},
-	"dns_mode": {
+	"dns": {
 		"mode": "ipv4_only",
 		"protocol": "tcp_and_udp",
 		"nameservers": ["8.8.8.8:53", "8.8.4.4:53"]
@@ -160,7 +160,7 @@ Note: must provide `endpoint.local` and `endpoint.remote`
 
 ---
 ### dns: [mode, protocol, nameservers]
-this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lookup priority with `"dns_mode": "ipv4_only"`, which is equal to `"dns_mode": {"mode": "ipv4_only"}`
+~~this is compatibe with old versions(before `v1.5.0-rc3`),~~ you could still set lookup strategy with `"dns": "ipv4_only"`, which is equal to `"dns": {"mode": "ipv4_only"}`
 
 #### dns.mode
 - ipv4_only
@@ -178,7 +178,7 @@ this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lo
 format: ["server1", "server2" ...]
 
 default:
-On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). Otherwise use google's public dns as default upstream resolver(`8.8.8.8`, `8.8.4.4` and `2001:4860:4860::8888`, `2001:4860:4860::8844`).
+On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). Otherwise use google's public dns as default upstream resolver(`8.8.8.8:53`, `8.8.4.4:53` and `2001:4860:4860::8888:53`, `2001:4860:4860::8844:53`).
 
 ---
 ### endpoint objects
diff --git a/src/conf/mod.rs b/src/conf/mod.rs
index 2c811dc1..76cdd387 100644
--- a/src/conf/mod.rs
+++ b/src/conf/mod.rs
@@ -25,7 +25,7 @@ pub struct FullConf {
     #[serde(default)]
     pub log: LogConf,
 
-    #[serde(default, rename = "dns_mode")]
+    #[serde(default)]
     pub dns: CompatibleDnsConf,
 
     pub endpoints: Vec,

From 59cb6ecdbced5aa265509f0fd2b9e45c0c8c62b1 Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Fri, 3 Dec 2021 16:43:38 +0900
Subject: [PATCH 088/169] parse endpoint in conf module

---
 src/conf/endpoint.rs | 73 +++++++++++++++++++++++++++++++++++++-------
 src/utils/types.rs   | 54 +++-----------------------------
 2 files changed, 67 insertions(+), 60 deletions(-)

diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs
index 765c08e7..8a539196 100644
--- a/src/conf/endpoint.rs
+++ b/src/conf/endpoint.rs
@@ -1,5 +1,6 @@
 use serde::{Serialize, Deserialize};
-use crate::utils::Endpoint;
+use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
+use crate::utils::{Endpoint, RemoteAddr, ConnectOpts};
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct EndpointConf {
@@ -35,16 +36,66 @@ const fn udp_timeout() -> usize {
 }
 
 impl EndpointConf {
+    fn build_local(&self) -> SocketAddr {
+        self.local
+            .to_socket_addrs()
+            .expect("invalid local address")
+            .next()
+            .unwrap()
+    }
+
+    fn build_remote(&self) -> RemoteAddr {
+        let Self { remote, .. } = self;
+        if let Ok(sockaddr) = remote.parse::() {
+            RemoteAddr::SocketAddr(sockaddr)
+        } else {
+            let mut iter = remote.rsplitn(2, ':');
+            let port = iter.next().unwrap().parse::().unwrap();
+            let addr = iter.next().unwrap().to_string();
+            // test addr
+            let _ = crate::dns::resolve_sync(&addr, 0).unwrap();
+            RemoteAddr::DomainName(addr, port)
+        }
+    }
+
+    fn build_send_through(&self) -> Option {
+        let Self { through, .. } = self;
+        match through.to_socket_addrs() {
+            Ok(mut x) => Some(x.next().unwrap()),
+            Err(_) => {
+                let mut ipstr = String::from(through);
+                ipstr.retain(|c| c != '[' && c != ']');
+                ipstr
+                    .parse::()
+                    .map_or(None, |ip| Some(SocketAddr::new(ip, 0)))
+            }
+        }
+    }
+
+    fn build_conn_opts(&self) -> ConnectOpts {
+        let Self {
+            udp,
+            fast_open,
+            zero_copy,
+            tcp_timeout,
+            udp_timeout,
+            ..
+        } = *self;
+
+        ConnectOpts {
+            use_udp: udp,
+            fast_open,
+            zero_copy,
+            tcp_timeout,
+            udp_timeout,
+            send_through: self.build_send_through(),
+        }
+    }
+
     pub fn build(&self) -> Endpoint {
-        Endpoint::new(
-            &self.local,
-            &self.remote,
-            &self.through,
-            self.udp,
-            self.fast_open,
-            self.zero_copy,
-            self.tcp_timeout,
-            self.udp_timeout,
-        )
+        let local = self.build_local();
+        let remote = self.build_remote();
+        let opts = self.build_conn_opts();
+        Endpoint::new(local, remote, opts)
     }
 }
diff --git a/src/utils/types.rs b/src/utils/types.rs
index 54e3d569..ef5ee00c 100644
--- a/src/utils/types.rs
+++ b/src/utils/types.rs
@@ -1,6 +1,6 @@
 use std::io::Result;
 use std::fmt::{Formatter, Display};
-use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
+use std::net::SocketAddr;
 
 use crate::dns;
 
@@ -67,59 +67,15 @@ impl RemoteAddr {
 }
 
 impl Endpoint {
-    #[allow(clippy::too_many_arguments)]
     pub fn new(
-        local: &str,
-        remote: &str,
-        through: &str,
-        use_udp: bool,
-        fast_open: bool,
-        zero_copy: bool,
-        tcp_timeout: usize,
-        udp_timeout: usize,
+        local: SocketAddr,
+        remote: RemoteAddr,
+        opts: ConnectOpts,
     ) -> Self {
-        // check local addr
-        let local = local
-            .to_socket_addrs()
-            .expect("invalid local address")
-            .next()
-            .unwrap();
-
-        // check remote addr
-        let remote = if let Ok(sockaddr) = remote.parse::() {
-            RemoteAddr::SocketAddr(sockaddr)
-        } else {
-            let mut iter = remote.rsplitn(2, ':');
-            let port = iter.next().unwrap().parse::().unwrap();
-            let addr = iter.next().unwrap().to_string();
-            // test addr
-            let _ = dns::resolve_sync(&addr, 0).unwrap();
-            RemoteAddr::DomainName(addr, port)
-        };
-
-        // check bind addr
-        let through = match through.to_socket_addrs() {
-            Ok(mut x) => Some(x.next().unwrap()),
-            Err(_) => {
-                let mut ipstr = String::from(through);
-                ipstr.retain(|c| c != '[' && c != ']');
-                ipstr
-                    .parse::()
-                    .map_or(None, |ip| Some(SocketAddr::new(ip, 0)))
-            }
-        };
-
         Endpoint {
             local,
             remote,
-            opts: ConnectOpts {
-                use_udp,
-                fast_open,
-                zero_copy,
-                tcp_timeout,
-                udp_timeout,
-                send_through: through,
-            },
+            opts,
         }
     }
 }

From 156812d01432225f2f42f1bc0ca4d728c4cee861 Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Fri, 3 Dec 2021 16:58:54 +0900
Subject: [PATCH 089/169] make multi-thread optional

---
 Cargo.toml          |  5 +++--
 readme.md           |  1 +
 src/main.rs         | 22 +++++++++++++++++-----
 src/utils/consts.rs |  8 ++++++++
 src/utils/types.rs  |  3 ---
 5 files changed, 29 insertions(+), 10 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index b6837ecd..b3e557a7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,7 +16,7 @@ toml = "0.5"
 serde = { version = "1", features = ["derive"] }
 serde_json = "1"
 
-tokio = { version = "1", features = ["rt", "rt-multi-thread", "io-util", "net", "time"] }
+tokio = { version = "1", features = ["rt", "io-util", "net", "time"] }
 trust-dns-resolver = { version = "0.20", optional = true }
 
 lazy_static = "1"
@@ -50,11 +50,12 @@ panic = "abort"
 opt-level = 0
 
 [features]
-default = ["udp", "zero-copy", "trust-dns"]
+default = ["udp", "zero-copy", "trust-dns", "multi-thread" ]
 udp = []
 tfo = ["tokio-tfo"]
 zero-copy = ["libc"]
 trust-dns = ["trust-dns-resolver"]
+multi-thread = ["tokio/rt-multi-thread"]
 jemalloc = ["jemallocator"]
 mi-malloc = ["mimalloc"]
 
diff --git a/readme.md b/readme.md
index a8626973..d8677ffe 100644
--- a/readme.md
+++ b/readme.md
@@ -19,6 +19,7 @@ available Options:
 - udp *(enabled by default)*
 - trust-dns *(enabled by default)*
 - zero-copy *(enabled on linux)*
+- multi-thread *(enabled by default)*
 - tfo
 - mi-malloc
 - jemalloc
diff --git a/src/main.rs b/src/main.rs
index 16b12772..323d86af 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -103,9 +103,21 @@ fn setup_dns(dns: conf::CompatibleDnsConf) {
 }
 
 fn execute(eps: Vec) {
-    tokio::runtime::Builder::new_multi_thread()
-        .enable_all()
-        .build()
-        .unwrap()
-        .block_on(relay::run(eps))
+    #[cfg(feature = "multi-thread")]
+    {
+        tokio::runtime::Builder::new_multi_thread()
+            .enable_all()
+            .build()
+            .unwrap()
+            .block_on(relay::run(eps))
+    }
+
+    #[cfg(not(feature = "multi-thread"))]
+    {
+        tokio::runtime::Builder::new_current_thread()
+            .enable_all()
+            .build()
+            .unwrap()
+            .block_on(relay::run(eps))
+    }
 }
diff --git a/src/utils/consts.rs b/src/utils/consts.rs
index 38ff5155..88253b1f 100644
--- a/src/utils/consts.rs
+++ b/src/utils/consts.rs
@@ -1,5 +1,9 @@
 use std::fmt::{Display, Formatter};
 
+// default timeout
+pub const TCP_TIMEOUT: usize = 300;
+pub const UDP_TIMEOUT: usize = 30;
+
 // https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1
 pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") {
     512
@@ -24,6 +28,7 @@ def_feat!(FEATURE_ZERO_COPY, "zero-copy");
 def_feat!(FEATURE_TRUST_DNS, "trust-dns");
 def_feat!(FEATURE_MIMALLOC, "mi-malloc");
 def_feat!(FEATURE_JEMALLOC, "jemalloc");
+def_feat!(FEATURE_MULTI_THREAD, "multi-thread");
 
 pub struct Features {
     pub udp: bool,
@@ -32,6 +37,7 @@ pub struct Features {
     pub trust_dns: bool,
     pub mimalloc: bool,
     pub jemalloc: bool,
+    pub multi_thread: bool,
 }
 
 pub const FEATURES: Features = Features {
@@ -41,6 +47,7 @@ pub const FEATURES: Features = Features {
     trust_dns: FEATURE_TRUST_DNS,
     mimalloc: FEATURE_MIMALLOC,
     jemalloc: FEATURE_JEMALLOC,
+    multi_thread: FEATURE_MULTI_THREAD,
 };
 
 impl Display for Features {
@@ -56,6 +63,7 @@ impl Display for Features {
         disp_feat!(tfo, "tfo");
         disp_feat!(zero_copy, "zero-copy");
         disp_feat!(trust_dns, "trust-dns");
+        disp_feat!(multi_thread, "multi-thread");
         disp_feat!(mimalloc, "mimalloc");
         disp_feat!(jemalloc, "jemalloc");
         Ok(())
diff --git a/src/utils/types.rs b/src/utils/types.rs
index ef5ee00c..b98296c9 100644
--- a/src/utils/types.rs
+++ b/src/utils/types.rs
@@ -4,9 +4,6 @@ use std::net::SocketAddr;
 
 use crate::dns;
 
-pub const TCP_TIMEOUT: usize = 300;
-pub const UDP_TIMEOUT: usize = 30;
-
 #[derive(Clone)]
 pub enum RemoteAddr {
     SocketAddr(SocketAddr),

From 5e5b65262f7970dba6ecfe5688d43c02fedd9464 Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Fri, 3 Dec 2021 18:57:46 +0900
Subject: [PATCH 090/169] read config from env var

---
 readme.md       | 20 ++++++++++++++------
 src/conf/mod.rs | 32 ++++++++++++++++++++++----------
 src/main.rs     | 35 +++++++++++++++++++++++------------
 3 files changed, 59 insertions(+), 28 deletions(-)

diff --git a/readme.md b/readme.md
index d8677ffe..6541635a 100644
--- a/readme.md
+++ b/readme.md
@@ -69,18 +69,26 @@ GLOBAL OPTIONS:
 
 start from command line arguments:
 ```shell
-# enable udp
-realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --udp
-
-# specify outbound ip
-realm -l 127.0.0.1:5000 -r 1.1.1.1:443 --through 127.0.0.1
+realm -l 0.0.0.0:5000 -r 1.1.1.1:443
 ```
 
-or use a config file:
+start from config file:
 ```shell
+# use toml
+realm -c config.toml
+
+# use json
 realm -c config.json
 ```
 
+start from environment variable:
+```shell
+CONFIG='{"endpoints":[{"local":"127.0.0.1:5000","remote":"1.1.1.1:443"}]}' realm
+
+export CONFIG=`cat config.json | jq -c `
+realm
+```
+
 ## Configuration
 TOML Example
 ```toml
diff --git a/src/conf/mod.rs b/src/conf/mod.rs
index 76cdd387..9976ed41 100644
--- a/src/conf/mod.rs
+++ b/src/conf/mod.rs
@@ -1,4 +1,5 @@
 use std::fs;
+use std::io::{Result, Error, ErrorKind};
 
 use serde::{Serialize, Deserialize};
 
@@ -45,21 +46,32 @@ impl FullConf {
         }
     }
 
-    pub fn from_config_file(file: &str) -> Self {
-        let config = fs::read_to_string(file)
+    pub fn from_conf_file(file: &str) -> Self {
+        let conf = fs::read_to_string(file)
             .unwrap_or_else(|e| panic!("unable to open {}: {}", file, &e));
-        let toml_err = match toml::from_str(&config) {
-            Ok(x) => return x,
+        match Self::from_conf_str(&conf) {
+            Ok(x) => x,
+            Err(e) => panic!("failed to parse {}: {}", file, &e),
+        }
+    }
+
+    pub fn from_conf_str(conf: &str) -> Result {
+        let toml_err = match toml::from_str(conf) {
+            Ok(x) => return Ok(x),
             Err(e) => e,
         };
-        let json_err = match serde_json::from_str(&config) {
-            Ok(x) => return x,
+        let json_err = match serde_json::from_str(conf) {
+            Ok(x) => return Ok(x),
             Err(e) => e,
         };
-        panic!(
-            "parse {0} as toml: {1}; parse {0} as json: {2}",
-            file, &toml_err, &json_err
-        );
+
+        Err(Error::new(
+            ErrorKind::Other,
+            format!(
+                "parse as toml: {0}; parse as json: {1}",
+                &toml_err, &json_err
+            ),
+        ))
     }
 
     pub fn add_endpoint(&mut self, endpoint: EndpointConf) -> &mut Self {
diff --git a/src/main.rs b/src/main.rs
index 323d86af..824f8079 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -4,12 +4,15 @@ mod conf;
 mod utils;
 mod relay;
 
+use std::env;
+
 use cfg_if::cfg_if;
 use cmd::CmdInput;
 use conf::FullConf;
 use utils::Endpoint;
 
 const VERSION: &str = "1.5.0-rc6";
+const ENV_CONFIG: &str = "CONFIG";
 
 cfg_if! {
     if #[cfg(all(feature = "mi-malloc"))] {
@@ -24,21 +27,29 @@ cfg_if! {
 }
 
 fn main() {
-    let conf = match cmd::scan() {
-        CmdInput::Endpoint(ep, opts) => {
-            let mut conf = FullConf::default();
-            conf.add_endpoint(ep).apply_global_opts(opts);
-            conf
-        }
-        CmdInput::Config(conf, opts) => {
-            let mut conf = FullConf::from_config_file(&conf);
-            conf.apply_global_opts(opts);
-            conf
+    let conf = || {
+        if let Ok(conf_str) = env::var(ENV_CONFIG) {
+            if let Ok(conf) = FullConf::from_conf_str(&conf_str) {
+                return conf;
+            }
+        };
+
+        match cmd::scan() {
+            CmdInput::Endpoint(ep, opts) => {
+                let mut conf = FullConf::default();
+                conf.add_endpoint(ep).apply_global_opts(opts);
+                conf
+            }
+            CmdInput::Config(conf, opts) => {
+                let mut conf = FullConf::from_conf_file(&conf);
+                conf.apply_global_opts(opts);
+                conf
+            }
+            CmdInput::None => std::process::exit(0),
         }
-        CmdInput::None => std::process::exit(0),
     };
 
-    start_from_conf(conf);
+    start_from_conf(conf());
 }
 
 fn start_from_conf(conf: FullConf) {

From 70681e0cb31be4ad3a1e7e0b5673d5fed6a3919b Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Wed, 8 Dec 2021 10:05:18 +0900
Subject: [PATCH 091/169] fix default log level conflict

---
 src/conf/log.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/conf/log.rs b/src/conf/log.rs
index 553630b6..bf6c4a00 100644
--- a/src/conf/log.rs
+++ b/src/conf/log.rs
@@ -78,7 +78,7 @@ fn output() -> String {
 impl Default for LogConf {
     fn default() -> Self {
         Self {
-            level: LogLevel::Info,
+            level: Default::default(),
             output: output(),
         }
     }

From e42634daf20a1c5056b5d30412b56d4926d00736 Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Wed, 8 Dec 2021 10:51:40 +0900
Subject: [PATCH 092/169] remove compatible dns config

---
 readme.md       |  2 +-
 src/conf/dns.rs | 54 -------------------------------------------------
 src/conf/mod.rs | 34 +++++++------------------------
 src/main.rs     | 25 +++++++++--------------
 4 files changed, 17 insertions(+), 98 deletions(-)

diff --git a/readme.md b/readme.md
index 6541635a..f65f085b 100644
--- a/readme.md
+++ b/readme.md
@@ -169,7 +169,7 @@ Note: must provide `endpoint.local` and `endpoint.remote`
 
 ---
 ### dns: [mode, protocol, nameservers]
-~~this is compatibe with old versions(before `v1.5.0-rc3`),~~ you could still set lookup strategy with `"dns": "ipv4_only"`, which is equal to `"dns": {"mode": "ipv4_only"}`
+~~this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lookup strategy with `"dns": "ipv4_only"`, which is equal to `"dns": {"mode": "ipv4_only"}`~~ You must use `dns.mode` instead of `dns_mode`
 
 #### dns.mode
 - ipv4_only
diff --git a/src/conf/dns.rs b/src/conf/dns.rs
index 2d14ad83..f240efbc 100644
--- a/src/conf/dns.rs
+++ b/src/conf/dns.rs
@@ -191,57 +191,3 @@ impl From for (ResolverConfig, ResolverOpts) {
         (conf, opts)
     }
 }
-
-// compatible dns config
-#[derive(Debug, Serialize, Deserialize)]
-#[serde(untagged, rename_all = "snake_case")]
-pub enum CompatibleDnsConf {
-    DnsConf(DnsConf),
-    DnsMode(DnsMode),
-    None,
-}
-
-impl Default for CompatibleDnsConf {
-    fn default() -> Self {
-        Self::None
-    }
-}
-
-impl AsRef for CompatibleDnsConf {
-    fn as_ref(&self) -> &DnsConf {
-        match self {
-            CompatibleDnsConf::DnsConf(x) => x,
-            _ => unreachable!(),
-        }
-    }
-}
-
-impl AsMut for CompatibleDnsConf {
-    fn as_mut(&mut self) -> &mut DnsConf {
-        match self {
-            CompatibleDnsConf::DnsConf(x) => x,
-            _ => unreachable!(),
-        }
-    }
-}
-
-impl Display for CompatibleDnsConf {
-    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        use CompatibleDnsConf::*;
-        match self {
-            DnsConf(conf) => write!(f, "{}", conf),
-            DnsMode(mode) => write!(
-                f,
-                "{}, protocol={}, servers=system",
-                mode,
-                DnsProtocol::default(),
-            ),
-            None => write!(
-                f,
-                "mode={}, protocol={}, servers=system",
-                self::DnsMode::default(),
-                DnsProtocol::default(),
-            ),
-        }
-    }
-}
diff --git a/src/conf/mod.rs b/src/conf/mod.rs
index 9976ed41..3ad960be 100644
--- a/src/conf/mod.rs
+++ b/src/conf/mod.rs
@@ -7,7 +7,7 @@ mod log;
 pub use self::log::{LogLevel, LogConf};
 
 mod dns;
-pub use dns::{DnsMode, DnsProtocol, DnsConf, CompatibleDnsConf};
+pub use dns::{DnsMode, DnsProtocol, DnsConf};
 
 mod endpoint;
 pub use endpoint::EndpointConf;
@@ -27,7 +27,7 @@ pub struct FullConf {
     pub log: LogConf,
 
     #[serde(default)]
-    pub dns: CompatibleDnsConf,
+    pub dns: DnsConf,
 
     pub endpoints: Vec,
 }
@@ -41,7 +41,7 @@ impl FullConf {
     ) -> Self {
         FullConf {
             log,
-            dns: CompatibleDnsConf::DnsConf(dns),
+            dns,
             endpoints,
         }
     }
@@ -79,22 +79,6 @@ impl FullConf {
         self
     }
 
-    // move CompatibleDnsConf::DnsMode into CompatibleDnsConf::DnsConf
-    pub fn move_dns_conf(&mut self) -> &mut Self {
-        if let CompatibleDnsConf::None = self.dns {
-            let conf = DnsConf::default();
-            self.dns = CompatibleDnsConf::DnsConf(conf);
-        }
-        if let CompatibleDnsConf::DnsMode(mode) = self.dns {
-            let conf = DnsConf {
-                mode,
-                ..Default::default()
-            };
-            self.dns = CompatibleDnsConf::DnsConf(conf);
-        }
-        self
-    }
-
     pub fn apply_global_opts(&mut self, opts: GlobalOpts) -> &mut Self {
         let GlobalOpts {
             log_level,
@@ -104,11 +88,6 @@ impl FullConf {
             dns_servers,
         } = opts;
 
-        if dns_mode.is_some() || dns_protocol.is_some() || dns_servers.is_some()
-        {
-            self.move_dns_conf();
-        }
-
         macro_rules! reset {
             ($res: expr, $field: ident) => {
                 if let Some($field) = $field {
@@ -116,11 +95,12 @@ impl FullConf {
                 }
             };
         }
+
         reset!(self.log.level, log_level);
         reset!(self.log.output, log_output);
-        reset!(self.dns.as_mut().mode, dns_mode);
-        reset!(self.dns.as_mut().protocol, dns_protocol);
-        reset!(self.dns.as_mut().nameservers, dns_servers);
+        reset!(self.dns.mode, dns_mode);
+        reset!(self.dns.protocol, dns_protocol);
+        reset!(self.dns.nameservers, dns_servers);
 
         self
     }
diff --git a/src/main.rs b/src/main.rs
index 824f8079..421ab81a 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,7 +8,7 @@ use std::env;
 
 use cfg_if::cfg_if;
 use cmd::CmdInput;
-use conf::FullConf;
+use conf::{FullConf, LogConf, DnsConf};
 use utils::Endpoint;
 
 const VERSION: &str = "1.5.0-rc6";
@@ -52,12 +52,12 @@ fn main() {
     start_from_conf(conf());
 }
 
-fn start_from_conf(conf: FullConf) {
+fn start_from_conf(full: FullConf) {
     let FullConf {
         log: log_conf,
         dns: dns_conf,
         endpoints: eps_conf,
-    } = conf;
+    } = full;
 
     setup_log(log_conf);
     setup_dns(dns_conf);
@@ -74,10 +74,10 @@ fn start_from_conf(conf: FullConf) {
     execute(eps);
 }
 
-fn setup_log(conf: conf::LogConf) {
-    println!("log: {}", &conf);
+fn setup_log(log: LogConf) {
+    println!("log: {}", &log);
 
-    let (level, output) = conf.into();
+    let (level, output) = log.into();
     fern::Dispatch::new()
         .format(|out, message, record| {
             out.finish(format_args!(
@@ -95,20 +95,13 @@ fn setup_log(conf: conf::LogConf) {
 }
 
 #[allow(unused_variables)]
-fn setup_dns(dns: conf::CompatibleDnsConf) {
+fn setup_dns(dns: DnsConf) {
     println!("dns: {}", &dns);
 
     #[cfg(feature = "trust-dns")]
     {
-        use conf::CompatibleDnsConf::*;
-        match dns {
-            DnsConf(conf) => {
-                let (conf, opts) = conf.into();
-                dns::configure(Some(conf), Some(opts));
-            }
-            DnsMode(mode) => dns::configure(Option::None, Some(mode.into())),
-            None => (),
-        }
+        let (conf, opts) = dns.into();
+        dns::configure(Some(conf), Some(opts));
         dns::build();
     }
 }

From 918ef1300fc4582441a7f0b2b40072740df11d58 Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Wed, 8 Dec 2021 21:31:03 +0900
Subject: [PATCH 093/169] improve config parser

---
 src/conf/dns.rs     | 82 ++++++++++++++++++++++++++++-----------------
 src/conf/log.rs     | 38 ++++++++++-----------
 src/conf/mod.rs     | 35 ++++++++++++-------
 src/main.rs         | 12 +++----
 src/utils/consts.rs |  3 ++
 5 files changed, 103 insertions(+), 67 deletions(-)

diff --git a/src/conf/dns.rs b/src/conf/dns.rs
index f240efbc..4570b44c 100644
--- a/src/conf/dns.rs
+++ b/src/conf/dns.rs
@@ -1,6 +1,7 @@
 use cfg_if::cfg_if;
 use std::fmt::{Formatter, Display};
 use serde::{Serialize, Deserialize};
+use super::Config;
 
 cfg_if! {
     if #[cfg(feature = "trust-dns")] {
@@ -74,7 +75,7 @@ impl From for ResolverOpts {
 }
 
 // dns protocol
-#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
+#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone, Copy)]
 #[serde(rename_all = "snake_case")]
 pub enum DnsProtocol {
     Tcp,
@@ -124,60 +125,80 @@ impl From for Vec {
 }
 
 // dns config
-#[derive(Debug, Default, Serialize, Deserialize)]
+#[derive(Debug, Default, Serialize, Deserialize, Clone)]
 pub struct DnsConf {
     #[serde(default)]
-    pub mode: DnsMode,
+    pub mode: Option,
 
     #[serde(default)]
-    pub protocol: DnsProtocol,
+    pub protocol: Option,
 
     #[serde(default)]
-    pub nameservers: Vec,
+    pub nameservers: Option>,
 }
 
 impl Display for DnsConf {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        let servers = if self.nameservers.is_empty() {
-            String::from("system")
-        } else {
-            self.nameservers.join(", ")
+        let DnsConf {
+            mode,
+            protocol,
+            nameservers,
+        } = self;
+
+        let mode = match mode {
+            Some(m) => *m,
+            None => Default::default(),
+        };
+
+        let protocol = match protocol {
+            Some(x) => *x,
+            None => Default::default(),
+        };
+
+        let nameservers = match nameservers {
+            Some(s) => s.join(", "),
+            None => String::from("system"),
         };
 
-        write!(f, "mode={}, protocol={}, ", self.mode, self.protocol).unwrap();
-        write!(f, "servers={}", &servers)
+        write!(f, "mode={}, protocol={}, ", &mode, &protocol).unwrap();
+        write!(f, "servers={}", &nameservers)
     }
 }
 
-#[cfg(feature = "trust-dns")]
-impl From for (ResolverConfig, ResolverOpts) {
-    fn from(conf: DnsConf) -> Self {
+impl Config for DnsConf {
+    type Output = (Option, Option);
+
+    fn resolve(self) -> Self::Output {
         let DnsConf {
             mode,
             protocol,
             nameservers,
-        } = conf;
+        } = self;
 
-        let opts = mode.into();
+        let opts: Option = mode.map(|x| x.into());
 
-        let protocols: Vec = protocol.into();
+        let protocol = protocol.unwrap_or_default();
+        if nameservers.is_none() && (protocol == DnsProtocol::default()) {
+            return (None, opts);
+        }
 
-        let nameservers = if nameservers.is_empty() {
-            use crate::dns::DnsConf as XdnsConf;
-            let XdnsConf { conf, .. } = XdnsConf::default();
-            let mut addrs: Vec =
-                conf.name_servers().iter().map(|x| x.socket_addr).collect();
-            addrs.dedup();
-            addrs
-        } else {
-            nameservers
+        let mut conf = ResolverConfig::new();
+        let protocols: Vec = protocol.into();
+        let nameservers = match nameservers {
+            Some(addrs) => addrs
                 .iter()
                 .map(|x| x.to_socket_addrs().unwrap().next().unwrap())
-                .collect()
+                .collect(),
+            None => {
+                use crate::dns::DnsConf as TrustDnsConf;
+                let TrustDnsConf { conf, .. } = TrustDnsConf::default();
+                let mut addrs: Vec =
+                    conf.name_servers().iter().map(|x| x.socket_addr).collect();
+                addrs.dedup();
+                addrs
+            }
         };
 
-        let mut conf = ResolverConfig::new();
-
         for socket_addr in nameservers {
             for protocol in protocols.clone() {
                 conf.add_name_server(NameServerConfig {
@@ -188,6 +209,7 @@ impl From for (ResolverConfig, ResolverOpts) {
                 });
             }
         }
-        (conf, opts)
+
+        (Some(conf), opts)
     }
 }
diff --git a/src/conf/log.rs b/src/conf/log.rs
index bf6c4a00..e3ab384a 100644
--- a/src/conf/log.rs
+++ b/src/conf/log.rs
@@ -1,6 +1,8 @@
 use std::fmt::{Formatter, Display};
 use serde::{Serialize, Deserialize};
 use log::LevelFilter;
+use super::Config;
+use crate::utils::DEFAULT_LOG_FILE;
 
 #[derive(Debug, Serialize, Deserialize, Clone, Copy)]
 #[serde(rename_all = "snake_case")]
@@ -63,32 +65,25 @@ impl Display for LogLevel {
     }
 }
 
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Default, Debug, Serialize, Deserialize, Clone)]
 pub struct LogConf {
     #[serde(default)]
-    pub level: LogLevel,
-    #[serde(default = "output")]
-    pub output: String,
-}
+    pub level: Option,
 
-fn output() -> String {
-    String::from("stdout")
+    #[serde(default)]
+    pub output: Option,
 }
 
-impl Default for LogConf {
-    fn default() -> Self {
-        Self {
-            level: Default::default(),
-            output: output(),
-        }
-    }
-}
+impl Config for LogConf {
+    type Output = (LevelFilter, fern::Output);
 
-impl From for (LevelFilter, fern::Output) {
-    fn from(conf: LogConf) -> Self {
+    fn resolve(self) -> Self::Output {
         use std::io;
         use std::fs::OpenOptions;
-        let LogConf { level, output } = conf;
+        let LogConf { level, output } = self;
+        let level = level.unwrap_or_default();
+        let output = output.unwrap_or_else(||String::from(DEFAULT_LOG_FILE));
+
         let output: fern::Output = match output.as_str() {
             "stdout" => io::stdout().into(),
             "stderr" => io::stderr().into(),
@@ -100,12 +95,17 @@ impl From for (LevelFilter, fern::Output) {
                 .unwrap_or_else(|e| panic!("failed to open {}: {}", output, &e))
                 .into(),
         };
+
         (level.into(), output)
     }
 }
 
 impl Display for LogConf {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
-        write!(f, "level={}, output={}", self.level, &self.output)
+        let LogConf { level, output } = self.clone();
+        let level = level.unwrap_or_default();
+        let output = output.unwrap_or_default();
+
+        write!(f, "level={}, output={}", level, output)
     }
 }
diff --git a/src/conf/mod.rs b/src/conf/mod.rs
index 3ad960be..4bce30e6 100644
--- a/src/conf/mod.rs
+++ b/src/conf/mod.rs
@@ -12,6 +12,12 @@ pub use dns::{DnsMode, DnsProtocol, DnsConf};
 mod endpoint;
 pub use endpoint::EndpointConf;
 
+pub trait Config {
+    type Output;
+
+    fn resolve(self) -> Self::Output;
+}
+
 #[derive(Debug, Default)]
 pub struct GlobalOpts {
     pub log_level: Option,
@@ -24,10 +30,10 @@ pub struct GlobalOpts {
 #[derive(Debug, Default, Serialize, Deserialize)]
 pub struct FullConf {
     #[serde(default)]
-    pub log: LogConf,
+    pub log: Option,
 
     #[serde(default)]
-    pub dns: DnsConf,
+    pub dns: Option,
 
     pub endpoints: Vec,
 }
@@ -35,8 +41,8 @@ pub struct FullConf {
 impl FullConf {
     #[allow(unused)]
     pub fn new(
-        log: LogConf,
-        dns: DnsConf,
+        log: Option,
+        dns: Option,
         endpoints: Vec,
     ) -> Self {
         FullConf {
@@ -89,18 +95,23 @@ impl FullConf {
         } = opts;
 
         macro_rules! reset {
-            ($res: expr, $field: ident) => {
-                if let Some($field) = $field {
-                    $res = $field
+            ($main: ident, $field: ident, $value: expr) => {
+                if $value.is_some() {
+                    let mut conf = match self.$main.clone() {
+                        Some(c) => c,
+                        None => Default::default(),
+                    };
+                    conf.$field = $value;
+                    self.$main = Some(conf);
                 }
             };
         }
 
-        reset!(self.log.level, log_level);
-        reset!(self.log.output, log_output);
-        reset!(self.dns.mode, dns_mode);
-        reset!(self.dns.protocol, dns_protocol);
-        reset!(self.dns.nameservers, dns_servers);
+        reset!(log, level, log_level);
+        reset!(log, output, log_output);
+        reset!(dns, mode, dns_mode);
+        reset!(dns, protocol, dns_protocol);
+        reset!(dns, nameservers, dns_servers);
 
         self
     }
diff --git a/src/main.rs b/src/main.rs
index 421ab81a..cb1add29 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -8,7 +8,7 @@ use std::env;
 
 use cfg_if::cfg_if;
 use cmd::CmdInput;
-use conf::{FullConf, LogConf, DnsConf};
+use conf::{Config, FullConf, LogConf, DnsConf};
 use utils::Endpoint;
 
 const VERSION: &str = "1.5.0-rc6";
@@ -59,8 +59,8 @@ fn start_from_conf(full: FullConf) {
         endpoints: eps_conf,
     } = full;
 
-    setup_log(log_conf);
-    setup_dns(dns_conf);
+    setup_log(log_conf.unwrap_or_default());
+    setup_dns(dns_conf.unwrap_or_default());
 
     let eps: Vec = eps_conf
         .into_iter()
@@ -77,7 +77,7 @@ fn start_from_conf(full: FullConf) {
 fn setup_log(log: LogConf) {
     println!("log: {}", &log);
 
-    let (level, output) = log.into();
+    let (level, output) = log.resolve();
     fern::Dispatch::new()
         .format(|out, message, record| {
             out.finish(format_args!(
@@ -100,8 +100,8 @@ fn setup_dns(dns: DnsConf) {
 
     #[cfg(feature = "trust-dns")]
     {
-        let (conf, opts) = dns.into();
-        dns::configure(Some(conf), Some(opts));
+        let (conf, opts) = dns.resolve();
+        dns::configure(conf, opts);
         dns::build();
     }
 }
diff --git a/src/utils/consts.rs b/src/utils/consts.rs
index 88253b1f..6af7db79 100644
--- a/src/utils/consts.rs
+++ b/src/utils/consts.rs
@@ -1,5 +1,8 @@
 use std::fmt::{Display, Formatter};
 
+// default logfile
+pub const DEFAULT_LOG_FILE: &str = "stdout";
+
 // default timeout
 pub const TCP_TIMEOUT: usize = 300;
 pub const UDP_TIMEOUT: usize = 30;

From 5320dfe5699c306bdaece85017d03c3f0165573e Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Wed, 8 Dec 2021 21:56:15 +0900
Subject: [PATCH 094/169] add network global config

---
 readme.md       | 24 ++++++++++++++----------
 src/conf/log.rs |  2 +-
 src/conf/mod.rs |  8 ++++++++
 src/conf/net.rs | 19 +++++++++++++++++++
 src/main.rs     |  1 +
 5 files changed, 43 insertions(+), 11 deletions(-)
 create mode 100644 src/conf/net.rs

diff --git a/readme.md b/readme.md
index f65f085b..322280cf 100644
--- a/readme.md
+++ b/readme.md
@@ -101,16 +101,18 @@ mode = "ipv4_only"
 protocol = "tcp_and_udp"
 nameservers = ["8.8.8.8:53", "8.8.4.4:53"]
 
+[network]
+udp = true
+zero_copy = true
+fast_open = true
+tcp_timeout = 300
+udp_timeout = 30
+
 [[endpoints]]
 local = "0.0.0.0:5000"
 remote = "1.1.1.1:443"
 
 [[endpoints]]
-udp = true
-fast_open = true
-zero_copy = false
-tcp_timeout = 300
-udp_timeout = 30
 local = "0.0.0.0:10000"
 remote = "www.google.com:443"
 through = "0.0.0.0"
@@ -129,17 +131,19 @@ through = "0.0.0.0"
 		"protocol": "tcp_and_udp",
 		"nameservers": ["8.8.8.8:53", "8.8.4.4:53"]
 	},
+	"network": {
+		"udp": true,
+		"fast_open": true,
+		"zero_copy": true,
+		"tcp_timeout": 300,
+		"udp_timeout": 30,
+	},
 	"endpoints": [
 		{
 			"local": "0.0.0.0:5000",
 			"remote": "1.1.1.1:443"
 		},
 		{
-			"udp": true,
-			"fast_open": true,
-			"zero_copy": true,
-			"tcp_timeout": 300,
-			"udp_timeout": 30,
 			"local": "0.0.0.0:10000",
 			"remote": "www.google.com:443",
 			"through": "0.0.0.0"
diff --git a/src/conf/log.rs b/src/conf/log.rs
index e3ab384a..7d0aea19 100644
--- a/src/conf/log.rs
+++ b/src/conf/log.rs
@@ -82,7 +82,7 @@ impl Config for LogConf {
         use std::fs::OpenOptions;
         let LogConf { level, output } = self;
         let level = level.unwrap_or_default();
-        let output = output.unwrap_or_else(||String::from(DEFAULT_LOG_FILE));
+        let output = output.unwrap_or_else(|| String::from(DEFAULT_LOG_FILE));
 
         let output: fern::Output = match output.as_str() {
             "stdout" => io::stdout().into(),
diff --git a/src/conf/mod.rs b/src/conf/mod.rs
index 4bce30e6..21a28d0a 100644
--- a/src/conf/mod.rs
+++ b/src/conf/mod.rs
@@ -9,6 +9,9 @@ pub use self::log::{LogLevel, LogConf};
 mod dns;
 pub use dns::{DnsMode, DnsProtocol, DnsConf};
 
+mod net;
+pub use net::{NetConf};
+
 mod endpoint;
 pub use endpoint::EndpointConf;
 
@@ -35,6 +38,9 @@ pub struct FullConf {
     #[serde(default)]
     pub dns: Option,
 
+    #[serde(default)]
+    pub network: Option,
+
     pub endpoints: Vec,
 }
 
@@ -43,11 +49,13 @@ impl FullConf {
     pub fn new(
         log: Option,
         dns: Option,
+        network: Option,
         endpoints: Vec,
     ) -> Self {
         FullConf {
             log,
             dns,
+            network,
             endpoints,
         }
     }
diff --git a/src/conf/net.rs b/src/conf/net.rs
new file mode 100644
index 00000000..75e8463d
--- /dev/null
+++ b/src/conf/net.rs
@@ -0,0 +1,19 @@
+use serde::{Serialize, Deserialize};
+
+#[derive(Serialize, Debug, Deserialize, Clone, Copy, Default)]
+pub struct NetConf {
+    #[serde(default)]
+    pub udp: Option,
+
+    #[serde(default)]
+    pub fast_open: Option,
+
+    #[serde(default)]
+    pub zero_copy: Option,
+
+    #[serde(default)]
+    pub tcp_timeout: Option,
+
+    #[serde(default)]
+    pub udp_timeout: Option,
+}
diff --git a/src/main.rs b/src/main.rs
index cb1add29..abb8fdfe 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -57,6 +57,7 @@ fn start_from_conf(full: FullConf) {
         log: log_conf,
         dns: dns_conf,
         endpoints: eps_conf,
+        ..
     } = full;
 
     setup_log(log_conf.unwrap_or_default());

From 63342cb230e34a9fab4cf94517d155935fa3b5ca Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Thu, 9 Dec 2021 15:33:24 +0900
Subject: [PATCH 095/169] refactor config parser

---
 src/cmd/mod.rs       | 68 ++++++--------------------------
 src/conf/dns.rs      | 38 +++++++++++++++++-
 src/conf/endpoint.rs | 84 ++++++++++++++++++----------------------
 src/conf/log.rs      | 30 ++++++++++++++-
 src/conf/mod.rs      | 83 ++++++++++++++++++++++-----------------
 src/conf/net.rs      | 92 ++++++++++++++++++++++++++++++++++++++++++--
 src/main.rs          |  8 ++--
 src/utils/consts.rs  |  4 +-
 src/utils/types.rs   |  4 +-
 9 files changed, 259 insertions(+), 152 deletions(-)

diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs
index 43dcedf2..e6659221 100644
--- a/src/cmd/mod.rs
+++ b/src/cmd/mod.rs
@@ -1,23 +1,22 @@
 use clap::{App, AppSettings};
 use clap::{Arg, ArgMatches};
 
-use crate::conf::GlobalOpts;
+use crate::conf::CmdOverride;
 use crate::conf::EndpointConf;
+use crate::conf::{Config, LogConf, DnsConf, NetConf};
 
 use super::VERSION;
 use crate::utils::FEATURES;
-use crate::utils::TCP_TIMEOUT;
-use crate::utils::UDP_TIMEOUT;
 
 pub enum CmdInput {
-    Config(String, GlobalOpts),
-    Endpoint(EndpointConf, GlobalOpts),
+    Config(String, CmdOverride),
+    Endpoint(EndpointConf, CmdOverride),
     None,
 }
 
 fn add_flags(app: App) -> App {
     app.help_heading("FLAGS").args(&[
-        Arg::new("udp")
+        Arg::new("use_udp")
             .short('u')
             .long("udp")
             .about("enable udp forward")
@@ -180,61 +179,16 @@ fn parse_matches(matches: ArgMatches) -> CmdInput {
     if matches.value_of("local").is_some()
         && matches.value_of("remote").is_some()
     {
-        let ep = parse_single_ep(&matches);
+        let ep = EndpointConf::from_cmd_args(&matches);
         return CmdInput::Endpoint(ep, opts);
     }
 
     CmdInput::None
 }
 
-fn parse_single_ep(matches: &ArgMatches) -> EndpointConf {
-    let udp = matches.is_present("udp");
-    let fast_open = matches.is_present("fast_open");
-    let zero_copy = matches.is_present("zero_copy");
-
-    let local = matches.value_of("local").unwrap().to_string();
-    let remote = matches.value_of("remote").unwrap().to_string();
-    let through = matches
-        .value_of("through")
-        .map_or(String::new(), String::from);
-
-    let tcp_timeout = matches
-        .value_of("tcp_timeout")
-        .map_or(TCP_TIMEOUT, |t| t.parse::().unwrap_or(TCP_TIMEOUT));
-    let udp_timeout = matches
-        .value_of("udp_timeout")
-        .map_or(UDP_TIMEOUT, |t| t.parse::().unwrap_or(UDP_TIMEOUT));
-
-    EndpointConf {
-        udp,
-        fast_open,
-        zero_copy,
-        local,
-        remote,
-        through,
-        tcp_timeout,
-        udp_timeout,
-    }
-}
-
-fn parse_global_opts(matches: &ArgMatches) -> GlobalOpts {
-    let log_level = matches
-        .value_of("log_level")
-        .map(|x| String::from(x).into());
-    let log_output = matches.value_of("log_output").map(String::from);
-    let dns_mode = matches.value_of("dns_mode").map(|x| String::from(x).into());
-    let dns_protocol = matches
-        .value_of("dns_protocol")
-        .map(|x| String::from(x).into());
-    let dns_servers = matches
-        .value_of("dns_servers")
-        .map(|x| x.split(',').map(String::from).collect());
-
-    GlobalOpts {
-        log_level,
-        log_output,
-        dns_mode,
-        dns_protocol,
-        dns_servers,
-    }
+fn parse_global_opts(matches: &ArgMatches) -> CmdOverride {
+    let log = LogConf::from_cmd_args(matches);
+    let dns = DnsConf::from_cmd_args(matches);
+    let network = NetConf::from_cmd_args(matches);
+    CmdOverride { log, dns, network }
 }
diff --git a/src/conf/dns.rs b/src/conf/dns.rs
index 4570b44c..263b051d 100644
--- a/src/conf/dns.rs
+++ b/src/conf/dns.rs
@@ -168,7 +168,7 @@ impl Display for DnsConf {
 impl Config for DnsConf {
     type Output = (Option, Option);
 
-    fn resolve(self) -> Self::Output {
+    fn build(self) -> Self::Output {
         let DnsConf {
             mode,
             protocol,
@@ -212,4 +212,40 @@ impl Config for DnsConf {
 
         (Some(conf), opts)
     }
+
+    fn rst_field(&mut self, other: &Self) -> &mut Self {
+        use crate::rst;
+        let other = other.clone();
+        rst!(self, mode, other);
+        rst!(self, protocol, other);
+        rst!(self, nameservers, other);
+        self
+    }
+
+    fn take_field(&mut self, other: &Self) -> &mut Self {
+        use crate::take;
+        let other = other.clone();
+        take!(self, mode, other);
+        take!(self, protocol, other);
+        take!(self, nameservers, other);
+        self
+    }
+
+    fn from_cmd_args(matches: &clap::ArgMatches) -> Self {
+        let mode = matches.value_of("dns_mode").map(|x| String::from(x).into());
+
+        let protocol = matches
+            .value_of("dns_protocol")
+            .map(|x| String::from(x).into());
+
+        let nameservers = matches
+            .value_of("dns_servers")
+            .map(|x| x.split(',').map(String::from).collect());
+
+        Self {
+            mode,
+            protocol,
+            nameservers,
+        }
+    }
 }
diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs
index 8a539196..371cc198 100644
--- a/src/conf/endpoint.rs
+++ b/src/conf/endpoint.rs
@@ -1,38 +1,19 @@
 use serde::{Serialize, Deserialize};
 use std::net::{IpAddr, SocketAddr, ToSocketAddrs};
-use crate::utils::{Endpoint, RemoteAddr, ConnectOpts};
+use super::{NetConf, Config};
+use crate::utils::{Endpoint, RemoteAddr};
 
 #[derive(Debug, Serialize, Deserialize)]
 pub struct EndpointConf {
-    #[serde(default)]
-    pub udp: bool,
-
-    #[serde(default)]
-    pub fast_open: bool,
-
-    #[serde(default)]
-    pub zero_copy: bool,
-
-    #[serde(default = "tcp_timeout")]
-    pub tcp_timeout: usize,
-
-    #[serde(default = "udp_timeout")]
-    pub udp_timeout: usize,
-
     pub local: String,
 
     pub remote: String,
 
     #[serde(default)]
-    pub through: String,
-}
+    pub through: Option,
 
-const fn tcp_timeout() -> usize {
-    crate::utils::TCP_TIMEOUT
-}
-
-const fn udp_timeout() -> usize {
-    crate::utils::UDP_TIMEOUT
+    #[serde(default)]
+    pub network: NetConf,
 }
 
 impl EndpointConf {
@@ -60,6 +41,10 @@ impl EndpointConf {
 
     fn build_send_through(&self) -> Option {
         let Self { through, .. } = self;
+        let through = match through {
+            Some(x) => x,
+            None => return None,
+        };
         match through.to_socket_addrs() {
             Ok(mut x) => Some(x.next().unwrap()),
             Err(_) => {
@@ -71,31 +56,38 @@ impl EndpointConf {
             }
         }
     }
+}
 
-    fn build_conn_opts(&self) -> ConnectOpts {
-        let Self {
-            udp,
-            fast_open,
-            zero_copy,
-            tcp_timeout,
-            udp_timeout,
-            ..
-        } = *self;
-
-        ConnectOpts {
-            use_udp: udp,
-            fast_open,
-            zero_copy,
-            tcp_timeout,
-            udp_timeout,
-            send_through: self.build_send_through(),
-        }
-    }
+impl Config for EndpointConf {
+    type Output = Endpoint;
 
-    pub fn build(&self) -> Endpoint {
+    fn build(self) -> Self::Output {
         let local = self.build_local();
         let remote = self.build_remote();
-        let opts = self.build_conn_opts();
-        Endpoint::new(local, remote, opts)
+        let through = self.build_send_through();
+        let mut conn_opts = self.network.build();
+        conn_opts.send_through = through;
+        Endpoint::new(local, remote, conn_opts)
+    }
+
+    fn rst_field(&mut self, _: &Self) -> &mut Self {
+        unreachable!()
+    }
+
+    fn take_field(&mut self, _: &Self) -> &mut Self {
+        unreachable!()
+    }
+
+    fn from_cmd_args(matches: &clap::ArgMatches) -> Self {
+        let local = matches.value_of("local").unwrap().to_string();
+        let remote = matches.value_of("remote").unwrap().to_string();
+        let through = matches.value_of("through").map(String::from);
+
+        EndpointConf {
+            local,
+            remote,
+            through,
+            network: Default::default(),
+        }
     }
 }
diff --git a/src/conf/log.rs b/src/conf/log.rs
index 7d0aea19..e40da127 100644
--- a/src/conf/log.rs
+++ b/src/conf/log.rs
@@ -77,7 +77,7 @@ pub struct LogConf {
 impl Config for LogConf {
     type Output = (LevelFilter, fern::Output);
 
-    fn resolve(self) -> Self::Output {
+    fn build(self) -> Self::Output {
         use std::io;
         use std::fs::OpenOptions;
         let LogConf { level, output } = self;
@@ -98,6 +98,34 @@ impl Config for LogConf {
 
         (level.into(), output)
     }
+
+    fn rst_field(&mut self, other: &Self) -> &mut Self {
+        use crate::rst;
+        let other = other.clone();
+
+        rst!(self, level, other);
+        rst!(self, output, other);
+        self
+    }
+
+    fn take_field(&mut self, other: &Self) -> &mut Self {
+        use crate::take;
+        let other = other.clone();
+
+        take!(self, level, other);
+        take!(self, output, other);
+        self
+    }
+
+    fn from_cmd_args(matches: &clap::ArgMatches) -> Self {
+        let level = matches
+            .value_of("log_level")
+            .map(|x| String::from(x).into());
+
+        let output = matches.value_of("log_output").map(String::from);
+
+        Self { level, output }
+    }
 }
 
 impl Display for LogConf {
diff --git a/src/conf/mod.rs b/src/conf/mod.rs
index 21a28d0a..e2476597 100644
--- a/src/conf/mod.rs
+++ b/src/conf/mod.rs
@@ -1,6 +1,7 @@
 use std::fs;
 use std::io::{Result, Error, ErrorKind};
 
+use clap::ArgMatches;
 use serde::{Serialize, Deserialize};
 
 mod log;
@@ -18,25 +19,29 @@ pub use endpoint::EndpointConf;
 pub trait Config {
     type Output;
 
-    fn resolve(self) -> Self::Output;
+    fn build(self) -> Self::Output;
+
+    fn rst_field(&mut self, other: &Self) -> &mut Self;
+
+    fn take_field(&mut self, other: &Self) -> &mut Self;
+
+    fn from_cmd_args(matches: &ArgMatches) -> Self;
 }
 
 #[derive(Debug, Default)]
-pub struct GlobalOpts {
-    pub log_level: Option,
-    pub log_output: Option,
-    pub dns_mode: Option,
-    pub dns_protocol: Option,
-    pub dns_servers: Option>,
+pub struct CmdOverride {
+    pub log: LogConf,
+    pub dns: DnsConf,
+    pub network: NetConf,
 }
 
 #[derive(Debug, Default, Serialize, Deserialize)]
 pub struct FullConf {
     #[serde(default)]
-    pub log: Option,
+    pub log: LogConf,
 
     #[serde(default)]
-    pub dns: Option,
+    pub dns: DnsConf,
 
     #[serde(default)]
     pub network: Option,
@@ -47,8 +52,8 @@ pub struct FullConf {
 impl FullConf {
     #[allow(unused)]
     pub fn new(
-        log: Option,
-        dns: Option,
+        log: LogConf,
+        dns: DnsConf,
         network: Option,
         endpoints: Vec,
     ) -> Self {
@@ -93,34 +98,40 @@ impl FullConf {
         self
     }
 
-    pub fn apply_global_opts(&mut self, opts: GlobalOpts) -> &mut Self {
-        let GlobalOpts {
-            log_level,
-            log_output,
-            dns_mode,
-            dns_protocol,
-            dns_servers,
+    pub fn apply_global_opts(&mut self, opts: CmdOverride) -> &mut Self {
+        let CmdOverride {
+            ref log,
+            ref dns,
+            ref network,
         } = opts;
+        let global_network = self.network.unwrap_or_default();
 
-        macro_rules! reset {
-            ($main: ident, $field: ident, $value: expr) => {
-                if $value.is_some() {
-                    let mut conf = match self.$main.clone() {
-                        Some(c) => c,
-                        None => Default::default(),
-                    };
-                    conf.$field = $value;
-                    self.$main = Some(conf);
-                }
-            };
-        }
-
-        reset!(log, level, log_level);
-        reset!(log, output, log_output);
-        reset!(dns, mode, dns_mode);
-        reset!(dns, protocol, dns_protocol);
-        reset!(dns, nameservers, dns_servers);
+        self.log.rst_field(log);
+        self.dns.rst_field(dns);
+        self.endpoints.iter_mut().for_each(|x| {
+            x.network.take_field(&global_network).rst_field(network);
+        });
 
         self
     }
 }
+
+#[macro_export]
+macro_rules! rst {
+    ($this: ident, $field: ident, $other: ident) => {
+        let Self { $field, .. } = $other;
+        if $field.is_some() {
+            $this.$field = $field;
+        }
+    };
+}
+
+#[macro_export]
+macro_rules! take {
+    ($this: ident, $field: ident, $other: ident) => {
+        let Self { $field, .. } = $other;
+        if $this.$field.is_none() && $field.is_some() {
+            $this.$field = $field;
+        }
+    };
+}
diff --git a/src/conf/net.rs b/src/conf/net.rs
index 75e8463d..a058ef84 100644
--- a/src/conf/net.rs
+++ b/src/conf/net.rs
@@ -1,9 +1,12 @@
 use serde::{Serialize, Deserialize};
+use super::Config;
+use crate::utils::ConnectOpts;
+use crate::utils::{TCP_TIMEOUT, UDP_TIMEOUT};
 
 #[derive(Serialize, Debug, Deserialize, Clone, Copy, Default)]
 pub struct NetConf {
     #[serde(default)]
-    pub udp: Option,
+    pub use_udp: Option,
 
     #[serde(default)]
     pub fast_open: Option,
@@ -12,8 +15,91 @@ pub struct NetConf {
     pub zero_copy: Option,
 
     #[serde(default)]
-    pub tcp_timeout: Option,
+    pub tcp_timeout: Option,
 
     #[serde(default)]
-    pub udp_timeout: Option,
+    pub udp_timeout: Option,
+}
+
+impl Config for NetConf {
+    type Output = ConnectOpts;
+
+    fn build(self) -> Self::Output {
+        macro_rules! unbox {
+            ($field: ident) => {
+                self.$field.unwrap_or_default()
+            };
+            ($field: ident, $value: expr) => {
+                self.$field.unwrap_or($value)
+            };
+        }
+
+        let use_udp = unbox!(use_udp);
+        let fast_open = unbox!(fast_open);
+        let zero_copy = unbox!(zero_copy);
+        let tcp_timeout = unbox!(tcp_timeout, TCP_TIMEOUT);
+        let udp_timeout = unbox!(udp_timeout, UDP_TIMEOUT);
+
+        ConnectOpts {
+            use_udp,
+            fast_open,
+            zero_copy,
+            tcp_timeout,
+            udp_timeout,
+            send_through: None,
+        }
+    }
+
+    fn rst_field(&mut self, other: &Self) -> &mut Self {
+        use crate::rst;
+        let other = *other;
+
+        rst!(self, use_udp, other);
+        rst!(self, fast_open, other);
+        rst!(self, zero_copy, other);
+        rst!(self, tcp_timeout, other);
+        rst!(self, udp_timeout, other);
+        self
+    }
+
+    fn take_field(&mut self, other: &Self) -> &mut Self {
+        use crate::take;
+        let other = *other;
+
+        take!(self, use_udp, other);
+        take!(self, fast_open, other);
+        take!(self, zero_copy, other);
+        take!(self, tcp_timeout, other);
+        take!(self, udp_timeout, other);
+        self
+    }
+
+    fn from_cmd_args(matches: &clap::ArgMatches) -> Self {
+        let use_udp = matches.is_present("use_udp");
+        let fast_open = matches.is_present("fast_open");
+        let zero_copy = matches.is_present("zero_copy");
+
+        let tcp_timeout = matches
+            .value_of("tcp_timeout")
+            .map(|x| x.parse::().unwrap());
+        let udp_timeout = matches
+            .value_of("udp_timeout")
+            .map(|x| x.parse::().unwrap());
+
+        const fn bool_to_opt(b: bool) -> Option {
+            if b {
+                Some(true)
+            } else {
+                None
+            }
+        }
+
+        Self {
+            use_udp: bool_to_opt(use_udp),
+            fast_open: bool_to_opt(fast_open),
+            zero_copy: bool_to_opt(zero_copy),
+            tcp_timeout,
+            udp_timeout,
+        }
+    }
 }
diff --git a/src/main.rs b/src/main.rs
index abb8fdfe..fb88bed5 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -60,8 +60,8 @@ fn start_from_conf(full: FullConf) {
         ..
     } = full;
 
-    setup_log(log_conf.unwrap_or_default());
-    setup_dns(dns_conf.unwrap_or_default());
+    setup_log(log_conf);
+    setup_dns(dns_conf);
 
     let eps: Vec = eps_conf
         .into_iter()
@@ -78,7 +78,7 @@ fn start_from_conf(full: FullConf) {
 fn setup_log(log: LogConf) {
     println!("log: {}", &log);
 
-    let (level, output) = log.resolve();
+    let (level, output) = log.build();
     fern::Dispatch::new()
         .format(|out, message, record| {
             out.finish(format_args!(
@@ -101,7 +101,7 @@ fn setup_dns(dns: DnsConf) {
 
     #[cfg(feature = "trust-dns")]
     {
-        let (conf, opts) = dns.resolve();
+        let (conf, opts) = dns.build();
         dns::configure(conf, opts);
         dns::build();
     }
diff --git a/src/utils/consts.rs b/src/utils/consts.rs
index 6af7db79..8795ca37 100644
--- a/src/utils/consts.rs
+++ b/src/utils/consts.rs
@@ -4,8 +4,8 @@ use std::fmt::{Display, Formatter};
 pub const DEFAULT_LOG_FILE: &str = "stdout";
 
 // default timeout
-pub const TCP_TIMEOUT: usize = 300;
-pub const UDP_TIMEOUT: usize = 30;
+pub const TCP_TIMEOUT: u64 = 300;
+pub const UDP_TIMEOUT: u64 = 30;
 
 // https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1
 pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") {
diff --git a/src/utils/types.rs b/src/utils/types.rs
index b98296c9..a276afb4 100644
--- a/src/utils/types.rs
+++ b/src/utils/types.rs
@@ -15,8 +15,8 @@ pub struct ConnectOpts {
     pub use_udp: bool,
     pub fast_open: bool,
     pub zero_copy: bool,
-    pub tcp_timeout: usize,
-    pub udp_timeout: usize,
+    pub tcp_timeout: u64,
+    pub udp_timeout: u64,
     pub send_through: Option,
 }
 

From 82b09acb49a474fc181c6c394b3c13a6e34b3c9d Mon Sep 17 00:00:00 2001
From: zephyr 
Date: Thu, 9 Dec 2021 16:13:38 +0900
Subject: [PATCH 096/169] rename some fields

---
 readme.md            | 98 +++++++++++++++++++++++++++++---------------
 src/cmd/mod.rs       | 74 ++++++++++++++++-----------------
 src/conf/endpoint.rs |  8 ++--
 src/relay/mod.rs     | 10 ++---
 src/relay/udp.rs     | 16 ++++----
 src/utils/types.rs   |  8 ++--
 6 files changed, 124 insertions(+), 90 deletions(-)

diff --git a/readme.md b/readme.md
index 322280cf..1daba1b1 100644
--- a/readme.md
+++ b/readme.md
@@ -35,8 +35,8 @@ cargo build --release --no-default-features --features udp, tfo, zero-copy, trus
 ```
 
 ## Usage
-```shell
-Realm 1.5.0-rc6 [udp][zero-copy][trust-dns]
+```
+Realm 1.5.x [udp][zero-copy][trust-dns][multi-thread]
 
 A high efficiency relay tool
 
@@ -44,20 +44,18 @@ USAGE:
     realm [FLAGS] [OPTIONS]
 
 FLAGS:
-    -u, --udp       enable udp forward
-    -f, --tfo       enable tcp fast open
-    -z, --splice    enable tcp zero copy
-    -d, --daemon    run as a unix daemon
+    -h, --help       show help
+    -v, --version    show version
+    -d, --daemon     run as a unix daemon
+    -u, --udp        force enable udp forward
+    -f, --tfo        force enable tcp fast open
+    -z, --splice     force enable tcp zero copy
 
 OPTIONS:
-    -h, --help                    show help
-    -v, --version                 show version
-    -c, --config            use config file
-    -l, --listen            listen address
-    -r, --remote            remote address
-    -x, --through           send through ip or address
-        --tcp-timeout     set timeout value for tcp
-        --udp-timeout     set timeout value for udp
+    -c, --config      use config file
+    -l, --listen      listen address
+    -r, --remote      remote address
+    -x, --through     send through ip or address
 
 GLOBAL OPTIONS:
         --log-level           override log level
@@ -65,6 +63,8 @@ GLOBAL OPTIONS:
         --dns-mode             override dns mode
         --dns-protocol     override dns protocol
         --dns-servers       override dns servers
+        --tcp-timeout        override tcp timeout
+        --udp-timeout        override udp timeout
 ```
 
 start from command line arguments:
@@ -102,18 +102,18 @@ protocol = "tcp_and_udp"
 nameservers = ["8.8.8.8:53", "8.8.4.4:53"]
 
 [network]
-udp = true
+use_udp = true
 zero_copy = true
 fast_open = true
 tcp_timeout = 300
 udp_timeout = 30
 
 [[endpoints]]
-local = "0.0.0.0:5000"
+listen = "0.0.0.0:5000"
 remote = "1.1.1.1:443"
 
 [[endpoints]]
-local = "0.0.0.0:10000"
+listen = "0.0.0.0:10000"
 remote = "www.google.com:443"
 through = "0.0.0.0"
 ```
@@ -132,7 +132,7 @@ through = "0.0.0.0"
 		"nameservers": ["8.8.8.8:53", "8.8.4.4:53"]
 	},
 	"network": {
-		"udp": true,
+		"use_udp": true,
 		"fast_open": true,
 		"zero_copy": true,
 		"tcp_timeout": 300,
@@ -140,11 +140,11 @@ through = "0.0.0.0"
 	},
 	"endpoints": [
 		{
-			"local": "0.0.0.0:5000",
+			"listen": "0.0.0.0:5000",
 			"remote": "1.1.1.1:443"
 		},
 		{
-			"local": "0.0.0.0:10000",
+			"listen": "0.0.0.0:10000",
 			"remote": "www.google.com:443",
 			"through": "0.0.0.0"
 		}
@@ -153,11 +153,39 @@ through = "0.0.0.0"
 
-## global: [log, dns, endpoints] -Note: must provide `endpoint.local` and `endpoint.remote` +## global +```shell +├── log +│   ├── level +│   └── output +├── dns +│   ├── mode +│   ├── protocol +│   └── nameservers +├── network +│   ├── use_udp +│   ├── fast_open +│   ├── zero_copy +│   ├── tcp_timeout +│   └── udp_timeout +└── endpoints + ├── listen + ├── remote + ├── through + └── network + ├── use_udp + ├── fast_open + ├── zero_copy + ├── tcp_timeout + └── udp_timeout +``` + +You should provide at least `endpoint.listen` and `endpoint.remote`, other fields will take their default values if not provided. + +Priority: cmd override > endpoint config > global config --- -### log: [level, output] +### log #### log.level - off *(default)* @@ -172,7 +200,7 @@ Note: must provide `endpoint.local` and `endpoint.remote` - path, e.g. (`/var/log/realm.log`) --- -### dns: [mode, protocol, nameservers] +### dns ~~this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lookup strategy with `"dns": "ipv4_only"`, which is equal to `"dns": {"mode": "ipv4_only"}`~~ You must use `dns.mode` instead of `dns_mode` #### dns.mode @@ -193,13 +221,19 @@ format: ["server1", "server2" ...] default: On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). Otherwise use google's public dns as default upstream resolver(`8.8.8.8:53`, `8.8.4.4:53` and `2001:4860:4860::8888:53`, `2001:4860:4860::8844:53`). +--- +### network +- use_udp (default: false) +- zero_copy (default: false) +- fast_open (default: false) +- tcp_timeout (default: 300) +- udp_timeout (default: 30) + +To disable timeout, you need to explicitly set timeout value as 0 + --- ### endpoint objects -- local: listen address -- remote: remote address -- through: send through specified ip or address, this is optional -- udp: true|false, default=false -- zero_copy: true|false, default=false -- fast_open: true|false, default=false -- tcp_timeout: tcp timeout(sec), default=300 -- udp_timeout: udp timeout(sec), default=30 +- local: listen address +- remote: remote address +- through: send through specified ip or address +- network: override global network config diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index e6659221..52f11767 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -16,81 +16,69 @@ pub enum CmdInput { fn add_flags(app: App) -> App { app.help_heading("FLAGS").args(&[ + Arg::new("help") + .short('h') + .long("help") + .about("show help") + .display_order(0), + Arg::new("version") + .short('v') + .long("version") + .about("show version") + .display_order(1), + Arg::new("daemon") + .short('d') + .long("daemon") + .about("run as a unix daemon") + .display_order(2), Arg::new("use_udp") .short('u') .long("udp") - .about("enable udp forward") - .display_order(0), + .about("force enable udp forward") + .display_order(3), Arg::new("fast_open") .short('f') .long("tfo") - .about("enable tcp fast open") - .display_order(1), + .about("force enable tcp fast open") + .display_order(4), Arg::new("zero_copy") .short('z') .long("splice") - .about("enable tcp zero copy") - .display_order(2), - Arg::new("daemon") - .short('d') - .long("daemon") - .about("run as a unix daemon") - .display_order(3), + .about("force enable tcp zero copy") + .display_order(5), ]) } fn add_options(app: App) -> App { app.help_heading("OPTIONS").args(&[ - Arg::new("help") - .short('h') - .long("help") - .about("show help") - .display_order(0), - Arg::new("version") - .short('v') - .long("version") - .about("show version") - .display_order(1), Arg::new("config") .short('c') .long("config") .about("use config file") .value_name("path") .takes_value(true) - .display_order(2), + .display_order(0), Arg::new("local") .short('l') .long("listen") .about("listen address") .value_name("addr") .takes_value(true) - .display_order(3), + .display_order(1), Arg::new("remote") .short('r') .long("remote") .about("remote address") .value_name("addr") .takes_value(true) - .display_order(4), + .display_order(2), Arg::new("through") .short('x') .long("through") .about("send through ip or address") .value_name("addr") .takes_value(true) - .display_order(5), - Arg::new("tcp_timeout") - .long("tcp-timeout") - .about("set timeout value for tcp") - .value_name("second") - .takes_value(true) - .display_order(6), - Arg::new("udp_timeout") - .long("udp-timeout") - .about("set timeout value for udp") - .value_name("second") - .takes_value(true) - .display_order(7), + .display_order(3), ]) } @@ -126,6 +114,18 @@ fn add_global_options(app: App) -> App { .value_name("servers") .takes_value(true) .display_order(4), + Arg::new("tcp_timeout") + .long("tcp-timeout") + .about("override tcp timeout") + .value_name("second") + .takes_value(true) + .display_order(5), + Arg::new("udp_timeout") + .long("udp-timeout") + .about("override udp timeout") + .value_name("second") + .takes_value(true) + .display_order(6), ]) } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index 371cc198..0a35d706 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -5,7 +5,7 @@ use crate::utils::{Endpoint, RemoteAddr}; #[derive(Debug, Serialize, Deserialize)] pub struct EndpointConf { - pub local: String, + pub listen: String, pub remote: String, @@ -18,7 +18,7 @@ pub struct EndpointConf { impl EndpointConf { fn build_local(&self) -> SocketAddr { - self.local + self.listen .to_socket_addrs() .expect("invalid local address") .next() @@ -79,12 +79,12 @@ impl Config for EndpointConf { } fn from_cmd_args(matches: &clap::ArgMatches) -> Self { - let local = matches.value_of("local").unwrap().to_string(); + let listen = matches.value_of("local").unwrap().to_string(); let remote = matches.value_of("remote").unwrap().to_string(); let through = matches.value_of("through").map(String::from); EndpointConf { - local, + listen, remote, through, network: Default::default(), diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 23d169c7..e5407ff2 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -19,15 +19,15 @@ pub async fn run(eps: Vec) { async fn proxy_tcp(ep: Endpoint) { let Endpoint { - local, + listen, remote, opts, .. } = ep; - let lis = TcpListener::bind(local) + let lis = TcpListener::bind(listen) .await - .unwrap_or_else(|e| panic!("unable to bind {}: {}", &local, &e)); + .unwrap_or_else(|e| panic!("unable to bind {}: {}", &listen, &e)); loop { let (stream, addr) = match lis.accept().await { @@ -50,13 +50,13 @@ mod udp; #[cfg(feature = "udp")] async fn proxy_udp(ep: Endpoint) { let Endpoint { - local, + listen, remote, opts, .. } = ep; - if let Err(e) = udp::proxy(local, remote, opts).await { + if let Err(e) = udp::proxy(listen, remote, opts).await { panic!("udp forward exit: {}", &e); } } diff --git a/src/relay/udp.rs b/src/relay/udp.rs index b8b4bee8..cc7bfa09 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -18,7 +18,7 @@ type SockMap = Arc>>>; const BUF_SIZE: usize = DEFAULT_BUF_SIZE; pub async fn proxy( - local: SocketAddr, + listen: SocketAddr, remote: RemoteAddr, conn_opts: ConnectOpts, ) -> Result<()> { @@ -28,12 +28,12 @@ pub async fn proxy( .. } = conn_opts; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); - let local_sock = Arc::new(UdpSocket::bind(&local).await?); + let listen_sock = Arc::new(UdpSocket::bind(&listen).await?); let timeout = Duration::from_secs(timeout as u64); let mut buf = vec![0u8; BUF_SIZE]; loop { - let (n, client_addr) = match local_sock.recv_from(&mut buf).await { + let (n, client_addr) = match listen_sock.recv_from(&mut buf).await { Ok(x) => x, Err(e) => { error!("failed to recv udp packet from client: {}", &e); @@ -61,7 +61,7 @@ pub async fn proxy( client_addr, &remote_addr, &send_through, - local_sock.clone(), + listen_sock.clone(), timeout, ) .await @@ -79,7 +79,7 @@ pub async fn proxy( async fn send_back( sock_map: SockMap, client_addr: SocketAddr, - local_sock: Arc, + listen_sock: Arc, alloc_sock: Arc, timeout: Duration, ) { @@ -105,7 +105,7 @@ async fn send_back( debug!("recv udp packet from remote: {}", &remote_addr); - if let Err(e) = local_sock.send_to(&buf[..n], &client_addr).await { + if let Err(e) = listen_sock.send_to(&buf[..n], &client_addr).await { error!("failed to send udp packet back to client: {}", &e); continue; } @@ -130,7 +130,7 @@ async fn alloc_new_socket( client_addr: SocketAddr, remote_addr: &SocketAddr, send_through: &Option, - local_sock: Arc, + listen_sock: Arc, timeout: Duration, ) -> Arc { // pick a random port @@ -149,7 +149,7 @@ async fn alloc_new_socket( tokio::spawn(send_back( sock_map.clone(), client_addr, - local_sock, + listen_sock, alloc_sock.clone(), timeout, )); diff --git a/src/utils/types.rs b/src/utils/types.rs index a276afb4..dc682805 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -22,7 +22,7 @@ pub struct ConnectOpts { #[derive(Clone)] pub struct Endpoint { - pub local: SocketAddr, + pub listen: SocketAddr, pub remote: RemoteAddr, pub opts: ConnectOpts, } @@ -65,12 +65,12 @@ impl RemoteAddr { impl Endpoint { pub fn new( - local: SocketAddr, + listen: SocketAddr, remote: RemoteAddr, opts: ConnectOpts, ) -> Self { Endpoint { - local, + listen, remote, opts, } @@ -121,7 +121,7 @@ impl Display for Endpoint { write!( f, "{} -> {}, options: {}", - &self.local, &self.remote, &self.opts + &self.listen, &self.remote, &self.opts ) } } From b05090f17635f6b9d10af50a7ab9e6cfe546c63f Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 9 Dec 2021 19:46:33 +0900 Subject: [PATCH 097/169] disable timeout when value = 0 --- Cargo.lock | 1 + Cargo.toml | 1 + src/relay/tcp.rs | 16 +++++++----- src/utils/mod.rs | 3 +++ src/utils/timeout.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 76 insertions(+), 6 deletions(-) create mode 100644 src/utils/timeout.rs diff --git a/Cargo.lock b/Cargo.lock index cb6078dd..519df856 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -741,6 +741,7 @@ dependencies = [ "libc", "log", "mimalloc", + "pin-project", "serde", "serde_json", "tokio", diff --git a/Cargo.toml b/Cargo.toml index b3e557a7..b5b2b87c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ serde_json = "1" tokio = { version = "1", features = ["rt", "io-util", "net", "time"] } trust-dns-resolver = { version = "0.20", optional = true } +pin-project = "1" lazy_static = "1" # tfo diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index e62530a2..8ffb1148 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -29,8 +29,8 @@ use futures::try_join; use log::{debug, info}; use tokio::net::TcpSocket; -use tokio::time::timeout as timeoutfut; +use crate::utils::timeoutfut; use crate::utils::{RemoteAddr, ConnectOpts}; #[derive(Clone, Copy)] @@ -64,7 +64,11 @@ pub async fn proxy( } = conn_opts; let remote = remote.into_sockaddr().await?; - let timeout = Duration::from_secs(timeout as u64); + let timeout = if timeout != 0 { + Some(Duration::from_secs(timeout)) + } else { + None + }; let mut outbound = match send_through { Some(x) => { @@ -137,7 +141,7 @@ mod normal_copy { pub async fn copy( mut r: ReadHalf<'_>, mut w: WriteHalf<'_>, - timeout: Duration, + timeout: Option, direction: TcpDirection, ) -> Result<()> where { @@ -146,7 +150,7 @@ where { let mut buf = vec![0u8; BUF_SIZE]; let mut n: usize; loop { - n = timeoutfut(timeout, r.read(&mut buf)).await??; + n = timeoutfut(r.read(&mut buf), timeout).await??; if n == 0 { break; } @@ -231,7 +235,7 @@ mod zero_copy { pub async fn copy( r: ReadHalf<'_>, mut w: WriteHalf<'_>, - timeout: Duration, + timeout: Option, direction: TcpDirection, ) -> Result<()> { use std::os::unix::io::AsRawFd; @@ -252,7 +256,7 @@ mod zero_copy { let res = 'LOOP: loop { // read until the socket buffer is empty // or the pipe is filled - timeoutfut(timeout, rx.readable()).await??; + timeoutfut(rx.readable(), timeout).await??; while n < BUF_SIZE { match splice_n(rfd, wpipe, BUF_SIZE - n) { x if x > 0 => n += x as usize, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index cedd490b..ac89c154 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,6 +4,9 @@ pub use types::*; mod consts; pub use consts::*; +mod timeout; +pub use timeout::*; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; #[allow(unused)] diff --git a/src/utils/timeout.rs b/src/utils/timeout.rs new file mode 100644 index 00000000..91a753ea --- /dev/null +++ b/src/utils/timeout.rs @@ -0,0 +1,61 @@ +use std::pin::Pin; +use std::task::{Poll, Context}; +use std::future::Future; +use std::time::Duration; +use std::io::{Result, Error, ErrorKind}; + +use tokio::time::Sleep; + +use pin_project::pin_project; + +#[allow(clippy::large_enum_variant)] +#[pin_project(project = DelayP)] +enum Delay { + Some(#[pin] Sleep), + + None, +} + +#[pin_project] +pub struct Timeout { + #[pin] + value: T, + + #[pin] + delay: Delay, +} + +impl Future for Timeout { + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + use Poll::{Pending, Ready}; + + let this = self.project(); + + if let Ready(v) = this.value.poll(cx) { + return Ready(Ok(v)); + } + + if let DelayP::Some(delay) = this.delay.project() { + let delay: Pin<&mut Sleep> = delay; + if delay.poll(cx).is_ready() { + return Ready(Err(Error::new(ErrorKind::TimedOut, "timeout"))); + } + } + + Pending + } +} + +pub fn timeoutfut( + future: F, + duration: Option, +) -> Timeout { + use tokio::time::sleep; + let delay = duration.map_or(Delay::None, |d| Delay::Some(sleep(d))); + Timeout { + value: future, + delay, + } +} From f19c6ccfd867064867042502558dd81063538c5a Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 9 Dec 2021 19:47:38 +0900 Subject: [PATCH 098/169] rename env var --- readme.md | 4 ++-- src/main.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/readme.md b/readme.md index 1daba1b1..a18dcdc6 100644 --- a/readme.md +++ b/readme.md @@ -83,9 +83,9 @@ realm -c config.json start from environment variable: ```shell -CONFIG='{"endpoints":[{"local":"127.0.0.1:5000","remote":"1.1.1.1:443"}]}' realm +REALM_CONF='{"endpoints":[{"local":"127.0.0.1:5000","remote":"1.1.1.1:443"}]}' realm -export CONFIG=`cat config.json | jq -c ` +export REALM_CONF=`cat config.json | jq -c ` realm ``` diff --git a/src/main.rs b/src/main.rs index fb88bed5..c4a48bb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,8 +11,8 @@ use cmd::CmdInput; use conf::{Config, FullConf, LogConf, DnsConf}; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc6"; -const ENV_CONFIG: &str = "CONFIG"; +const VERSION: &str = "1.5.0-rc9"; +const ENV_CONFIG: &str = "REALM_CONF"; cfg_if! { if #[cfg(all(feature = "mi-malloc"))] { From b28cb66a61aa38f54616daf4720f4fa3fada843b Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 9 Dec 2021 19:51:49 +0900 Subject: [PATCH 099/169] update doc --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index a18dcdc6..771f58a8 100644 --- a/readme.md +++ b/readme.md @@ -229,11 +229,11 @@ On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.c - tcp_timeout (default: 300) - udp_timeout (default: 30) -To disable timeout, you need to explicitly set timeout value as 0 +To disable timeout, you need to explicitly set timeout value to 0 --- -### endpoint objects -- local: listen address +### endpoint +- listen: listen address - remote: remote address - through: send through specified ip or address - network: override global network config From c67625cd13ab5dff30a9a306887318b14551ea8d Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 9 Dec 2021 20:01:33 +0900 Subject: [PATCH 100/169] apply new timeout rule to udp --- src/relay/udp.rs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/relay/udp.rs b/src/relay/udp.rs index cc7bfa09..f322dd5f 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -7,11 +7,11 @@ use std::collections::HashMap; use log::{debug, info, error}; use tokio::net::UdpSocket; -use tokio::time::timeout as timeoutfut; use crate::utils::DEFAULT_BUF_SIZE; use crate::utils::{RemoteAddr, ConnectOpts}; use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; +use crate::utils::timeoutfut; // client <--> allocated socket type SockMap = Arc>>>; @@ -29,7 +29,12 @@ pub async fn proxy( } = conn_opts; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let listen_sock = Arc::new(UdpSocket::bind(&listen).await?); - let timeout = Duration::from_secs(timeout as u64); + let timeout = if timeout != 0 { + Some(Duration::from_secs(timeout)) + } else { + None + }; + let mut buf = vec![0u8; BUF_SIZE]; loop { @@ -81,13 +86,13 @@ async fn send_back( client_addr: SocketAddr, listen_sock: Arc, alloc_sock: Arc, - timeout: Duration, + timeout: Option, ) { let mut buf = vec![0u8; BUF_SIZE]; loop { let res = - match timeoutfut(timeout, alloc_sock.recv_from(&mut buf)).await { + match timeoutfut(alloc_sock.recv_from(&mut buf), timeout).await { Ok(x) => x, Err(_) => { info!("udp association for {} timeout", &client_addr); @@ -131,7 +136,7 @@ async fn alloc_new_socket( remote_addr: &SocketAddr, send_through: &Option, listen_sock: Arc, - timeout: Duration, + timeout: Option, ) -> Arc { // pick a random port let alloc_sock = Arc::new(match send_through { From 00a33ec85dcecd4434418e2e50e4360e332b4f51 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 11 Jan 2022 21:30:31 +0900 Subject: [PATCH 101/169] fix log configuration display --- src/conf/log.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf/log.rs b/src/conf/log.rs index e40da127..1512e410 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -132,7 +132,7 @@ impl Display for LogConf { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let LogConf { level, output } = self.clone(); let level = level.unwrap_or_default(); - let output = output.unwrap_or_default(); + let output = output.unwrap_or(String::from("stdout")); write!(f, "level={}, output={}", level, output) } From 6a8b86b10b64f5a5fcd3b0ae2ad35918c648bacb Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 2 Feb 2022 23:43:11 +0900 Subject: [PATCH 102/169] update deps --- Cargo.lock | 72 +++----------------------------------------------- Cargo.toml | 4 +-- src/cmd/mod.rs | 37 +++++++++++++------------- 3 files changed, 24 insertions(+), 89 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 519df856..491bb76d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,33 +84,17 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.0-beta.5" +version = "3.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feff3878564edb93745d58cf63e17b63f24142506e7a20c87a5521ed7bfb1d63" +checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" dependencies = [ "atty", "bitflags", - "clap_derive", "indexmap", - "lazy_static", "os_str_bytes", "strsim", "termcolor", "textwrap", - "unicase", -] - -[[package]] -name = "clap_derive" -version = "3.0.0-beta.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b15c6b4f786ffb6192ffe65a36855bc1fc2444bcd0945ae16748dcd6ed7d0d3" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn", ] [[package]] @@ -549,9 +533,9 @@ checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" [[package]] name = "os_str_bytes" -version = "4.2.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "addaa943333a514159c80c97ff4a93306530d965d27e139188283cd13e06a799" +checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64" dependencies = [ "memchr", ] @@ -625,30 +609,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-hack" version = "0.5.19" @@ -893,9 +853,6 @@ name = "textwrap" version = "0.14.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" -dependencies = [ - "unicode-width", -] [[package]] name = "thiserror" @@ -1040,15 +997,6 @@ dependencies = [ "trust-dns-proto", ] -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-bidi" version = "0.3.5" @@ -1073,12 +1021,6 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" -[[package]] -name = "unicode-width" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" - [[package]] name = "unicode-xid" version = "0.2.1" @@ -1097,12 +1039,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "version_check" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" - [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index b5b2b87c..bed17ab1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ name = "realm" version = "1.5.0" authors = ["zhboner "] -edition = "2018" +edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html @@ -11,7 +11,7 @@ cfg-if = "1" futures = "0.3" log = "0.4" -clap = "3.0.0-beta.5" +clap = "3" toml = "0.5" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 52f11767..2fce539c 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -19,32 +19,32 @@ fn add_flags(app: App) -> App { Arg::new("help") .short('h') .long("help") - .about("show help") + .help("show help") .display_order(0), Arg::new("version") .short('v') .long("version") - .about("show version") + .help("show version") .display_order(1), Arg::new("daemon") .short('d') .long("daemon") - .about("run as a unix daemon") + .help("run as a unix daemon") .display_order(2), Arg::new("use_udp") .short('u') .long("udp") - .about("force enable udp forward") + .help("force enable udp forward") .display_order(3), Arg::new("fast_open") .short('f') .long("tfo") - .about("force enable tcp fast open") + .help("force enable tcp fast open") .display_order(4), Arg::new("zero_copy") .short('z') .long("splice") - .about("force enable tcp zero copy") + .help("force enable tcp zero copy") .display_order(5), ]) } @@ -54,28 +54,28 @@ fn add_options(app: App) -> App { Arg::new("config") .short('c') .long("config") - .about("use config file") + .help("use config file") .value_name("path") .takes_value(true) .display_order(0), Arg::new("local") .short('l') .long("listen") - .about("listen address") + .help("listen address") .value_name("addr") .takes_value(true) .display_order(1), Arg::new("remote") .short('r') .long("remote") - .about("remote address") + .help("remote address") .value_name("addr") .takes_value(true) .display_order(2), Arg::new("through") .short('x') .long("through") - .about("send through ip or address") + .help("send through ip or address") .value_name("addr") .takes_value(true) .display_order(3), @@ -86,43 +86,43 @@ fn add_global_options(app: App) -> App { app.help_heading("GLOBAL OPTIONS").args(&[ Arg::new("log_level") .long("log-level") - .about("override log level") + .help("override log level") .value_name("level") .takes_value(true) .display_order(0), Arg::new("log_output") .long("log-output") - .about("override log output") + .help("override log output") .value_name("path") .takes_value(true) .display_order(1), Arg::new("dns_mode") .long("dns-mode") - .about("override dns mode") + .help("override dns mode") .value_name("mode") .takes_value(true) .display_order(2), Arg::new("dns_protocol") .long("dns-protocol") - .about("override dns protocol") + .help("override dns protocol") .value_name("protocol") .takes_value(true) .display_order(3), Arg::new("dns_servers") .long("dns-servers") - .about("override dns servers") + .help("override dns servers") .value_name("servers") .takes_value(true) .display_order(4), Arg::new("tcp_timeout") .long("tcp-timeout") - .about("override tcp timeout") + .help("override tcp timeout") .value_name("second") .takes_value(true) .display_order(5), Arg::new("udp_timeout") .long("udp-timeout") - .about("override udp timeout") + .help("override udp timeout") .value_name("second") .takes_value(true) .display_order(6), @@ -133,8 +133,7 @@ pub fn scan() -> CmdInput { let version = format!("{} {}", VERSION, FEATURES); let app = App::new("Realm") .about("A high efficiency relay tool") - .version(version.as_str()) - .license("MIT"); + .version(version.as_str()); let app = app .setting(AppSettings::ArgRequiredElseHelp) From dea2cba6b5cd7ff9955182a98c014086bdb42241 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 2 Feb 2022 23:46:27 +0900 Subject: [PATCH 103/169] add cross-compile for arm64: #7 --- .github/workflows/cross_compile.yml | 4 ++++ .github/workflows/release.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index b2f61c0a..b25e0be7 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -24,6 +24,10 @@ jobs: output: realm - target: x86_64-pc-windows-gnu output: realm.exe + - target: aarch64-unknown-linux-gnu + output: realm + - target: aarch64-unknown-linux-musl + output: realm steps: - uses: actions/checkout@v2 - name: install toolchain diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 47858222..fac0661e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,6 +20,10 @@ jobs: output: realm - target: x86_64-pc-windows-gnu output: realm.exe + - target: aarch64-unknown-linux-gnu + output: realm + - target: aarch64-unknown-linux-musl + output: realm steps: - uses: actions/checkout@v2 - name: install toolchain From f78390e8d9223c03975d5f77775c0285738b476c Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 3 Feb 2022 00:35:16 +0900 Subject: [PATCH 104/169] update doc --- readme.md | 66 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 7 deletions(-) diff --git a/readme.md b/readme.md index 771f58a8..4b8ef2d7 100644 --- a/readme.md +++ b/readme.md @@ -7,15 +7,37 @@ ## Introduction -realm is a simple, high performance relay server written in rust. +Realm is a simple, high performance relay server written in rust. ## Features + - ~~Zero configuration.~~ Setup and run in one command. - Concurrency. Bidirectional concurrent traffic leads to high performance. - Low resources cost. -## Custom Build -available Options: +## Build + +Install rust toolchains with [rustup](https://rustup.rs/). + +```shell +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +Clone this repository + +```shell +git clone https://github.com/zephyrchien/realm +``` + +Enter the directory and build + +```shell +cd realm +cargo build --release +``` + +### Build options + - udp *(enabled by default)* - trust-dns *(enabled by default)* - zero-copy *(enabled on linux)* @@ -24,7 +46,9 @@ available Options: - mi-malloc - jemalloc -see also: `Cargo.toml` +See also: `Cargo.toml` + +Examples: ```shell # simple tcp @@ -34,8 +58,17 @@ cargo build --release --no-default-features cargo build --release --no-default-features --features udp, tfo, zero-copy, trust-dns ``` +### Cross compile + +Please refer to [https://rust-lang.github.io/rustup/cross-compilation.html](https://rust-lang.github.io/rustup/cross-compilation.html). + +You may need to install cross-compilers or other SDKs, and specify them when building the project. + +Using [Cross](https://github.com/cross-rs/cross) is also a simple and good enough solution. + ## Usage -``` + +```shell Realm 1.5.x [udp][zero-copy][trust-dns][multi-thread] A high efficiency relay tool @@ -68,11 +101,13 @@ GLOBAL OPTIONS: ``` start from command line arguments: + ```shell realm -l 0.0.0.0:5000 -r 1.1.1.1:443 ``` start from config file: + ```shell # use toml realm -c config.toml @@ -82,6 +117,7 @@ realm -c config.json ``` start from environment variable: + ```shell REALM_CONF='{"endpoints":[{"local":"127.0.0.1:5000","remote":"1.1.1.1:443"}]}' realm @@ -90,7 +126,9 @@ realm ``` ## Configuration + TOML Example + ```toml [log] level = "warn" @@ -154,6 +192,7 @@ through = "0.0.0.0" ## global + ```shell ├── log │   ├── level @@ -185,9 +224,11 @@ You should provide at least `endpoint.listen` and `endpoint.remote`, other field Priority: cmd override > endpoint config > global config --- + ### log #### log.level + - off *(default)* - error - info @@ -195,15 +236,17 @@ Priority: cmd override > endpoint config > global config - trace #### log.output + - stdout *(default)* - stderr - path, e.g. (`/var/log/realm.log`) --- + ### dns -~~this is compatibe with old versions(before `v1.5.0-rc3`), you could still set lookup strategy with `"dns": "ipv4_only"`, which is equal to `"dns": {"mode": "ipv4_only"}`~~ You must use `dns.mode` instead of `dns_mode` #### dns.mode + - ipv4_only - ipv6_only - ipv4_then_ipv6 @@ -211,18 +254,25 @@ Priority: cmd override > endpoint config > global config - ipv4_and_ipv6 *(default)* #### dns.protocol + - tcp - udp - tcp_and_udp *(default)* #### dns.nameservers + format: ["server1", "server2" ...] default: -On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). Otherwise use google's public dns as default upstream resolver(`8.8.8.8:53`, `8.8.4.4:53` and `2001:4860:4860::8888:53`, `2001:4860:4860::8844:53`). + +On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). + +Otherwise, use google's public dns(`8.8.8.8:53`, `8.8.4.4:53` and `2001:4860:4860::8888:53`, `2001:4860:4860::8844:53`). --- + ### network + - use_udp (default: false) - zero_copy (default: false) - fast_open (default: false) @@ -232,7 +282,9 @@ On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.c To disable timeout, you need to explicitly set timeout value to 0 --- + ### endpoint + - listen: listen address - remote: remote address - through: send through specified ip or address From 44b54cde1ba3d8db52ff9d7faa937a9d29065052 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 3 Feb 2022 00:41:34 +0900 Subject: [PATCH 105/169] avoid extra allocation when displaying log config --- src/conf/log.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/conf/log.rs b/src/conf/log.rs index 1512e410..7583a228 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -132,7 +132,7 @@ impl Display for LogConf { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { let LogConf { level, output } = self.clone(); let level = level.unwrap_or_default(); - let output = output.unwrap_or(String::from("stdout")); + let output = output.unwrap_or_else(|| String::from("stdout")); write!(f, "level={}, output={}", level, output) } From bd68bae3430c75a7ad2fe3a93e40a1359dd5807c Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 3 Feb 2022 00:57:54 +0900 Subject: [PATCH 106/169] sync tokio-tfo --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- readme.md | 1 + tokio-tfo | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 491bb76d..5bc51325 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -929,8 +929,8 @@ dependencies = [ [[package]] name = "tokio-tfo" -version = "0.1.6" -source = "git+https://github.com/zephyrchien/tokio-tfo?branch=main#e64331f7e056f74c158281ca100b6758e43e41cf" +version = "0.1.9" +source = "git+https://github.com/zephyrchien/tokio-tfo?branch=main#03c8b5dc622c0e882a109fd4729477f7ebad3605" dependencies = [ "cfg-if", "futures", diff --git a/Cargo.toml b/Cargo.toml index bed17ab1..ad53676f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ pin-project = "1" lazy_static = "1" # tfo -tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.6", optional = true } +tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.9", optional = true } # zero-copy libc = { version = "0.2", optional = true } diff --git a/readme.md b/readme.md index 4b8ef2d7..80ab74d9 100644 --- a/readme.md +++ b/readme.md @@ -33,6 +33,7 @@ Enter the directory and build ```shell cd realm +git submodule sync && git submodule update --init --recursive cargo build --release ``` diff --git a/tokio-tfo b/tokio-tfo index e64331f7..03c8b5dc 160000 --- a/tokio-tfo +++ b/tokio-tfo @@ -1 +1 @@ -Subproject commit e64331f7e056f74c158281ca100b6758e43e41cf +Subproject commit 03c8b5dc622c0e882a109fd4729477f7ebad3605 From 50b528c875d49cc457695951bc8448f22d541917 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 3 Feb 2022 01:37:29 +0900 Subject: [PATCH 107/169] detailed log info --- src/relay/mod.rs | 2 +- src/relay/udp.rs | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index e5407ff2..9adb1e3b 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -38,7 +38,7 @@ async fn proxy_tcp(ep: Endpoint) { } }; - info!("new tcp connection from client {}", &addr); + info!("new tcp connection from {}", &addr); tokio::spawn(tcp::proxy(stream, remote.clone(), opts)); } diff --git a/src/relay/udp.rs b/src/relay/udp.rs index f322dd5f..d06bdda6 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -60,7 +60,7 @@ pub async fn proxy( let alloc_sock = match get_socket(&sock_map, &client_addr) { Some(x) => x, None => { - info!("new udp association for client {}", &client_addr); + info!("new udp association for {}", &client_addr); alloc_new_socket( &sock_map, client_addr, @@ -74,7 +74,7 @@ pub async fn proxy( }; if let Err(e) = alloc_sock.send_to(&buf[..n], &remote_addr).await { - error!("failed to send udp packet to remote: {}", &e); + error!("failed to send udp packet to {}: {}", &remote_addr, &e); } } @@ -111,13 +111,16 @@ async fn send_back( debug!("recv udp packet from remote: {}", &remote_addr); if let Err(e) = listen_sock.send_to(&buf[..n], &client_addr).await { - error!("failed to send udp packet back to client: {}", &e); + error!( + "failed to send udp packet back to {}: {}", + &client_addr, &e + ); continue; } } sock_map.write().unwrap().remove(&client_addr); - info!("remove udp association for {}", &client_addr); + debug!("remove udp association for {}", &client_addr); } #[inline] From 139f62f6ab02cbd6ee91bd038990f9a7cb1d80bf Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 3 Feb 2022 01:43:09 +0900 Subject: [PATCH 108/169] do not drop connection if socket option not supported: zhboner/realm#51 --- src/relay/tcp.rs | 44 +++++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index 8ffb1148..b5feb75d 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -26,7 +26,7 @@ use std::net::SocketAddr; use std::time::Duration; use futures::try_join; -use log::{debug, info}; +use log::{warn, debug}; use tokio::net::TcpSocket; @@ -49,6 +49,13 @@ impl Display for TcpDirection { } } +macro_rules! setsockopt_warn { + ($op: expr, $opt: expr) => {{ + let _ = + $op.map_err(|e| warn!("failed to set socket option $opt: {}", &e)); + }}; +} + #[allow(unused_variables)] pub async fn proxy( mut inbound: TcpStream, @@ -76,9 +83,12 @@ pub async fn proxy( SocketAddr::V4(_) => TcpSocket::new_v4()?, SocketAddr::V6(_) => TcpSocket::new_v6()?, }; - socket.set_reuseaddr(true)?; + + setsockopt_warn!(socket.set_reuseaddr(true), "reuseaddr"); + #[cfg(unix)] - socket.set_reuseport(true)?; + setsockopt_warn!(socket.set_reuseport(true), "reuseport"); + socket.bind(x)?; #[cfg(feature = "tfo")] @@ -94,10 +104,10 @@ pub async fn proxy( None => TcpStream::connect(remote).await?, }; - info!("new tcp connection to remote {}", &remote); + debug!("new tcp connection to {}", &remote); - inbound.set_nodelay(true)?; - outbound.set_nodelay(true)?; + setsockopt_warn!(inbound.set_nodelay(true), "nodelay"); + setsockopt_warn!(outbound.set_nodelay(true), "nodelay"); let (ri, wi) = inbound.split(); let (ro, wo) = outbound.split(); @@ -128,7 +138,13 @@ pub async fn proxy( ) }; - info!("tcp forward compelete or abort, close these 2 connection"); + debug!( + "tcp forward compelete: {}", + match &res { + Ok(_) => String::from("ok"), + Err(e) => e.to_string(), + } + ); // ignore read/write n bytes res.map(|_| ()) @@ -268,12 +284,7 @@ mod zero_copy { clear_readiness(rx, Interest::READABLE); break; } - _ => { - break 'LOOP Err(Error::new( - ErrorKind::Other, - "failed to splice from tcp connection", - )) - } + _ => break 'LOOP Err(Error::last_os_error()), } } // write until the pipe is empty @@ -284,12 +295,7 @@ mod zero_copy { x if x < 0 && is_wouldblock() => { clear_readiness(wx, Interest::WRITABLE); } - _ => { - break 'LOOP Err(Error::new( - ErrorKind::Other, - "failed to splice to tcp connection", - )) - } + _ => break 'LOOP Err(Error::last_os_error()), } } // complete From ad809f3e81c34df67891bd6fd9cc841b11318877 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 3 Feb 2022 03:15:56 +0900 Subject: [PATCH 109/169] adapt to tfo --- Cargo.lock | 265 ++++++++++++++++++++++------------------------- src/relay/tcp.rs | 9 +- tokio-tfo | 2 +- 3 files changed, 129 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5bc51325..00ac7ac0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "aho-corasick" -version = "0.7.15" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" dependencies = [ "memchr", ] [[package]] name = "async-trait" -version = "0.1.48" +version = "0.1.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36ea56748e10732c49404c153638a15ec3d6211ec5ff35d9bb20e13b93576adf" +checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3" dependencies = [ "proc-macro2", "quote", @@ -41,9 +41,9 @@ checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "bitflags" -version = "1.2.1" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "boxfnonce" @@ -165,9 +165,9 @@ checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" [[package]] name = "futures" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e7e43a803dae2fa37c1f6a8fe121e1f7bf9548b4dfc0522a42f34145dadfc27" +checksum = "28560757fe2bb34e79f907794bb6b22ae8b0e5c669b638a1132f2592b19035b4" dependencies = [ "futures-channel", "futures-core", @@ -180,9 +180,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e682a68b29a882df0545c143dc3646daefe80ba479bcdede94d5a703de2871e2" +checksum = "ba3dda0b6588335f360afc675d0564c17a77a2bda81ca178a4b6081bd86c7f0b" dependencies = [ "futures-core", "futures-sink", @@ -190,15 +190,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0402f765d8a89a26043b889b26ce3c4679d268fa6bb22cd7c6aad98340e179d1" +checksum = "d0c8ff0461b82559810cdccfde3215c3f373807f5e5232b71479bff7bb2583d7" [[package]] name = "futures-executor" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "badaa6a909fac9e7236d0620a2f57f7664640c56575b71a7552fbd68deafab79" +checksum = "29d6d2ff5bb10fb95c85b8ce46538a2e5f5e7fdc755623a7d4529ab8a4ed9d2a" dependencies = [ "futures-core", "futures-task", @@ -207,18 +207,16 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acc499defb3b348f8d8f3f66415835a9131856ff7714bf10dadfc4ec4bdb29a1" +checksum = "b1f9d34af5a1aac6fb380f735fe510746c38067c5bf16c7fd250280503c971b2" [[package]] name = "futures-macro" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c40298486cdf52cc00cd6d6987892ba502c7656a16a4192a9992b1ccedd121" +checksum = "6dbd947adfffb0efc70599b3ddcf7b5597bb5fa9e245eb99f62b3a5f7bb8bd3c" dependencies = [ - "autocfg", - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -226,23 +224,22 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a57bead0ceff0d6dde8f465ecd96c9338121bb7717d3e7b108059531870c4282" +checksum = "e3055baccb68d74ff6480350f8d6eb8fcfa3aa11bdc1a1ae3afdd0514617d508" [[package]] name = "futures-task" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a16bef9fc1a4dddb5bee51c989e3fbba26569cbb0e31f5b303c184e3dd33dae" +checksum = "6ee7c6485c30167ce4dfb83ac568a849fe53274c831081476ee13e0dce1aad72" [[package]] name = "futures-util" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feb5c238d27e2bf94ffdfd27b2c29e3df4a68c4193bb6427384259e2bf191967" +checksum = "d9b5cf40b47a271f77a8b1bec03ca09044d99d2372c0de244e66430761127164" dependencies = [ - "autocfg", "futures-channel", "futures-core", "futures-io", @@ -252,16 +249,14 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] [[package]] name = "getrandom" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +checksum = "418d37c8b1d42553c93648be529cb70f920d3baf8ef469b74b9638df426e0b4c" dependencies = [ "cfg-if", "libc", @@ -276,18 +271,18 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "heck" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87cbf45460356b7deeb5e3415b5563308c0a9b057c85e12b06ad551f98d0a6ac" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] [[package]] name = "hermit-abi" -version = "0.1.18" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" dependencies = [ "libc", ] @@ -311,9 +306,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "idna" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ "matches", "unicode-bidi", @@ -322,9 +317,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "1.7.0" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" +checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", "hashbrown", @@ -332,9 +327,9 @@ dependencies = [ [[package]] name = "instant" -version = "0.1.9" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] @@ -353,15 +348,15 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.3.0" +version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47be2f14c678be2fdcab04ab1171db51b2762ce6f0a8ee87c8dd4a04ed216135" +checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" [[package]] name = "itoa" -version = "0.4.7" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] name = "jemalloc-sys" @@ -392,9 +387,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.105" +version = "0.2.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "869d572136620d55835903746bcb5cdc54cb2851fd0aeec53220b4bb65ef3013" +checksum = "565dbd88872dbe4cc8a46e527f26483c1d1f7afa6b884a3bd6cd893d4f98da74" [[package]] name = "libmimalloc-sys" @@ -413,9 +408,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3" [[package]] name = "lock_api" -version = "0.4.3" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176" +checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" dependencies = [ "scopeguard", ] @@ -446,9 +441,9 @@ checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "matches" -version = "0.1.8" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" [[package]] name = "memchr" @@ -467,9 +462,9 @@ dependencies = [ [[package]] name = "mio" -version = "0.7.11" +version = "0.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956" +checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" dependencies = [ "libc", "log", @@ -517,9 +512,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.13.0" +version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" dependencies = [ "hermit-abi", "libc", @@ -527,9 +522,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" +checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5" [[package]] name = "os_str_bytes" @@ -542,9 +537,9 @@ dependencies = [ [[package]] name = "parking_lot" -version = "0.11.1" +version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", @@ -553,9 +548,9 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018" +checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" dependencies = [ "cfg-if", "instant", @@ -573,18 +568,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" +checksum = "58ad3879ad3baf4e44784bc6a718a8698867bb991f8ce24d1bcbe2cfb4c3a75e" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" +checksum = "744b6f092ba29c3650faf274db506afd39944f48420f6c86b17cfe0ee1cb36bb" dependencies = [ "proc-macro2", "quote", @@ -593,9 +588,9 @@ dependencies = [ [[package]] name = "pin-project-lite" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc0e1f259c92177c30a4c9d177246edd0a3568b25756a977d0632cf8fa37e905" +checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" [[package]] name = "pin-utils" @@ -605,27 +600,15 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.10" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" [[package]] name = "proc-macro2" -version = "1.0.32" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba508cc11742c0dc5c1659771673afbab7a0efab23aa17e854cbab0837ed0b43" +checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029" dependencies = [ "unicode-xid", ] @@ -638,18 +621,18 @@ checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" -version = "1.0.9" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7" +checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145" dependencies = [ "proc-macro2", ] [[package]] name = "rand" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8" dependencies = [ "libc", "rand_chacha", @@ -659,9 +642,9 @@ dependencies = [ [[package]] name = "rand_chacha" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", @@ -669,18 +652,18 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7" dependencies = [ "rand_core", ] @@ -712,18 +695,18 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.2.5" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94341e4e44e24f6b591b59e47a8a027df12e008d73fd5672dbea9cc22f4507d9" +checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" dependencies = [ "bitflags", ] [[package]] name = "regex" -version = "1.4.6" +version = "1.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a26af418b574bd56588335b3a3659a65725d4e636eb1016c2f9e3b38c7cc759" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" dependencies = [ "aho-corasick", "memchr", @@ -748,9 +731,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.5" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" +checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" [[package]] name = "scopeguard" @@ -760,18 +743,18 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "serde" -version = "1.0.126" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" +checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.126" +version = "1.0.136" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" +checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9" dependencies = [ "proc-macro2", "quote", @@ -780,9 +763,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.64" +version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "799e97dc9fdae36a5c8b8f2cae9ce2ee9fdce2058c57a93e6099d919fd982f79" +checksum = "d23c1ba4cf0efd44be32017709280b32d1cea5c3f1275c3b6d9e8bc54f758085" dependencies = [ "itoa", "ryu", @@ -791,15 +774,15 @@ dependencies = [ [[package]] name = "slab" -version = "0.4.2" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" +checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" [[package]] name = "smallvec" -version = "1.6.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" [[package]] name = "socket2" @@ -814,9 +797,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.2" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" +checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0" dependencies = [ "libc", "winapi", @@ -830,9 +813,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2afee18b8beb5a596ecb4a2dce128c719b4ba399d34126b9e4396e3f9860966" +checksum = "8a65b3f4ffa0092e9887669db0eae07941f023991ab58ea44da8fe8e2d511c6b" dependencies = [ "proc-macro2", "quote", @@ -856,18 +839,18 @@ checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" [[package]] name = "thiserror" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" +checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.24" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" +checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" dependencies = [ "proc-macro2", "quote", @@ -886,9 +869,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.2.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342" +checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2" dependencies = [ "tinyvec_macros", ] @@ -901,11 +884,10 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.12.0" +version = "1.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2c2416fdedca8443ae44b4527de1ea633af61d8f7169ffa6e72c5b53d24efcc" +checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" dependencies = [ - "autocfg", "bytes", "libc", "memchr", @@ -918,9 +900,9 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "1.5.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2dd85aeaba7b68df939bd357c6afb36c87951be9e80bf9c859f2fc3e9fca0fd" +checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7" dependencies = [ "proc-macro2", "quote", @@ -930,7 +912,7 @@ dependencies = [ [[package]] name = "tokio-tfo" version = "0.1.9" -source = "git+https://github.com/zephyrchien/tokio-tfo?branch=main#03c8b5dc622c0e882a109fd4729477f7ebad3605" +source = "git+https://github.com/zephyrchien/tokio-tfo?branch=main#b09d81c98dae6d8bb4cab0243037d33c5a75d5ce" dependencies = [ "cfg-if", "futures", @@ -938,7 +920,7 @@ dependencies = [ "log", "once_cell", "pin-project", - "socket2 0.4.2", + "socket2 0.4.4", "tokio", "winapi", ] @@ -954,9 +936,9 @@ dependencies = [ [[package]] name = "trust-dns-proto" -version = "0.20.1" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d57e219ba600dd96c2f6d82eb79645068e14edbc5c7e27514af40436b88150c" +checksum = "ca94d4e9feb6a181c690c4040d7a24ef34018d8313ac5044a61d21222ae24e31" dependencies = [ "async-trait", "cfg-if", @@ -979,9 +961,9 @@ dependencies = [ [[package]] name = "trust-dns-resolver" -version = "0.20.1" +version = "0.20.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0437eea3a6da51acc1e946545ff53d5b8fb2611ff1c3bed58522dde100536ae" +checksum = "ecae383baad9995efaa34ce8e57d12c3f305e545887472a492b838f4b5cfb77a" dependencies = [ "cfg-if", "futures-util", @@ -999,39 +981,36 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0" -dependencies = [ - "matches", -] +checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" [[package]] name = "unicode-normalization" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07fbfce1c8a97d547e8b5334978438d9d6ec8c20e38f56d4a4374d181493eaef" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796" +checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" [[package]] name = "unicode-xid" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" +checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" [[package]] name = "url" -version = "2.2.1" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ccd964113622c8e9322cfac19eb1004a07e636c545f325da085d5cdde6f1f8b" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs index b5feb75d..2c9f1d68 100644 --- a/src/relay/tcp.rs +++ b/src/relay/tcp.rs @@ -365,12 +365,14 @@ mod tfo { #[allow(unused)] pub async fn readable(&self) -> Result<()> { - self.0.inner().readable().await + let s = unsafe { const_cast(&self.0).inner() }; + s.readable().await } #[allow(unused)] pub async fn writable(&self) -> Result<()> { - self.0.inner().writable().await + let s = unsafe { const_cast(&self.0).inner() }; + s.writable().await } pub fn set_nodelay(&self, nodelay: bool) -> Result<()> { @@ -387,7 +389,8 @@ mod tfo { interest: Interest, f: impl FnOnce() -> Result, ) -> Result { - self.0.inner().try_io(interest, f) + let s = unsafe { const_cast(&self.0).inner() }; + s.try_io(interest, f) } } diff --git a/tokio-tfo b/tokio-tfo index 03c8b5dc..b09d81c9 160000 --- a/tokio-tfo +++ b/tokio-tfo @@ -1 +1 @@ -Subproject commit 03c8b5dc622c0e882a109fd4729477f7ebad3605 +Subproject commit b09d81c98dae6d8bb4cab0243037d33c5a75d5ce From 61c4bf433acb04cd2525ec7eb899a7aac892a9a9 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 4 Feb 2022 23:56:21 +0900 Subject: [PATCH 110/169] refactor relay --- src/relay/mod.rs | 16 +- src/relay/tcp.rs | 500 ------------------------------------------- src/relay/tcp/mod.rs | 97 +++++++++ src/relay/tcp/tfo.rs | 131 ++++++++++++ src/relay/tcp/zio.rs | 418 ++++++++++++++++++++++++++++++++++++ src/relay/udp.rs | 31 +-- 6 files changed, 676 insertions(+), 517 deletions(-) delete mode 100644 src/relay/tcp.rs create mode 100644 src/relay/tcp/mod.rs create mode 100644 src/relay/tcp/tfo.rs create mode 100644 src/relay/tcp/zio.rs diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 9adb1e3b..20daeb50 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -33,14 +33,24 @@ async fn proxy_tcp(ep: Endpoint) { let (stream, addr) = match lis.accept().await { Ok(x) => x, Err(e) => { - error!("failed to accept tcp connection: {}", &e); + error!("[tcp]failed to accept: {}", &e); continue; } }; - info!("new tcp connection from {}", &addr); + let msg = format!("{} => {}", &addr, &remote); + info!("[tcp]{}", &msg); - tokio::spawn(tcp::proxy(stream, remote.clone(), opts)); + let remote = remote.clone(); + tokio::spawn(async move { + match tcp::proxy(stream, remote, opts).await { + Ok((up, dl)) => info!( + "[tcp]{} finish, upload: {}b, download: {}b", + msg, up, dl + ), + Err(e) => error!("[tcp]{} error: {}", msg, e), + } + }); } } diff --git a/src/relay/tcp.rs b/src/relay/tcp.rs deleted file mode 100644 index 2c9f1d68..00000000 --- a/src/relay/tcp.rs +++ /dev/null @@ -1,500 +0,0 @@ -use cfg_if::cfg_if; - -cfg_if! { - if #[cfg(feature = "tfo")] { - use tfo::TcpStream; - use tfo::{ReadHalf, WriteHalf}; - pub use tfo::TcpListener; - } else { - use tokio::net::TcpStream; - use tokio::net::tcp::{ReadHalf,WriteHalf}; - pub use tokio::net::TcpListener; - } -} - -cfg_if! { - if #[cfg(all(target_os = "linux", feature = "zero-copy"))] { - const BUF_SIZE: usize = crate::utils::DEFAULT_PIPE_CAP; - } else { - const BUF_SIZE: usize = crate::utils::DEFAULT_BUF_SIZE; - } -} - -use std::io::Result; -use std::fmt::{Display, Formatter}; -use std::net::SocketAddr; -use std::time::Duration; -use futures::try_join; - -use log::{warn, debug}; - -use tokio::net::TcpSocket; - -use crate::utils::timeoutfut; -use crate::utils::{RemoteAddr, ConnectOpts}; - -#[derive(Clone, Copy)] -pub enum TcpDirection { - Forward, - Reverse, -} - -impl Display for TcpDirection { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - use TcpDirection::*; - match self { - Forward => write!(f, "forward"), - Reverse => write!(f, "reverse"), - } - } -} - -macro_rules! setsockopt_warn { - ($op: expr, $opt: expr) => {{ - let _ = - $op.map_err(|e| warn!("failed to set socket option $opt: {}", &e)); - }}; -} - -#[allow(unused_variables)] -pub async fn proxy( - mut inbound: TcpStream, - remote: RemoteAddr, - conn_opts: ConnectOpts, -) -> Result<()> { - let ConnectOpts { - tcp_timeout: timeout, - fast_open, - zero_copy, - send_through, - .. - } = conn_opts; - - let remote = remote.into_sockaddr().await?; - let timeout = if timeout != 0 { - Some(Duration::from_secs(timeout)) - } else { - None - }; - - let mut outbound = match send_through { - Some(x) => { - let socket = match x { - SocketAddr::V4(_) => TcpSocket::new_v4()?, - SocketAddr::V6(_) => TcpSocket::new_v6()?, - }; - - setsockopt_warn!(socket.set_reuseaddr(true), "reuseaddr"); - - #[cfg(unix)] - setsockopt_warn!(socket.set_reuseport(true), "reuseport"); - - socket.bind(x)?; - - #[cfg(feature = "tfo")] - if fast_open { - TcpStream::connect_with_socket(socket, remote).await? - } else { - socket.connect(remote).await?.into() - } - - #[cfg(not(feature = "tfo"))] - socket.connect(remote).await? - } - None => TcpStream::connect(remote).await?, - }; - - debug!("new tcp connection to {}", &remote); - - setsockopt_warn!(inbound.set_nodelay(true), "nodelay"); - setsockopt_warn!(outbound.set_nodelay(true), "nodelay"); - - let (ri, wi) = inbound.split(); - let (ro, wo) = outbound.split(); - - use TcpDirection::{Forward, Reverse}; - - #[cfg(all(target_os = "linux", feature = "zero-copy"))] - let res = if zero_copy { - use zero_copy::copy; - try_join!( - copy(ri, wo, timeout, Forward), - copy(ro, wi, timeout, Reverse) - ) - } else { - use normal_copy::copy; - try_join!( - copy(ri, wo, timeout, Forward), - copy(ro, wi, timeout, Reverse) - ) - }; - - #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] - let res = { - use normal_copy::copy; - try_join!( - copy(ri, wo, timeout, Forward), - copy(ro, wi, timeout, Reverse) - ) - }; - - debug!( - "tcp forward compelete: {}", - match &res { - Ok(_) => String::from("ok"), - Err(e) => e.to_string(), - } - ); - - // ignore read/write n bytes - res.map(|_| ()) -} - -mod normal_copy { - use super::*; - - #[allow(unused)] - pub async fn copy( - mut r: ReadHalf<'_>, - mut w: WriteHalf<'_>, - timeout: Option, - direction: TcpDirection, - ) -> Result<()> -where { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - - let mut buf = vec![0u8; BUF_SIZE]; - let mut n: usize; - loop { - n = timeoutfut(r.read(&mut buf), timeout).await??; - if n == 0 { - break; - } - w.write_all(&buf[..n]).await?; - w.flush().await?; - } - w.shutdown().await?; - - debug!("tcp forward half-complete, direction: {}", direction); - - Ok(()) - } -} - -#[cfg(all(target_os = "linux", feature = "zero-copy"))] -mod zero_copy { - use super::*; - use std::ops::Drop; - use std::io::{Error, ErrorKind}; - use tokio::io::Interest; - - struct Pipe(pub i32, pub i32); - - impl Drop for Pipe { - fn drop(&mut self) { - unsafe { - libc::close(self.0); - libc::close(self.1); - } - } - } - - impl Pipe { - fn create() -> Result { - use libc::{c_int, O_NONBLOCK}; - let mut pipes = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); - unsafe { - if libc::pipe2(pipes.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 - { - return Err(Error::new( - ErrorKind::Other, - "failed to create a pipe", - )); - } - Ok(Pipe(pipes.assume_init()[0], pipes.assume_init()[1])) - } - } - } - - #[inline] - fn splice_n(r: i32, w: i32, n: usize) -> isize { - use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; - unsafe { - libc::splice( - r, - std::ptr::null_mut::(), - w, - std::ptr::null_mut::(), - n, - SPLICE_F_MOVE | SPLICE_F_NONBLOCK, - ) - } - } - - #[inline] - fn is_wouldblock() -> bool { - use libc::{EWOULDBLOCK, EAGAIN}; - let err = Error::last_os_error().raw_os_error(); - match err { - Some(e) => e == EWOULDBLOCK || e == EAGAIN, - None => false, - } - } - - #[inline] - fn clear_readiness(x: &TcpStream, interest: Interest) { - let _ = x.try_io(interest, || { - Err(Error::new(ErrorKind::WouldBlock, "")) as Result<()> - }); - } - - pub async fn copy( - r: ReadHalf<'_>, - mut w: WriteHalf<'_>, - timeout: Option, - direction: TcpDirection, - ) -> Result<()> { - use std::os::unix::io::AsRawFd; - use tokio::io::AsyncWriteExt; - // init pipe - let pipe = Pipe::create()?; - let (rpipe, wpipe) = (pipe.0, pipe.1); - // rw ref - let rx = r.as_ref(); - let wx = w.as_ref(); - // rw raw fd - let rfd = rx.as_raw_fd(); - let wfd = wx.as_raw_fd(); - // ctrl - let mut n: usize = 0; - let mut done = false; - - let res = 'LOOP: loop { - // read until the socket buffer is empty - // or the pipe is filled - timeoutfut(rx.readable(), timeout).await??; - while n < BUF_SIZE { - match splice_n(rfd, wpipe, BUF_SIZE - n) { - x if x > 0 => n += x as usize, - x if x == 0 => { - done = true; - break; - } - x if x < 0 && is_wouldblock() => { - clear_readiness(rx, Interest::READABLE); - break; - } - _ => break 'LOOP Err(Error::last_os_error()), - } - } - // write until the pipe is empty - while n > 0 { - wx.writable().await?; - match splice_n(rpipe, wfd, n) { - x if x > 0 => n -= x as usize, - x if x < 0 && is_wouldblock() => { - clear_readiness(wx, Interest::WRITABLE); - } - _ => break 'LOOP Err(Error::last_os_error()), - } - } - // complete - if done { - break Ok(()); - } - }; - - if done { - w.shutdown().await?; - debug!("tcp forward half-complete, direction: {}", direction); - }; - res - } -} - -#[cfg(feature = "tfo")] -mod tfo { - use std::io::Result; - use std::net::SocketAddr; - use std::pin::Pin; - use std::task::{Poll, Context}; - - use tokio_tfo::{TfoStream, TfoListener}; - use tokio::io::{AsyncRead, AsyncWrite}; - use tokio::io::ReadBuf; - use tokio::io::Interest; - use tokio::net::TcpSocket; - - pub struct TcpListener(TfoListener); - - pub struct TcpStream(TfoStream); - pub struct ReadHalf<'a>(&'a TcpStream); - pub struct WriteHalf<'a>(&'a TcpStream); - - #[allow(clippy::mut_from_ref)] - #[inline] - unsafe fn const_cast(x: &T) -> &mut T { - let const_ptr = x as *const T; - let mut_ptr = const_ptr as *mut T; - &mut *mut_ptr - } - - impl TcpListener { - pub async fn bind(addr: SocketAddr) -> Result { - TfoListener::bind(addr).await.map(TcpListener) - } - - pub async fn accept(&self) -> Result<(TcpStream, SocketAddr)> { - self.0.accept().await.map(|(x, addr)| (TcpStream(x), addr)) - } - } - - impl TcpStream { - pub async fn connect(addr: SocketAddr) -> Result { - TfoStream::connect(addr).await.map(TcpStream) - } - - pub async fn connect_with_socket( - socket: TcpSocket, - addr: SocketAddr, - ) -> Result { - TfoStream::connect_with_socket(socket, addr) - .await - .map(TcpStream) - } - - #[allow(unused)] - pub async fn readable(&self) -> Result<()> { - let s = unsafe { const_cast(&self.0).inner() }; - s.readable().await - } - - #[allow(unused)] - pub async fn writable(&self) -> Result<()> { - let s = unsafe { const_cast(&self.0).inner() }; - s.writable().await - } - - pub fn set_nodelay(&self, nodelay: bool) -> Result<()> { - self.0.set_nodelay(nodelay) - } - - pub fn split(&mut self) -> (ReadHalf, WriteHalf) { - (ReadHalf(&*self), WriteHalf(&*self)) - } - - #[allow(unused)] - pub fn try_io( - &self, - interest: Interest, - f: impl FnOnce() -> Result, - ) -> Result { - let s = unsafe { const_cast(&self.0).inner() }; - s.try_io(interest, f) - } - } - - impl From for TcpStream { - fn from(x: TfoStream) -> Self { - TcpStream(x) - } - } - - impl From for TcpStream { - fn from(x: tokio::net::TcpStream) -> Self { - TcpStream(x.into()) - } - } - - impl AsyncRead for TcpStream { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - Pin::new(&mut self.get_mut().0).poll_read(cx, buf) - } - } - - impl AsyncWrite for TcpStream { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(&mut self.get_mut().0).poll_write(cx, buf) - } - - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(&mut self.get_mut().0).poll_flush(cx) - } - - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(&mut self.get_mut().0).poll_shutdown(cx) - } - } - - impl<'a> AsyncRead for ReadHalf<'a> { - fn poll_read( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut ReadBuf<'_>, - ) -> Poll> { - Pin::new(unsafe { const_cast(self.0) }).poll_read(cx, buf) - } - } - - impl<'a> AsyncWrite for WriteHalf<'a> { - fn poll_write( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - Pin::new(unsafe { const_cast(self.0) }).poll_write(cx, buf) - } - - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(unsafe { const_cast(self.0) }).poll_flush(cx) - } - - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - Pin::new(unsafe { const_cast(self.0) }).poll_shutdown(cx) - } - } - - impl<'a> AsRef for ReadHalf<'a> { - fn as_ref(&self) -> &TcpStream { - self.0 - } - } - - impl<'a> AsRef for WriteHalf<'a> { - fn as_ref(&self) -> &TcpStream { - self.0 - } - } - - #[cfg(target_os = "linux")] - mod linux_ext { - use super::*; - use std::os::unix::io::AsRawFd; - use std::os::unix::prelude::RawFd; - impl AsRawFd for TcpStream { - fn as_raw_fd(&self) -> RawFd { - self.0.as_raw_fd() - } - } - } -} diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs new file mode 100644 index 00000000..c7b8852e --- /dev/null +++ b/src/relay/tcp/mod.rs @@ -0,0 +1,97 @@ +mod zio; + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(feature = "tfo")] { + mod tfo; + use tfo::TcpStream; + pub use tfo::TcpListener; + } else { + use tokio::net::TcpStream; + pub use tokio::net::TcpListener; + } +} + +use std::io::Result; +use std::net::SocketAddr; +use std::time::Duration; + +use log::{warn, debug}; + +use tokio::net::TcpSocket; + +use crate::utils::{RemoteAddr, ConnectOpts}; + +macro_rules! setsockopt_warn { + ($op: expr, $opt: expr) => {{ + let _ = $op.map_err(|e| warn!("[tcp]failed to setsockopt $opt: {}", e)); + }}; +} + +#[allow(unused_variables)] +pub async fn proxy( + mut inbound: TcpStream, + remote: RemoteAddr, + conn_opts: ConnectOpts, +) -> Result<(u64, u64)> { + let ConnectOpts { + tcp_timeout: timeout, + fast_open, + zero_copy, + send_through, + .. + } = conn_opts; + + let remote = remote.into_sockaddr().await?; + + debug!("[tcp]remote resolved as {}", &remote); + + let timeout = if timeout != 0 { + Some(Duration::from_secs(timeout)) + } else { + None + }; + + let mut outbound = match send_through { + Some(x) => { + let socket = match x { + SocketAddr::V4(_) => TcpSocket::new_v4()?, + SocketAddr::V6(_) => TcpSocket::new_v6()?, + }; + + setsockopt_warn!(socket.set_reuseaddr(true), "reuseaddr"); + + #[cfg(unix)] + setsockopt_warn!(socket.set_reuseport(true), "reuseport"); + + socket.bind(x)?; + + #[cfg(feature = "tfo")] + if fast_open { + TcpStream::connect_with_socket(socket, remote).await? + } else { + socket.connect(remote).await?.into() + } + + #[cfg(not(feature = "tfo"))] + socket.connect(remote).await? + } + None => TcpStream::connect(remote).await?, + }; + + setsockopt_warn!(inbound.set_nodelay(true), "nodelay"); + setsockopt_warn!(outbound.set_nodelay(true), "nodelay"); + + #[cfg(all(target_os = "linux", feature = "zero-copy"))] + let res = if zero_copy { + zio::bidi_copy_pipe(&mut inbound, &mut outbound).await + } else { + zio::bidi_copy_buffer(&mut inbound, &mut outbound).await + }; + + #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] + let res = zio::bidi_copy_buffer(&mut inbound, &mut outbound).await; + + res +} diff --git a/src/relay/tcp/tfo.rs b/src/relay/tcp/tfo.rs new file mode 100644 index 00000000..e71b4d5a --- /dev/null +++ b/src/relay/tcp/tfo.rs @@ -0,0 +1,131 @@ +use std::io::Result; +use std::net::SocketAddr; +use std::pin::Pin; +use std::task::{Poll, Context}; + +use tokio_tfo::{TfoStream, TfoListener}; +use tokio::io::{AsyncRead, AsyncWrite}; +use tokio::io::ReadBuf; +use tokio::io::Interest; +use tokio::net::TcpSocket; + +pub struct TcpStream(TfoStream); +pub struct TcpListener(TfoListener); + +#[allow(clippy::mut_from_ref)] +#[inline] +unsafe fn const_cast(x: &T) -> &mut T { + let const_ptr = x as *const T; + let mut_ptr = const_ptr as *mut T; + &mut *mut_ptr +} + +macro_rules! inner { + ($x :ident) => { + unsafe { const_cast(&$x.0).inner() } + }; +} + +impl TcpListener { + pub async fn bind(addr: SocketAddr) -> Result { + TfoListener::bind(addr).await.map(TcpListener) + } + + pub async fn accept(&self) -> Result<(TcpStream, SocketAddr)> { + self.0.accept().await.map(|(x, addr)| (TcpStream(x), addr)) + } +} + +impl TcpStream { + pub async fn connect(addr: SocketAddr) -> Result { + TfoStream::connect(addr).await.map(TcpStream) + } + + pub async fn connect_with_socket( + socket: TcpSocket, + addr: SocketAddr, + ) -> Result { + TfoStream::connect_with_socket(socket, addr) + .await + .map(TcpStream) + } + + pub fn set_nodelay(&self, nodelay: bool) -> Result<()> { + self.0.set_nodelay(nodelay) + } + + pub fn poll_read_ready(&self, cx: &mut Context<'_>) -> Poll> { + inner!(self).poll_read_ready(cx) + } + + pub fn poll_write_ready(&self, cx: &mut Context<'_>) -> Poll> { + inner!(self).poll_write_ready(cx) + } + + #[allow(unused)] + pub fn try_io( + &self, + interest: Interest, + f: impl FnOnce() -> Result, + ) -> Result { + inner!(self).try_io(interest, f) + } +} + +impl From for TcpStream { + fn from(x: TfoStream) -> Self { + TcpStream(x) + } +} + +impl From for TcpStream { + fn from(x: tokio::net::TcpStream) -> Self { + TcpStream(x.into()) + } +} + +impl AsyncRead for TcpStream { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut ReadBuf<'_>, + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_read(cx, buf) + } +} + +impl AsyncWrite for TcpStream { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_write(cx, buf) + } + + fn poll_flush( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_flush(cx) + } + + fn poll_shutdown( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + Pin::new(&mut self.get_mut().0).poll_shutdown(cx) + } +} + +#[cfg(target_os = "linux")] +mod linux_ext { + use super::*; + use std::os::unix::io::AsRawFd; + use std::os::unix::prelude::RawFd; + impl AsRawFd for TcpStream { + fn as_raw_fd(&self) -> RawFd { + self.0.as_raw_fd() + } + } +} diff --git a/src/relay/tcp/zio.rs b/src/relay/tcp/zio.rs new file mode 100644 index 00000000..db87a43e --- /dev/null +++ b/src/relay/tcp/zio.rs @@ -0,0 +1,418 @@ +use std::io::{Result, Error, ErrorKind}; +use std::pin::Pin; +use std::task::{Context, Poll}; +use std::future::Future; + +use futures::ready; + +use super::TcpStream; +use tokio::io::ReadBuf; +use tokio::io::{AsyncRead, AsyncWrite}; + +use crate::utils::DEFAULT_BUF_SIZE; +trait Require +where + Self: Sized, +{ + fn new() -> Result; + + fn poll_read_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll>; + + fn poll_write_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll>; + + fn poll_flush_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll>; +} + +struct CopyBuffer> { + read_done: bool, + need_flush: bool, + pos: usize, + cap: usize, + amt: u64, + buf: B, +} + +// use array buffer by default +impl Require for CopyBuffer { + fn new() -> Result { + Ok(Self { + read_done: false, + need_flush: false, + pos: 0, + cap: 0, + amt: 0, + buf: vec![0; DEFAULT_BUF_SIZE].into_boxed_slice(), + }) + } + + fn poll_read_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll> { + let mut buf = ReadBuf::new(&mut self.buf); + Pin::new(stream) + .poll_read(cx, &mut buf) + .map_ok(|_| buf.filled().len()) + } + + fn poll_write_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll> { + Pin::new(stream).poll_write(cx, &self.buf[self.pos..self.cap]) + } + + fn poll_flush_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll> { + Pin::new(stream).poll_flush(cx) + } +} + +impl CopyBuffer +where + CopyBuffer: Require, +{ + fn poll_copy( + &mut self, + cx: &mut Context<'_>, + r: &mut TcpStream, + w: &mut TcpStream, + ) -> Poll> { + loop { + // If our buffer is empty, then we need to read some data to + // continue. + if self.pos == self.cap && !self.read_done { + let n = match self.poll_read_tcp(cx, r) { + Poll::Ready(Ok(n)) => n, + Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), + Poll::Pending => { + // Try flushing when the reader has no progress to avoid deadlock + // when the reader depends on buffered writer. + if self.need_flush { + ready!(self.poll_flush_tcp(cx, w))?; + self.need_flush = false; + } + + return Poll::Pending; + } + }; + + if n == 0 { + self.read_done = true; + } else { + self.pos = 0; + self.cap = n; + } + } + + // If our buffer has some data, let's write it out! + while self.pos < self.cap { + let i = ready!(self.poll_write_tcp(cx, w))?; + + if i == 0 { + return Poll::Ready(Err(Error::new( + ErrorKind::WriteZero, + "write zero byte into writer", + ))); + } else { + self.pos += i; + self.amt += i as u64; + self.need_flush = true; + } + } + + // If pos larger than cap, this loop will never stop. + // In particular, user's wrong poll_write implementation returning + // incorrect written length may lead to thread blocking. + debug_assert!( + self.pos <= self.cap, + "writer returned length larger than input slice" + ); + + // If we've written all the data and we've seen EOF, flush out the + // data and finish the transfer. + if self.pos == self.cap && self.read_done { + ready!(self.poll_flush_tcp(cx, w))?; + return Poll::Ready(Ok(self.amt)); + } + } + } +} + +enum TransferState { + Running(CopyBuffer), + ShuttingDown(u64), + Done(u64), +} + +struct BidiCopy<'a, B> { + a: &'a mut TcpStream, + b: &'a mut TcpStream, + a_to_b: TransferState, + b_to_a: TransferState, +} + +fn transfer_one_direction( + cx: &mut Context<'_>, + state: &mut TransferState, + r: &mut TcpStream, + w: &mut TcpStream, +) -> Poll> +where + CopyBuffer: Require, +{ + loop { + match state { + TransferState::Running(buf) => { + let count = ready!(buf.poll_copy(cx, r, w))?; + *state = TransferState::ShuttingDown(count); + } + TransferState::ShuttingDown(count) => { + ready!(Pin::new(&mut *w).poll_shutdown(cx))?; + + *state = TransferState::Done(*count); + } + TransferState::Done(count) => return Poll::Ready(Ok(*count)), + } + } +} + +impl<'a, B> Future for BidiCopy<'a, B> +where + B: Unpin, + CopyBuffer: Require, +{ + type Output = Result<(u64, u64)>; + + fn poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll { + // Unpack self into mut refs to each field to avoid borrow check issues. + let BidiCopy { + a, + b, + a_to_b, + b_to_a, + } = &mut *self; + + let a_to_b = transfer_one_direction(cx, a_to_b, a, b)?; + let b_to_a = transfer_one_direction(cx, b_to_a, b, a)?; + + // It is not a problem if ready! returns early because transfer_one_direction for the + // other direction will keep returning TransferState::Done(count) in future calls to poll + let a_to_b = ready!(a_to_b); + let b_to_a = ready!(b_to_a); + + Poll::Ready(Ok((a_to_b, b_to_a))) + } +} + +// async fn bidi_copy( +// a: &mut TcpStream, +// b: &mut TcpStream, +// ) -> Result<(u64, u64)> +// where +// B: Unpin, +// CopyBuffer: Require, +// { +// let a_to_b = TransferState::Running(CopyBuffer::::new()?); +// let b_to_a = TransferState::Running(CopyBuffer::::new()?); +// BidiCopy { +// a, +// b, +// a_to_b, +// b_to_a, +// } +// .await +// } + +pub async fn bidi_copy_buffer( + a: &mut TcpStream, + b: &mut TcpStream, +) -> Result<(u64, u64)> { + let a_to_b = + TransferState::Running(CopyBuffer::>::new().unwrap()); + let b_to_a = + TransferState::Running(CopyBuffer::>::new().unwrap()); + BidiCopy { + a, + b, + a_to_b, + b_to_a, + } + .await +} + +#[cfg(all(target_os = "linux", feature = "zero-copy"))] +pub async fn bidi_copy_pipe( + a: &mut TcpStream, + b: &mut TcpStream, +) -> Result<(u64, u64)> { + use zero_copy::Pipe; + let a_to_b = TransferState::Running(CopyBuffer::::new()?); + let b_to_a = TransferState::Running(CopyBuffer::::new()?); + BidiCopy { + a, + b, + a_to_b, + b_to_a, + } + .await +} + +#[cfg(all(target_os = "linux", feature = "zero-copy"))] +mod zero_copy { + use super::*; + use std::ops::Drop; + use std::os::unix::io::{RawFd, AsRawFd}; + use tokio::io::Interest; + use crate::utils::DEFAULT_PIPE_CAP; + + pub struct Pipe(RawFd, RawFd); + + impl Drop for Pipe { + fn drop(&mut self) { + unsafe { + libc::close(self.0); + libc::close(self.1); + } + } + } + + impl Pipe { + fn create() -> Result { + use libc::{c_int, O_NONBLOCK}; + let mut pipe = std::mem::MaybeUninit::<[c_int; 2]>::uninit(); + unsafe { + if libc::pipe2(pipe.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 + { + Err(Error::last_os_error()) + } else { + Ok(Pipe(pipe.assume_init()[0], pipe.assume_init()[1])) + } + } + } + } + + #[inline] + fn splice_n(r: RawFd, w: RawFd, n: usize) -> isize { + use libc::{loff_t, SPLICE_F_MOVE, SPLICE_F_NONBLOCK}; + unsafe { + libc::splice( + r, + std::ptr::null_mut::(), + w, + std::ptr::null_mut::(), + n, + SPLICE_F_MOVE | SPLICE_F_NONBLOCK, + ) + } + } + + #[inline] + fn handle_wouldblock(is_wouldblock: &mut bool) -> Error { + use libc::{EWOULDBLOCK, EAGAIN}; + let err = Error::last_os_error(); + match err.raw_os_error() { + Some(e) if e == EWOULDBLOCK || e == EAGAIN => { + *is_wouldblock = true; + ErrorKind::WouldBlock.into() + } + _ => err, + } + } + + impl Require for CopyBuffer { + fn new() -> Result { + let pipe = Pipe::create()?; + Ok(CopyBuffer { + read_done: false, + need_flush: false, + pos: 0, + cap: 0, + amt: 0, + buf: pipe, + }) + } + + fn poll_read_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll> { + loop { + ready!(stream.poll_read_ready(cx))?; + + let mut is_wouldblock = false; + let res = + stream.try_io(Interest::READABLE, || { + match splice_n( + stream.as_raw_fd(), + self.buf.1, + DEFAULT_PIPE_CAP, + ) { + x if x >= 0 => Ok(x as usize), + _ => Err(handle_wouldblock(&mut is_wouldblock)), + } + }); + + if !is_wouldblock { + return Poll::Ready(res); + } + } + } + + fn poll_write_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll> { + loop { + ready!(stream.poll_write_ready(cx)?); + + let mut is_wouldblock = false; + let res = + stream.try_io(Interest::WRITABLE, || { + match splice_n( + self.buf.0, + stream.as_raw_fd(), + self.cap - self.pos, + ) { + x if x >= 0 => Ok(x as usize), + _ => Err(handle_wouldblock(&mut is_wouldblock)), + } + }); + + if !is_wouldblock { + return Poll::Ready(res); + } + } + } + + fn poll_flush_tcp( + &mut self, + cx: &mut Context<'_>, + stream: &mut TcpStream, + ) -> Poll> { + Pin::new(stream).poll_flush(cx) + } + } +} diff --git a/src/relay/udp.rs b/src/relay/udp.rs index d06bdda6..fc618f41 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -41,17 +41,20 @@ pub async fn proxy( let (n, client_addr) = match listen_sock.recv_from(&mut buf).await { Ok(x) => x, Err(e) => { - error!("failed to recv udp packet from client: {}", &e); + error!("[udp]failed to recvfrom client: {}", e); continue; } }; - debug!("recv udp packet from {}", &client_addr); + debug!("[udp]recvfrom client {}", &client_addr); let remote_addr = match remote.to_sockaddr().await { - Ok(x) => x, + Ok(x) => { + debug!("[udp]remote resolved as {}", &x); + x + } Err(e) => { - error!("failed to resolve remote addr: {}", &e); + error!("[udp]failed to resolve remote: {}", e); continue; } }; @@ -60,7 +63,10 @@ pub async fn proxy( let alloc_sock = match get_socket(&sock_map, &client_addr) { Some(x) => x, None => { - info!("new udp association for {}", &client_addr); + info!( + "[udp]new association {} => {}", + &client_addr, &remote_addr + ); alloc_new_socket( &sock_map, client_addr, @@ -74,7 +80,7 @@ pub async fn proxy( }; if let Err(e) = alloc_sock.send_to(&buf[..n], &remote_addr).await { - error!("failed to send udp packet to {}: {}", &remote_addr, &e); + error!("[udp]failed to sendto remote {}: {}", &remote_addr, e); } } @@ -95,7 +101,7 @@ async fn send_back( match timeoutfut(alloc_sock.recv_from(&mut buf), timeout).await { Ok(x) => x, Err(_) => { - info!("udp association for {} timeout", &client_addr); + debug!("[udp]association for {} timeout", &client_addr); break; } }; @@ -103,24 +109,21 @@ async fn send_back( let (n, remote_addr) = match res { Ok(x) => x, Err(e) => { - error!("failed to recv udp packet from remote: {}", &e); + error!("[udp]failed to recvfrom remote: {}", e); continue; } }; - debug!("recv udp packet from remote: {}", &remote_addr); + debug!("[udp]recvfrom remote {}", &remote_addr); if let Err(e) = listen_sock.send_to(&buf[..n], &client_addr).await { - error!( - "failed to send udp packet back to {}: {}", - &client_addr, &e - ); + error!("[udp]failed to sendto client{}: {}", &client_addr, e); continue; } } sock_map.write().unwrap().remove(&client_addr); - debug!("remove udp association for {}", &client_addr); + info!("[udp]remove association for {}", &client_addr); } #[inline] From 564c3a3ac38a720f1167d6726995f95fb68520b0 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 4 Feb 2022 23:57:52 +0900 Subject: [PATCH 111/169] rc10 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index c4a48bb9..189c4bb5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use cmd::CmdInput; use conf::{Config, FullConf, LogConf, DnsConf}; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc9"; +const VERSION: &str = "1.5.0-rc10"; const ENV_CONFIG: &str = "REALM_CONF"; cfg_if! { From 3c0cee7d5e75d12866d0088edb1ce4b4c6efcf3a Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 15 Feb 2022 01:45:23 +0900 Subject: [PATCH 112/169] new option: set nofile limit --- src/cmd/mod.rs | 24 ++++++++++++++++++++---- src/utils/mod.rs | 23 ++++++++++++++++++++++- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 2fce539c..d5672a09 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -51,34 +51,41 @@ fn add_flags(app: App) -> App { fn add_options(app: App) -> App { app.help_heading("OPTIONS").args(&[ + Arg::new("nofile") + .short('n') + .long("nofile") + .help("set nofile limit") + .value_name("limit") + .takes_value(true) + .display_order(0), Arg::new("config") .short('c') .long("config") .help("use config file") .value_name("path") .takes_value(true) - .display_order(0), + .display_order(1), Arg::new("local") .short('l') .long("listen") .help("listen address") .value_name("addr") .takes_value(true) - .display_order(1), + .display_order(2), Arg::new("remote") .short('r') .long("remote") .help("remote address") .value_name("addr") .takes_value(true) - .display_order(2), + .display_order(3), Arg::new("through") .short('x') .long("through") .help("send through ip or address") .value_name("addr") .takes_value(true) - .display_order(3), + .display_order(4), ]) } @@ -169,6 +176,15 @@ fn parse_matches(matches: ArgMatches) -> CmdInput { crate::utils::daemonize(); } + #[cfg(all(unix, not(target_os = "android")))] + if let Some(nofile) = matches.value_of("nofile") { + if let Ok(nofile) = nofile.parse::() { + crate::utils::set_nofile_limit(nofile); + } else { + eprintln!("invalid nofile value: {}", nofile); + } + } + let opts = parse_global_opts(&matches); if let Some(config) = matches.value_of("config") { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index ac89c154..c6b3a402 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -34,5 +34,26 @@ pub fn daemonize() { daemon .start() - .unwrap_or_else(|e| eprintln!("failed to daemonize, {}", e)); + .unwrap_or_else(|e| eprintln!("failed to daemonize: {}", e)); +} + +// refer to +// https://man7.org/linux/man-pages/man2/setrlimit.2.html +// https://github.com/shadowsocks/shadowsocks-rust/blob/master/crates/shadowsocks-service/src/sys/unix/mod.rs +#[cfg(all(unix, not(target_os = "android")))] +pub fn set_nofile_limit(nofile: u64) { + use libc::RLIMIT_NOFILE; + use libc::{rlimit, rlim_t}; + use std::io::Error; + + let lim = rlimit { + rlim_cur: nofile as rlim_t, + rlim_max: nofile as rlim_t, + }; + + if unsafe { libc::setrlimit(RLIMIT_NOFILE, &lim as *const _) } < 0 { + eprintln!("failed to set nofile limit: {}", Error::last_os_error()); + } else { + println!("set nofile limit to {}", nofile); + } } From a55abe2e99b1312aa09b6c593f307a5ebdf9542b Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 15 Feb 2022 02:03:16 +0900 Subject: [PATCH 113/169] show nofile limit when starting --- src/cmd/mod.rs | 21 ++++++++++++++++----- src/utils/mod.rs | 21 +++++++++++++++++++++ 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index d5672a09..4f92224d 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -177,11 +177,22 @@ fn parse_matches(matches: ArgMatches) -> CmdInput { } #[cfg(all(unix, not(target_os = "android")))] - if let Some(nofile) = matches.value_of("nofile") { - if let Ok(nofile) = nofile.parse::() { - crate::utils::set_nofile_limit(nofile); - } else { - eprintln!("invalid nofile value: {}", nofile); + { + use crate::utils::get_nofile_limit; + use crate::utils::set_nofile_limit; + + // get + if let Some((soft, hard)) = get_nofile_limit() { + println!("nofile limit: soft={}, hard={}", soft, hard); + } + + // set + if let Some(nofile) = matches.value_of("nofile") { + if let Ok(nofile) = nofile.parse::() { + set_nofile_limit(nofile); + } else { + eprintln!("invalid nofile value: {}", nofile); + } } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c6b3a402..79b4cd8c 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -57,3 +57,24 @@ pub fn set_nofile_limit(nofile: u64) { println!("set nofile limit to {}", nofile); } } + +// refer to +// https://man7.org/linux/man-pages/man2/setrlimit.2.html +#[cfg(all(unix, not(target_os = "android")))] +pub fn get_nofile_limit() -> Option<(u64, u64)> { + use libc::RLIMIT_NOFILE; + use libc::rlimit; + use std::io::Error; + + let mut lim = rlimit { + rlim_cur: 0, + rlim_max: 0, + }; + + if unsafe { libc::getrlimit(RLIMIT_NOFILE, &mut lim as *mut _) } < 0 { + eprintln!("failed to get nofile limit: {}", Error::last_os_error()); + return None; + }; + + Some((lim.rlim_cur as u64, lim.rlim_max as u64)) +} From aea46e8fd8b39f6874b4895ec1a351aefcd9307a Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 15 Feb 2022 03:34:23 +0900 Subject: [PATCH 114/169] new option: dns-min-ttl, dns-max-ttl, dns-cache-size; related: zephyrchien/midori#7 --- src/cmd/mod.rs | 26 ++++++++-- src/conf/dns.rs | 127 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 134 insertions(+), 19 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 4f92224d..286e123e 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -109,30 +109,48 @@ fn add_global_options(app: App) -> App { .value_name("mode") .takes_value(true) .display_order(2), + Arg::new("dns_min_ttl") + .long("dns-min-ttl") + .help("override dns min ttl") + .value_name("second") + .takes_value(true) + .display_order(3), + Arg::new("dns_max_ttl") + .long("dns-max-ttl") + .help("override dns max ttl") + .value_name("second") + .takes_value(true) + .display_order(4), + Arg::new("dns_cache_size") + .long("dns-cache-size") + .help("override dns cache size") + .value_name("number") + .takes_value(true) + .display_order(5), Arg::new("dns_protocol") .long("dns-protocol") .help("override dns protocol") .value_name("protocol") .takes_value(true) - .display_order(3), + .display_order(6), Arg::new("dns_servers") .long("dns-servers") .help("override dns servers") .value_name("servers") .takes_value(true) - .display_order(4), + .display_order(7), Arg::new("tcp_timeout") .long("tcp-timeout") .help("override tcp timeout") .value_name("second") .takes_value(true) - .display_order(5), + .display_order(8), Arg::new("udp_timeout") .long("udp-timeout") .help("override udp timeout") .value_name("second") .takes_value(true) - .display_order(6), + .display_order(9), ]) } diff --git a/src/conf/dns.rs b/src/conf/dns.rs index 263b051d..ccb09072 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -58,18 +58,14 @@ impl From for DnsMode { } #[cfg(feature = "trust-dns")] -impl From for ResolverOpts { +impl From for LookupIpStrategy { fn from(mode: DnsMode) -> Self { - let ip_strategy = match mode { + match mode { DnsMode::Ipv4Only => LookupIpStrategy::Ipv4Only, DnsMode::Ipv6Only => LookupIpStrategy::Ipv6Only, DnsMode::Ipv4AndIpv6 => LookupIpStrategy::Ipv4AndIpv6, DnsMode::Ipv4ThenIpv6 => LookupIpStrategy::Ipv4thenIpv6, DnsMode::Ipv6ThenIpv4 => LookupIpStrategy::Ipv6thenIpv4, - }; - ResolverOpts { - ip_strategy, - ..Default::default() } } } @@ -127,9 +123,22 @@ impl From for Vec { // dns config #[derive(Debug, Default, Serialize, Deserialize, Clone)] pub struct DnsConf { + // ResolverOpts #[serde(default)] pub mode: Option, + // MAX_TTL: u32 = 86400_u32 + // https://docs.rs/trust-dns-resolver/latest/src/trust_dns_resolver/dns_lru.rs.html#26 + #[serde(default)] + pub min_ttl: Option, + + #[serde(default)] + pub max_ttl: Option, + + #[serde(default)] + pub cache_size: Option, + + // ResolverConfig #[serde(default)] pub protocol: Option, @@ -139,21 +148,38 @@ pub struct DnsConf { impl Display for DnsConf { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + macro_rules! default { + ($ref: expr) => { + match $ref { + Some(x) => *x, + None => Default::default(), + } + }; + ($ref: expr, $value: expr) => { + match $ref { + Some(x) => *x, + None => $value, + } + }; + } let DnsConf { mode, + min_ttl, + max_ttl, + cache_size, protocol, nameservers, } = self; - let mode = match mode { - Some(m) => *m, - None => Default::default(), - }; + let mode = default!(mode); - let protocol = match protocol { - Some(x) => *x, - None => Default::default(), - }; + let min_ttl = default!(min_ttl, 0_u32); + + let max_ttl = default!(max_ttl, 86400_u32); + + let cache_size = default!(cache_size, 32_usize); + + let protocol = default!(protocol); let nameservers = match nameservers { Some(s) => s.join(", "), @@ -161,6 +187,12 @@ impl Display for DnsConf { }; write!(f, "mode={}, protocol={}, ", &mode, &protocol).unwrap(); + write!( + f, + "min-ttl={}, max-ttl={}, cache-size={}, ", + min_ttl, max_ttl, cache_size + ) + .unwrap(); write!(f, "servers={}", &nameservers) } } @@ -169,14 +201,58 @@ impl Config for DnsConf { type Output = (Option, Option); fn build(self) -> Self::Output { + use std::time::Duration; + let DnsConf { mode, protocol, nameservers, + min_ttl, + max_ttl, + cache_size, } = self; - let opts: Option = mode.map(|x| x.into()); + // parse into ResolverOpts + // default value: + // https://docs.rs/trust-dns-resolver/latest/src/trust_dns_resolver/config.rs.html#681-737 + + macro_rules! all_none { + ( $( $x: expr ),* ) => {{ + let mut res = true; + $( + res = res && $x.is_none(); + )* + res + }} + } + + let opts = if all_none![mode, min_ttl, max_ttl, cache_size] { + None + } else { + let ip_strategy: LookupIpStrategy = + mode.map(|x| x.into()).unwrap_or_default(); + + let positive_min_ttl = + min_ttl.map(|x| Duration::from_secs(x as u64)); + + let positive_max_ttl = + max_ttl.map(|x| Duration::from_secs(x as u64)); + + let cache_size = cache_size.unwrap_or({ + let ResolverOpts { cache_size, .. } = Default::default(); + cache_size + }); + + Some(ResolverOpts { + ip_strategy, + positive_min_ttl, + positive_max_ttl, + cache_size, + ..Default::default() + }) + }; + // parse into ResolverConfig let protocol = protocol.unwrap_or_default(); if nameservers.is_none() && (protocol == DnsProtocol::default()) { return (None, opts); @@ -217,6 +293,9 @@ impl Config for DnsConf { use crate::rst; let other = other.clone(); rst!(self, mode, other); + rst!(self, min_ttl, other); + rst!(self, max_ttl, other); + rst!(self, cache_size, other); rst!(self, protocol, other); rst!(self, nameservers, other); self @@ -226,6 +305,9 @@ impl Config for DnsConf { use crate::take; let other = other.clone(); take!(self, mode, other); + take!(self, min_ttl, other); + take!(self, max_ttl, other); + take!(self, cache_size, other); take!(self, protocol, other); take!(self, nameservers, other); self @@ -234,6 +316,18 @@ impl Config for DnsConf { fn from_cmd_args(matches: &clap::ArgMatches) -> Self { let mode = matches.value_of("dns_mode").map(|x| String::from(x).into()); + let min_ttl = matches + .value_of("dns_min_ttl") + .map(|x| x.parse::().unwrap()); + + let max_ttl = matches + .value_of("dns_max_ttl") + .map(|x| x.parse::().unwrap()); + + let cache_size = matches + .value_of("dns_cache_size") + .map(|x| x.parse::().unwrap()); + let protocol = matches .value_of("dns_protocol") .map(|x| String::from(x).into()); @@ -244,6 +338,9 @@ impl Config for DnsConf { Self { mode, + min_ttl, + max_ttl, + cache_size, protocol, nameservers, } From 50024531b9a711f9d56a65fc617c5e96d41c8641 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 15 Feb 2022 03:57:13 +0900 Subject: [PATCH 115/169] docs --- conf_tree | 55 ++++++++++++++++++++++++++++++++++++++++++++ readme.md | 62 +++++++++++++++++++++++++++++++------------------- src/cmd/mod.rs | 27 +++++++++++++++------- 3 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 conf_tree diff --git a/conf_tree b/conf_tree new file mode 100644 index 00000000..af1dd6bd --- /dev/null +++ b/conf_tree @@ -0,0 +1,55 @@ + +# +## log +### level +### output +## dns +### mode +### protocol +### nameservers +### min_ttl +### max_ttl +### cache_size +## network +### use_udp +### zero_copy +### fast_open +### tcp_timeout +### udp_timeout +## endpoints +### listen +### remote +### through +### network +#### use_udp +#### zero_copy +#### fast_open +#### tcp_timeout +#### udp_timeout + +├── log +│ ├── level +│ └── output +├── dns +│ ├── mode +│ ├── protocol +│ ├── nameservers +│ ├── min_ttl +│ ├── max_ttl +│ └── cache_size +├── network +│ ├── use_udp +│ ├── zero_copy +│ ├── fast_open +│ ├── tcp_timeout +│ └── udp_timeout +└── endpoints + ├── listen + ├── remote + ├── through + └── network + ├── use_udp + ├── zero_copy + ├── fast_open + ├── tcp_timeout + └── udp_timeout diff --git a/readme.md b/readme.md index 80ab74d9..d405b2a5 100644 --- a/readme.md +++ b/readme.md @@ -71,7 +71,6 @@ Using [Cross](https://github.com/cross-rs/cross) is also a simple and good enoug ```shell Realm 1.5.x [udp][zero-copy][trust-dns][multi-thread] - A high efficiency relay tool USAGE: @@ -86,19 +85,27 @@ FLAGS: -z, --splice force enable tcp zero copy OPTIONS: + -n, --nofile set nofile limit -c, --config use config file -l, --listen listen address -r, --remote remote address -x, --through send through ip or address -GLOBAL OPTIONS: - --log-level override log level - --log-output override log output +LOG OPTIONS: + --log-level override log level + --log-output override log output + +DNS OPTIONS: --dns-mode override dns mode + --dns-min-ttl override dns min ttl + --dns-max-ttl override dns max ttl + --dns-cache-size override dns cache size --dns-protocol override dns protocol --dns-servers override dns servers - --tcp-timeout override tcp timeout - --udp-timeout override udp timeout + +TIMEOUT OPTIONS: + --tcp-timeout override tcp timeout + --udp-timeout override udp timeout ``` start from command line arguments: @@ -196,26 +203,29 @@ through = "0.0.0.0" ```shell ├── log -│   ├── level -│   └── output +│ ├── level +│ └── output ├── dns -│   ├── mode -│   ├── protocol -│   └── nameservers +│ ├── mode +│ ├── protocol +│ ├── nameservers +│ ├── min_ttl +│ ├── max_ttl +│ └── cache_size ├── network -│   ├── use_udp -│   ├── fast_open -│   ├── zero_copy -│   ├── tcp_timeout -│   └── udp_timeout +│ ├── use_udp +│ ├── zero_copy +│ ├── fast_open +│ ├── tcp_timeout +│ └── udp_timeout └── endpoints ├── listen ├── remote ├── through └── network ├── use_udp - ├── fast_open ├── zero_copy + ├── fast_open ├── tcp_timeout └── udp_timeout ``` @@ -224,7 +234,6 @@ You should provide at least `endpoint.listen` and `endpoint.remote`, other field Priority: cmd override > endpoint config > global config ---- ### log @@ -242,8 +251,6 @@ Priority: cmd override > endpoint config > global config - stderr - path, e.g. (`/var/log/realm.log`) ---- - ### dns #### dns.mode @@ -266,11 +273,21 @@ format: ["server1", "server2" ...] default: -On **unix/windows**, it will read from the default location.(e.g. `/etc/resolv.conf`). +If on **unix/windows**, read from the default location.(e.g. `/etc/resolv.conf`). Otherwise, use google's public dns(`8.8.8.8:53`, `8.8.4.4:53` and `2001:4860:4860::8888:53`, `2001:4860:4860::8844:53`). ---- +#### dns.min_ttl + +default: 0 + +#### dns.max_ttl + +default: 86400 (1 day) + +#### cache_size + +default: 32 ### network @@ -282,7 +299,6 @@ Otherwise, use google's public dns(`8.8.8.8:53`, `8.8.4.4:53` and `2001:4860:486 To disable timeout, you need to explicitly set timeout value to 0 ---- ### endpoint diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 286e123e..40bad633 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -90,7 +90,8 @@ fn add_options(app: App) -> App { } fn add_global_options(app: App) -> App { - app.help_heading("GLOBAL OPTIONS").args(&[ + // log + let app = app.help_heading("LOG OPTIONS").args(&[ Arg::new("log_level") .long("log-level") .help("override log level") @@ -103,42 +104,50 @@ fn add_global_options(app: App) -> App { .value_name("path") .takes_value(true) .display_order(1), + ]); + + // dns + let app = app.help_heading("DNS OPTIONS").args(&[ Arg::new("dns_mode") .long("dns-mode") .help("override dns mode") .value_name("mode") .takes_value(true) - .display_order(2), + .display_order(0), Arg::new("dns_min_ttl") .long("dns-min-ttl") .help("override dns min ttl") .value_name("second") .takes_value(true) - .display_order(3), + .display_order(1), Arg::new("dns_max_ttl") .long("dns-max-ttl") .help("override dns max ttl") .value_name("second") .takes_value(true) - .display_order(4), + .display_order(2), Arg::new("dns_cache_size") .long("dns-cache-size") .help("override dns cache size") .value_name("number") .takes_value(true) - .display_order(5), + .display_order(3), Arg::new("dns_protocol") .long("dns-protocol") .help("override dns protocol") .value_name("protocol") .takes_value(true) - .display_order(6), + .display_order(4), Arg::new("dns_servers") .long("dns-servers") .help("override dns servers") .value_name("servers") .takes_value(true) - .display_order(7), + .display_order(5), + ]); + + // network + let app = app.help_heading("TIMEOUT OPTIONS").args([ Arg::new("tcp_timeout") .long("tcp-timeout") .help("override tcp timeout") @@ -151,7 +160,9 @@ fn add_global_options(app: App) -> App { .value_name("second") .takes_value(true) .display_order(9), - ]) + ]); + + app } pub fn scan() -> CmdInput { From c7b680e43847a61a283711d998e7e84f7e9b4fd0 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 15 Feb 2022 04:02:59 +0900 Subject: [PATCH 116/169] rc11 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 189c4bb5..91db73a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use cmd::CmdInput; use conf::{Config, FullConf, LogConf, DnsConf}; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc10"; +const VERSION: &str = "1.5.0-rc11"; const ENV_CONFIG: &str = "REALM_CONF"; cfg_if! { From 2b2385485b1a412a6938c7659edfb469a34ba901 Mon Sep 17 00:00:00 2001 From: zephyr Date: Tue, 15 Feb 2022 04:13:47 +0900 Subject: [PATCH 117/169] add examples --- examples/basic.json | 12 +++++++++ examples/basic.toml | 7 +++++ examples/full.json | 36 +++++++++++++++++++++++++ examples/full.toml | 28 +++++++++++++++++++ readme.md | 66 ++++++++++++++++++++++++++------------------- 5 files changed, 122 insertions(+), 27 deletions(-) create mode 100644 examples/basic.json create mode 100644 examples/basic.toml create mode 100644 examples/full.json create mode 100644 examples/full.toml diff --git a/examples/basic.json b/examples/basic.json new file mode 100644 index 00000000..fc0ce077 --- /dev/null +++ b/examples/basic.json @@ -0,0 +1,12 @@ +{ + "endpoints": [ + { + "listen": "0.0.0.0:5000", + "remote": "1.1.1.1:443" + }, + { + "listen": "0.0.0.0:10000", + "remote": "www.google.com:443" + } + ] +} diff --git a/examples/basic.toml b/examples/basic.toml new file mode 100644 index 00000000..6f71f2ec --- /dev/null +++ b/examples/basic.toml @@ -0,0 +1,7 @@ +[[endpoints]] +listen = "0.0.0.0:5000" +remote = "1.1.1.1:443" + +[[endpoints]] +listen = "0.0.0.0:10000" +remote = "www.google.com:443" diff --git a/examples/full.json b/examples/full.json new file mode 100644 index 00000000..52e695e6 --- /dev/null +++ b/examples/full.json @@ -0,0 +1,36 @@ +{ + "log": { + "level": "warn", + "output": "/var/log/realm.log" + }, + "dns": { + "mode": "ipv4_only", + "protocol": "tcp_and_udp", + "nameservers": [ + "8.8.8.8:53", + "8.8.4.4:53" + ], + "min_ttl": 600, + "max_ttl": 3600, + "cache_size": 256 + }, + "network": { + "use_udp": true, + "zero_copy": true, + "fast_open": true, + "tcp_timeout": 300, + "udp_timeout": 30 + }, + "endpoints": [ + { + "listen": "0.0.0.0:5000", + "remote": "1.1.1.1:443", + "through": "0.0.0.0" + }, + { + "listen": "0.0.0.0:10000", + "remote": "www.google.com:443", + "through": "0.0.0.0" + } + ] +} diff --git a/examples/full.toml b/examples/full.toml new file mode 100644 index 00000000..ed8a5ca6 --- /dev/null +++ b/examples/full.toml @@ -0,0 +1,28 @@ +[log] +level = "warn" +output = "/var/log/realm.log" + +[dns] +mode = "ipv4_only" +protocol = "tcp_and_udp" +nameservers = ["8.8.8.8:53", "8.8.4.4:53"] +min_ttl = 600 +max_ttl = 3600 +cache_size = 256 + +[network] +use_udp = true +zero_copy = true +fast_open = true +tcp_timeout = 300 +udp_timeout = 30 + +[[endpoints]] +listen = "0.0.0.0:5000" +remote = "1.1.1.1:443" +through = "0.0.0.0" + +[[endpoints]] +listen = "0.0.0.0:10000" +remote = "www.google.com:443" +through = "0.0.0.0" diff --git a/readme.md b/readme.md index d405b2a5..114153c3 100644 --- a/readme.md +++ b/readme.md @@ -146,6 +146,9 @@ output = "/var/log/realm.log" mode = "ipv4_only" protocol = "tcp_and_udp" nameservers = ["8.8.8.8:53", "8.8.4.4:53"] +min_ttl = 600 +max_ttl = 3600 +cache_size = 256 [network] use_udp = true @@ -157,44 +160,53 @@ udp_timeout = 30 [[endpoints]] listen = "0.0.0.0:5000" remote = "1.1.1.1:443" +through = "0.0.0.0" [[endpoints]] listen = "0.0.0.0:10000" remote = "www.google.com:443" through = "0.0.0.0" + ```
JSON Example
 {
-	"log": {
-		"level": "warn",
-		"output": "/var/log/realm.log"
-	},
-	"dns": {
-		"mode": "ipv4_only",
-		"protocol": "tcp_and_udp",
-		"nameservers": ["8.8.8.8:53", "8.8.4.4:53"]
-	},
-	"network": {
-		"use_udp": true,
-		"fast_open": true,
-		"zero_copy": true,
-		"tcp_timeout": 300,
-		"udp_timeout": 30,
-	},
-	"endpoints": [
-		{
-			"listen": "0.0.0.0:5000",
-			"remote": "1.1.1.1:443"
-		},
-		{
-			"listen": "0.0.0.0:10000",
-			"remote": "www.google.com:443",
-			"through": "0.0.0.0"
-		}
-	]
+  "log": {
+    "level": "warn",
+    "output": "/var/log/realm.log"
+  },
+  "dns": {
+    "mode": "ipv4_only",
+    "protocol": "tcp_and_udp",
+    "nameservers": [
+      "8.8.8.8:53",
+      "8.8.4.4:53"
+    ],
+    "min_ttl": 600,
+    "max_ttl": 3600,
+    "cache_size": 256
+  },
+  "network": {
+    "use_udp": true,
+    "zero_copy": true,
+    "fast_open": true,
+    "tcp_timeout": 300,
+    "udp_timeout": 30
+  },
+  "endpoints": [
+    {
+      "listen": "0.0.0.0:5000",
+      "remote": "1.1.1.1:443",
+      "through": "0.0.0.0"
+    },
+    {
+      "listen": "0.0.0.0:10000",
+      "remote": "www.google.com:443",
+      "through": "0.0.0.0"
+    }
+  ]
 }
 
From f4ed4056f78670a1ec3d9f116a9e318c1dc66756 Mon Sep 17 00:00:00 2001 From: zephyr Date: Thu, 17 Feb 2022 03:16:51 +0900 Subject: [PATCH 118/169] fix dependency issue when features disabled --- Cargo.toml | 6 ++---- src/conf/dns.rs | 10 ++++++++++ 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index ad53676f..15c674ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ edition = "2021" cfg-if = "1" futures = "0.3" log = "0.4" +libc = "0.2" clap = "3" toml = "0.5" @@ -25,9 +26,6 @@ lazy_static = "1" # tfo tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.9", optional = true } -# zero-copy -libc = { version = "0.2", optional = true } - # logger chrono = "0.4" fern = "0.6" @@ -54,7 +52,7 @@ opt-level = 0 default = ["udp", "zero-copy", "trust-dns", "multi-thread" ] udp = [] tfo = ["tokio-tfo"] -zero-copy = ["libc"] +zero-copy = [] trust-dns = ["trust-dns-resolver"] multi-thread = ["tokio/rt-multi-thread"] jemalloc = ["jemallocator"] diff --git a/src/conf/dns.rs b/src/conf/dns.rs index ccb09072..44d36f85 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -198,8 +198,18 @@ impl Display for DnsConf { } impl Config for DnsConf { + #[cfg(feature = "trust-dns")] type Output = (Option, Option); + #[cfg(not(feature = "trust-dns"))] + type Output = (); + + #[cfg(not(feature = "trust-dns"))] + fn build(self) -> Self::Output { + unreachable!() + } + + #[cfg(feature = "trust-dns")] fn build(self) -> Self::Output { use std::time::Duration; From b77f7cb2b767ac328615d3cc030a6083ad274f59 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 13:37:01 +0900 Subject: [PATCH 119/169] add proxy-protocol support --- Cargo.lock | 39 +++++ Cargo.toml | 7 +- src/cmd/mod.rs | 24 +++- src/conf/net.rs | 41 +++++- src/relay/mod.rs | 2 +- src/relay/tcp/haproxy.rs | 297 +++++++++++++++++++++++++++++++++++++++ src/relay/tcp/mod.rs | 15 +- src/utils/consts.rs | 7 + src/utils/types.rs | 32 +++-- 9 files changed, 446 insertions(+), 18 deletions(-) create mode 100644 src/relay/tcp/haproxy.rs diff --git a/Cargo.lock b/Cargo.lock index 00ac7ac0..0477f2c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,12 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "doc-comment" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" + [[package]] name = "enum-as-inner" version = "0.3.3" @@ -613,6 +619,16 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "proxy-protocol" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e50c72c21c738f5c5f350cc33640aee30bf7cd20f9d9da20ed41bce2671d532" +dependencies = [ + "bytes", + "snafu", +] + [[package]] name = "quick-error" version = "1.2.3" @@ -672,6 +688,7 @@ dependencies = [ name = "realm" version = "1.5.0" dependencies = [ + "bytes", "cfg-if", "chrono", "clap", @@ -685,6 +702,7 @@ dependencies = [ "log", "mimalloc", "pin-project", + "proxy-protocol", "serde", "serde_json", "tokio", @@ -784,6 +802,27 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +[[package]] +name = "snafu" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7" +dependencies = [ + "doc-comment", + "snafu-derive", +] + +[[package]] +name = "snafu-derive" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "socket2" version = "0.3.19" diff --git a/Cargo.toml b/Cargo.toml index 15c674ea..5b5034fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ cfg-if = "1" futures = "0.3" log = "0.4" libc = "0.2" +bytes = "1" clap = "3" toml = "0.5" @@ -30,6 +31,9 @@ tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", chrono = "0.4" fern = "0.6" +# haproxy proxy-protocol +haproxy = { package = "proxy-protocol", version = "0.5", optional = true } + # malloc mimalloc = { version = "0.1", optional = true } @@ -49,11 +53,12 @@ panic = "abort" opt-level = 0 [features] -default = ["udp", "zero-copy", "trust-dns", "multi-thread" ] +default = ["udp", "zero-copy", "trust-dns", "multi-thread", "proxy-protocol"] udp = [] tfo = ["tokio-tfo"] zero-copy = [] trust-dns = ["trust-dns-resolver"] +proxy-protocol = ["haproxy"] multi-thread = ["tokio/rt-multi-thread"] jemalloc = ["jemallocator"] mi-malloc = ["mimalloc"] diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 40bad633..a4dcb595 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -146,20 +146,38 @@ fn add_global_options(app: App) -> App { .display_order(5), ]); - // network + // proxy-protocol belogs to network + let app = app.help_heading("PROXY OPTIONS").args([ + Arg::new("send_proxy") + .long("send-proxy") + .help("send haproxy proxy protocol") + .display_order(0), + Arg::new("accept_proxy") + .long("accept-proxy") + .help("accept haproxy proxy protocol") + .display_order(1), + Arg::new("send_proxy_version") + .long("send-proxy-version") + .help("haproxy proxy protocol version") + .value_name("version") + .takes_value(true) + .display_order(2), + ]); + + // timeout belogs to network let app = app.help_heading("TIMEOUT OPTIONS").args([ Arg::new("tcp_timeout") .long("tcp-timeout") .help("override tcp timeout") .value_name("second") .takes_value(true) - .display_order(8), + .display_order(0), Arg::new("udp_timeout") .long("udp-timeout") .help("override udp timeout") .value_name("second") .takes_value(true) - .display_order(9), + .display_order(1), ]); app diff --git a/src/conf/net.rs b/src/conf/net.rs index a058ef84..f444ef3d 100644 --- a/src/conf/net.rs +++ b/src/conf/net.rs @@ -1,7 +1,8 @@ use serde::{Serialize, Deserialize}; use super::Config; -use crate::utils::ConnectOpts; +use crate::utils::{ConnectOpts, HaproxyOpts}; use crate::utils::{TCP_TIMEOUT, UDP_TIMEOUT}; +use crate::utils::PROXY_PROTOCOL_VERSION; #[derive(Serialize, Debug, Deserialize, Clone, Copy, Default)] pub struct NetConf { @@ -14,6 +15,15 @@ pub struct NetConf { #[serde(default)] pub zero_copy: Option, + #[serde(default)] + pub send_proxy: Option, + + #[serde(default)] + pub accept_proxy: Option, + + #[serde(default)] + pub send_proxy_version: Option, + #[serde(default)] pub tcp_timeout: Option, @@ -35,11 +45,20 @@ impl Config for NetConf { } let use_udp = unbox!(use_udp); + let fast_open = unbox!(fast_open); let zero_copy = unbox!(zero_copy); + let tcp_timeout = unbox!(tcp_timeout, TCP_TIMEOUT); let udp_timeout = unbox!(udp_timeout, UDP_TIMEOUT); + let accept_proxy = unbox!(accept_proxy) as usize; + let send_proxy = if unbox!(send_proxy) { + unbox!(send_proxy_version, PROXY_PROTOCOL_VERSION) + } else { + 0_usize + }; + ConnectOpts { use_udp, fast_open, @@ -47,6 +66,10 @@ impl Config for NetConf { tcp_timeout, udp_timeout, send_through: None, + haproxy_opts: HaproxyOpts { + send_proxy, + accept_proxy, + }, } } @@ -59,6 +82,9 @@ impl Config for NetConf { rst!(self, zero_copy, other); rst!(self, tcp_timeout, other); rst!(self, udp_timeout, other); + rst!(self, send_proxy, other); + rst!(self, accept_proxy, other); + rst!(self, send_proxy_version, other); self } @@ -71,11 +97,15 @@ impl Config for NetConf { take!(self, zero_copy, other); take!(self, tcp_timeout, other); take!(self, udp_timeout, other); + take!(self, send_proxy, other); + take!(self, accept_proxy, other); + take!(self, send_proxy_version, other); self } fn from_cmd_args(matches: &clap::ArgMatches) -> Self { let use_udp = matches.is_present("use_udp"); + let fast_open = matches.is_present("fast_open"); let zero_copy = matches.is_present("zero_copy"); @@ -86,6 +116,12 @@ impl Config for NetConf { .value_of("udp_timeout") .map(|x| x.parse::().unwrap()); + let send_proxy = matches.is_present("send_proxy"); + let accept_proxy = matches.is_present("accept_proxy"); + let send_proxy_version = matches + .value_of("send_proxy_version") + .map(|x| x.parse::().unwrap()); + const fn bool_to_opt(b: bool) -> Option { if b { Some(true) @@ -100,6 +136,9 @@ impl Config for NetConf { zero_copy: bool_to_opt(zero_copy), tcp_timeout, udp_timeout, + send_proxy: bool_to_opt(send_proxy), + accept_proxy: bool_to_opt(accept_proxy), + send_proxy_version, } } } diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 20daeb50..a42a8497 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -48,7 +48,7 @@ async fn proxy_tcp(ep: Endpoint) { "[tcp]{} finish, upload: {}b, download: {}b", msg, up, dl ), - Err(e) => error!("[tcp]{} error: {}", msg, e), + Err(e) => error!("[tcp]{}, error: {}", msg, e), } }); } diff --git a/src/relay/tcp/haproxy.rs b/src/relay/tcp/haproxy.rs new file mode 100644 index 00000000..43be7c3d --- /dev/null +++ b/src/relay/tcp/haproxy.rs @@ -0,0 +1,297 @@ +use std::io::{Error, ErrorKind, Result}; +use std::mem::MaybeUninit; +use std::net::SocketAddr; + +use log::{info, debug}; +use bytes::{BytesMut, Buf}; + +use haproxy::ProxyHeader; +use haproxy::{version1 as v1, version2 as v2}; +use haproxy::{encode, parse}; + +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use super::TcpStream; +use crate::utils; +use crate::utils::HaproxyOpts; + +// client -> relay -> server + +pub async fn handle_proxy_protocol( + src: &mut TcpStream, + dst: &mut TcpStream, + opts: HaproxyOpts, +) -> Result<()> { + let HaproxyOpts { + send_proxy, + accept_proxy, + } = opts; + + let mut client_addr = MaybeUninit::::uninit(); + let mut server_addr = MaybeUninit::::uninit(); + + // buf may not be used + let mut buf = MaybeUninit::::uninit(); + + // with src and dst got from header + let mut fwd_hdr = false; + + // parse PROXY header from client and write log + // may not get src and dst addr + if accept_proxy != 0 { + let buf = buf.write(BytesMut::with_capacity(256)); + + // FIXME: may not read the entire header + let n = src.read_buf(buf).await?; + let _ = buf.split_off(n); + debug!("[tcp]recv initial {} bytes: {:?}", n, buf); + + let header = parse(buf).map_err(|e| Error::new(ErrorKind::Other, e))?; + debug!("[tcp]proxy-protocol parsed, {} bytes left", buf.remaining()); + + if let Some((src, dst)) = handle_header(header) { + client_addr.write(src); + server_addr.write(dst); + fwd_hdr = true; + } + + // header has been parsed + // do not send header to server + if send_proxy != 1 && send_proxy != 2 { + // write left bytes + if !buf.is_empty() { + debug!("[tcp]send left {} bytes: {:?}", buf.len(), buf); + dst.write_all(buf).await?; + } + return Ok(()); + } + } + + // use real addr + if !fwd_hdr { + client_addr.write(src.peer_addr()?); + // FIXME: what is the dst addr here? seems not defined in the doc + // the doc only mentions that this field is similar to X-Origin-To + // which is seldom used + server_addr.write(match unsafe { client_addr.assume_init_ref() } { + SocketAddr::V4(_) => utils::new_sockaddr_v4(), + SocketAddr::V6(_) => utils::new_sockaddr_v6(), + }); + } + + // Safety: sockaddr is always initialized + // either parse from PROXY header or use real addr + let client_addr = unsafe { client_addr.assume_init() }; + let server_addr = unsafe { server_addr.assume_init() }; + + // write header + let header = encode(make_header(client_addr, server_addr, send_proxy)) + .map_err(|e| Error::new(ErrorKind::Other, e))?; + debug!("[tcp]send initial {} bytes: {:?}", header.len(), &header); + dst.write_all(&header).await?; + + // write left bytes + // Safety: buf is initialized, filled with PROXY header + if accept_proxy != 0 { + let buf = unsafe { buf.assume_init() }; + if !buf.is_empty() { + debug!("[tcp]send left {} bytes: {:?}", buf.len(), &buf); + dst.write_all(&buf).await?; + } + } + + Ok(()) +} + +macro_rules! unpack { + ($addr: expr, sin4) => { + match $addr { + SocketAddr::V4(x) => x, + _ => unreachable!(), + } + }; + ($addr: expr, sin6) => { + match $addr { + SocketAddr::V6(x) => x, + _ => unreachable!(), + } + }; +} + +fn make_header( + client_addr: SocketAddr, + server_addr: SocketAddr, + send_proxy: usize, +) -> ProxyHeader { + match send_proxy { + 2 => make_header_v2(client_addr, server_addr), + 1 => make_header_v1(client_addr, server_addr), + _ => unreachable!(), + } +} + +fn make_header_v1( + client_addr: SocketAddr, + server_addr: SocketAddr, +) -> ProxyHeader { + debug!( + "[tcp]send proxy-protocol-v1: {} => {}", + &client_addr, &server_addr + ); + + if client_addr.is_ipv4() { + ProxyHeader::Version1 { + addresses: v1::ProxyAddresses::Ipv4 { + source: unpack!(client_addr, sin4), + destination: unpack!(server_addr, sin4), + }, + } + } else { + ProxyHeader::Version1 { + addresses: v1::ProxyAddresses::Ipv6 { + source: unpack!(client_addr, sin6), + destination: unpack!(server_addr, sin6), + }, + } + } +} + +fn make_header_v2( + client_addr: SocketAddr, + server_addr: SocketAddr, +) -> ProxyHeader { + debug!( + "[tcp]send proxy-protocol-v2: {} => {}", + &client_addr, &server_addr + ); + + ProxyHeader::Version2 { + command: v2::ProxyCommand::Proxy, + transport_protocol: v2::ProxyTransportProtocol::Stream, + addresses: if client_addr.is_ipv4() { + v2::ProxyAddresses::Ipv4 { + source: unpack!(client_addr, sin4), + destination: unpack!(server_addr, sin4), + } + } else { + v2::ProxyAddresses::Ipv6 { + source: unpack!(client_addr, sin6), + destination: unpack!(server_addr, sin6), + } + }, + } +} + +fn handle_header(header: ProxyHeader) -> Option<(SocketAddr, SocketAddr)> { + use ProxyHeader::{Version1, Version2}; + match header { + Version1 { addresses } => handle_header_v1(addresses), + Version2 { + command, + transport_protocol, + addresses, + } => handle_header_v2(command, transport_protocol, addresses), + _ => { + info!("[tcp]accept proxy-protocol-v?"); + None + } + } +} + +fn handle_header_v1( + addr: v1::ProxyAddresses, +) -> Option<(SocketAddr, SocketAddr)> { + use v1::ProxyAddresses::*; + match addr { + Unknown => { + info!("[tcp]accept proxy-protocol-v1: unknown"); + None + } + Ipv4 { + source, + destination, + } => { + info!( + "[tcp]accept proxy-protocol-v1: {} => {}", + &source, &destination + ); + Some((SocketAddr::V4(source), SocketAddr::V4(destination))) + } + Ipv6 { + source, + destination, + } => { + info!( + "[tcp]accept proxy-protocol-v1: {} => {}", + &source, &destination + ); + Some((SocketAddr::V6(source), SocketAddr::V6(destination))) + } + } +} + +fn handle_header_v2( + cmd: v2::ProxyCommand, + proto: v2::ProxyTransportProtocol, + addr: v2::ProxyAddresses, +) -> Option<(SocketAddr, SocketAddr)> { + use v2::ProxyCommand as Command; + use v2::ProxyAddresses as Address; + use v2::ProxyTransportProtocol as Protocol; + + // The connection endpoints are the sender and the receiver. + // Such connections exist when the proxy sends health-checks to the server. + // The receiver must accept this connection as valid and must use the + // real connection endpoints and discard the protocol block including the + // family which is ignored + if let Command::Local = cmd { + info!("[tcp]accept proxy-protocol-v2: command = LOCAL, ignore"); + return None; + } + + // only get tcp address + match proto { + Protocol::Stream => {} + Protocol::Unspec => { + info!("[tcp]accept proxy-protocol-v2: protocol = UNSPEC, ignore"); + return None; + } + Protocol::Datagram => { + info!("[tcp]accept proxy-protocol-v2: protocol = DGRAM, ignore"); + return None; + } + } + + match addr { + Address::Ipv4 { + source, + destination, + } => { + info!( + "[tcp]accept proxy-protocol-v2: {} => {}", + &source, &destination + ); + Some((SocketAddr::V4(source), SocketAddr::V4(destination))) + } + Address::Ipv6 { + source, + destination, + } => { + info!( + "[tcp]accept proxy-protocol-v2: {} => {}", + &source, &destination + ); + Some((SocketAddr::V6(source), SocketAddr::V6(destination))) + } + Address::Unspec => { + info!( + "[tcp]accept proxy-protocol-v2: af_family = AF_UNSPEC, ignore" + ); + None + } + Address::Unix { .. } => { + info!("[tcp]accept proxy-protocol-v2: af_family = AF_UNIX, ignore"); + None + } + } +} diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index c7b8852e..1ad42302 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -1,7 +1,9 @@ mod zio; - use cfg_if::cfg_if; +#[cfg(feature = "proxy-protocol")] +mod haproxy; + cfg_if! { if #[cfg(feature = "tfo")] { mod tfo; @@ -40,6 +42,7 @@ pub async fn proxy( fast_open, zero_copy, send_through, + haproxy_opts, .. } = conn_opts; @@ -83,6 +86,16 @@ pub async fn proxy( setsockopt_warn!(inbound.set_nodelay(true), "nodelay"); setsockopt_warn!(outbound.set_nodelay(true), "nodelay"); + #[cfg(feature = "proxy-protocol")] + if haproxy_opts.send_proxy != 0 || haproxy_opts.accept_proxy != 0 { + haproxy::handle_proxy_protocol( + &mut inbound, + &mut outbound, + haproxy_opts, + ) + .await?; + } + #[cfg(all(target_os = "linux", feature = "zero-copy"))] let res = if zero_copy { zio::bidi_copy_pipe(&mut inbound, &mut outbound).await diff --git a/src/utils/consts.rs b/src/utils/consts.rs index 8795ca37..e720aedb 100644 --- a/src/utils/consts.rs +++ b/src/utils/consts.rs @@ -7,6 +7,9 @@ pub const DEFAULT_LOG_FILE: &str = "stdout"; pub const TCP_TIMEOUT: u64 = 300; pub const UDP_TIMEOUT: u64 = 30; +// default haproxy proxy-protocol version +pub const PROXY_PROTOCOL_VERSION: usize = 2; + // https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1 pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { 512 @@ -29,6 +32,7 @@ def_feat!(FEATURE_UDP, "udp"); def_feat!(FEATURE_TFO, "tfo"); def_feat!(FEATURE_ZERO_COPY, "zero-copy"); def_feat!(FEATURE_TRUST_DNS, "trust-dns"); +def_feat!(FEATURE_PROXY_PROTOCOL, "proxy-protocol"); def_feat!(FEATURE_MIMALLOC, "mi-malloc"); def_feat!(FEATURE_JEMALLOC, "jemalloc"); def_feat!(FEATURE_MULTI_THREAD, "multi-thread"); @@ -41,6 +45,7 @@ pub struct Features { pub mimalloc: bool, pub jemalloc: bool, pub multi_thread: bool, + pub proxy_protocol: bool, } pub const FEATURES: Features = Features { @@ -51,6 +56,7 @@ pub const FEATURES: Features = Features { mimalloc: FEATURE_MIMALLOC, jemalloc: FEATURE_JEMALLOC, multi_thread: FEATURE_MULTI_THREAD, + proxy_protocol: FEATURE_PROXY_PROTOCOL, }; impl Display for Features { @@ -66,6 +72,7 @@ impl Display for Features { disp_feat!(tfo, "tfo"); disp_feat!(zero_copy, "zero-copy"); disp_feat!(trust_dns, "trust-dns"); + disp_feat!(proxy_protocol, "proxy-protocol"); disp_feat!(multi_thread, "multi-thread"); disp_feat!(mimalloc, "mimalloc"); disp_feat!(jemalloc, "jemalloc"); diff --git a/src/utils/types.rs b/src/utils/types.rs index dc682805..72d4c923 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -10,6 +10,12 @@ pub enum RemoteAddr { DomainName(String, u16), } +#[derive(Clone, Copy)] +pub struct HaproxyOpts { + pub send_proxy: usize, + pub accept_proxy: usize, +} + #[derive(Clone, Copy)] pub struct ConnectOpts { pub use_udp: bool, @@ -17,6 +23,7 @@ pub struct ConnectOpts { pub zero_copy: bool, pub tcp_timeout: u64, pub udp_timeout: u64, + pub haproxy_opts: HaproxyOpts, pub send_through: Option, } @@ -89,14 +96,12 @@ impl Display for RemoteAddr { impl Display for ConnectOpts { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - macro_rules! on_off { - ($x: expr) => { - if $x { - "on" - } else { - "off" - } - }; + const fn on_off(b: bool) -> &'static str { + if b { + "on" + } else { + "off" + } } if let Some(send_through) = &self.send_through { write!(f, "send-through={}, ", send_through)?; @@ -104,9 +109,14 @@ impl Display for ConnectOpts { write!( f, "udp-forward={}, tcp-fast-open={}, tcp-zero-copy={}, ", - on_off!(self.use_udp), - on_off!(self.fast_open), - on_off!(self.zero_copy) + on_off(self.use_udp), + on_off(self.fast_open), + on_off(self.zero_copy) + )?; + write!( + f, + "send-proxy={}, accept-proxy={}, ", + self.haproxy_opts.send_proxy, self.haproxy_opts.accept_proxy )?; write!( f, From 1662b0a0e925cbcd013dfd1bc1b3c1716a1761f9 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 17:56:19 +0900 Subject: [PATCH 120/169] docs --- conf_tree | 20 ++-- readme.md | 282 +++++++++++++++++++++++++++++++++++++----------------- 2 files changed, 203 insertions(+), 99 deletions(-) diff --git a/conf_tree b/conf_tree index af1dd6bd..8f19e6be 100644 --- a/conf_tree +++ b/conf_tree @@ -1,5 +1,3 @@ - -# ## log ### level ### output @@ -16,16 +14,14 @@ ### fast_open ### tcp_timeout ### udp_timeout +### send_proxy +### accept_proxy +### send_proxy_version ## endpoints ### listen ### remote ### through ### network -#### use_udp -#### zero_copy -#### fast_open -#### tcp_timeout -#### udp_timeout ├── log │ ├── level @@ -42,14 +38,12 @@ │ ├── zero_copy │ ├── fast_open │ ├── tcp_timeout -│ └── udp_timeout +│ ├── udp_timeout +│ ├── send_proxy +│ ├── accept_proxy +│ └── send_proxy_version └── endpoints ├── listen ├── remote ├── through └── network - ├── use_udp - ├── zero_copy - ├── fast_open - ├── tcp_timeout - └── udp_timeout diff --git a/readme.md b/readme.md index 114153c3..b039f512 100644 --- a/readme.md +++ b/readme.md @@ -108,13 +108,13 @@ TIMEOUT OPTIONS: --udp-timeout override udp timeout ``` -start from command line arguments: +Start from command line arguments: ```shell realm -l 0.0.0.0:5000 -r 1.1.1.1:443 ``` -start from config file: +Start with a config file: ```shell # use toml @@ -124,90 +124,78 @@ realm -c config.toml realm -c config.json ``` -start from environment variable: +Start with environment variables: ```shell REALM_CONF='{"endpoints":[{"local":"127.0.0.1:5000","remote":"1.1.1.1:443"}]}' realm +# or export REALM_CONF=`cat config.json | jq -c ` realm ``` ## Configuration -TOML Example +See [examples](./examples) -```toml -[log] -level = "warn" -output = "/var/log/realm.log" - -[dns] -mode = "ipv4_only" -protocol = "tcp_and_udp" -nameservers = ["8.8.8.8:53", "8.8.4.4:53"] -min_ttl = 600 -max_ttl = 3600 -cache_size = 256 - -[network] -use_udp = true -zero_copy = true -fast_open = true -tcp_timeout = 300 -udp_timeout = 30 +Basic TOML Example +```toml [[endpoints]] listen = "0.0.0.0:5000" remote = "1.1.1.1:443" -through = "0.0.0.0" [[endpoints]] listen = "0.0.0.0:10000" remote = "www.google.com:443" -through = "0.0.0.0" - ```
JSON Example
-{
-  "log": {
-    "level": "warn",
-    "output": "/var/log/realm.log"
-  },
-  "dns": {
-    "mode": "ipv4_only",
-    "protocol": "tcp_and_udp",
-    "nameservers": [
-      "8.8.8.8:53",
-      "8.8.4.4:53"
-    ],
-    "min_ttl": 600,
-    "max_ttl": 3600,
-    "cache_size": 256
-  },
-  "network": {
-    "use_udp": true,
-    "zero_copy": true,
-    "fast_open": true,
-    "tcp_timeout": 300,
-    "udp_timeout": 30
-  },
+
+```json
+{
   "endpoints": [
     {
       "listen": "0.0.0.0:5000",
-      "remote": "1.1.1.1:443",
-      "through": "0.0.0.0"
+      "remote": "1.1.1.1:443"
     },
     {
       "listen": "0.0.0.0:10000",
-      "remote": "www.google.com:443",
-      "through": "0.0.0.0"
+      "remote": "www.google.com:443"
     }
   ]
-}
+}
+```
+
+
+
+ +
+Recommended Configuration +
+
+```toml
+[log]
+level = "warn"
+output = "/var/log/realm.log"
+
+[network]
+use_udp = true
+zero_copy = true
+
+[[endpoints]]
+listen = "0.0.0.0:5000"
+remote = "1.1.1.1:443"
+
+[[endpoints]]
+listen = "0.0.0.0:10000"
+remote = "www.google.com:443"
+
+
+```
+
 
@@ -229,57 +217,78 @@ through = "0.0.0.0" │ ├── zero_copy │ ├── fast_open │ ├── tcp_timeout -│ └── udp_timeout +│ ├── udp_timeout +│ ├── send_proxy +│ ├── accept_proxy +│ └── send_proxy_version └── endpoints ├── listen ├── remote ├── through └── network - ├── use_udp - ├── zero_copy - ├── fast_open - ├── tcp_timeout - └── udp_timeout ``` -You should provide at least `endpoint.listen` and `endpoint.remote`, other fields will take their default values if not provided. - -Priority: cmd override > endpoint config > global config +You should provide at least [endpoint.listen](#endpointlisten-string) and [endpoint.remote](#endpointremote-string), other fields will apply default values. +Option priority: cmd override > endpoint config > global config ### log -#### log.level +#### log.level: string -- off *(default)* +values: + +- off - error - info - debug - trace -#### log.output +default: off + +#### log.output: string -- stdout *(default)* +values: + +- stdout - stderr -- path, e.g. (`/var/log/realm.log`) +- path (e.g. `/var/log/realm.log`) + +default: stdout ### dns -#### dns.mode +Require the `trust-dns` feature + +#### dns.mode: string + +Dns resolve strategy. + +values: - ipv4_only - ipv6_only - ipv4_then_ipv6 - ipv6_then_ipv4 -- ipv4_and_ipv6 *(default)* +- ipv4_and_ipv6 + +default: ipv4_and_ipv6 -#### dns.protocol +#### dns.protocol: string + +Dns transport protocol. + +values: - tcp - udp -- tcp_and_udp *(default)* +- tcp_and_udp + +default: tcp_and_udp -#### dns.nameservers +#### dns.nameservers: string array + +Custom upstream servers. format: ["server1", "server2" ...] @@ -289,32 +298,133 @@ If on **unix/windows**, read from the default location.(e.g. `/etc/resolv.conf`) Otherwise, use google's public dns(`8.8.8.8:53`, `8.8.4.4:53` and `2001:4860:4860::8888:53`, `2001:4860:4860::8844:53`). -#### dns.min_ttl +#### dns.min_ttl: unsigned int + +The minimum lifetime of a positive dns cache default: 0 -#### dns.max_ttl +#### dns.max_ttl: unsigned int + +The maximum lifetime of a positive dns cache default: 86400 (1 day) -#### cache_size +#### dns.cache_size: unsigned int + +The maximum count of dns cache default: 32 ### network -- use_udp (default: false) -- zero_copy (default: false) -- fast_open (default: false) -- tcp_timeout (default: 300) -- udp_timeout (default: 30) +#### network.use_udp: bool + +Require the `udp` feature + +Start listening on a udp endpoint and forward packets to the remote peer. + +It will dynamically allocate local endpoints and establish udp associations. Once timeout, the endpoints will be deallocated and the association will be terminated. See also: [network.udp_timeout](#networkudptimeout-unsigned-int) + +Due to the receiver side not limiting access to the association, the relay works like a full-cone NAT. + +default: false + +#### network.zero_copy: bool + +Require the `zero-copy` feature + +Use `splice` instead of `send/recv` while handing tcp connection. This will save a lot of memory copies and context switches. + +default: false + +#### network.fast_open: bool + +Require the `fast-open` feature + +It is not recommended to enable this option, see [The Sad Story of TCP Fast Open](https://squeeze.isobar.com/2019/04/11/the-sad-story-of-tcp-fast-open/). + +default: false + +#### network.tcp_tomeout: unsigned int + +Close the connection if the peer does not send any data during `timeout`. + +***This option remains unimplemented! (since ce5213)*** + +To disable timeout, you need to explicitly set timeout value to 0. + +default: 300 + +#### network.udp_timeout: unsigned int + +Terminate udp association after `timeout`. + +The timeout value mut be properly configured in case of memory leak. Do not use a large `timeout`! + +default: 30 -To disable timeout, you need to explicitly set timeout value to 0 +#### network.send_proxy: bool +Requires the `proxy-protocol` feature + +Send haproxy PROXY header once the connection established. Both `v1` and `v2` are supported, see [send_proxy_version](#networksendproxyversion-unsigned-int). + +You should make sure the remote peer also speaks proxy-protocol. + +default: false + +#### network.send_proxy_version: unsigned int + +Requires the `proxy-protocol` feature + +This option has no effect unless [send_proxy](#networksendproxy-bool) is enabled. + +value: + +- 1 +- 2 + +default: 2 + +#### network.accept_proxy: bool + +Requires the `proxy-protocol` feature + +Wait for PROXY header once the connection established. + +If the remote sender does not send a `v1` or `v2` header before other contents, the connection will be closed. + +default: false ### endpoint -- listen: listen address -- remote: remote address -- through: send through specified ip or address -- network: override global network config +#### endpoint.listen: string + +Local address, supported formats: + +- ipv4:port +- ipv6:port + +#### endpoint.remote: string + +Remote address, supported formats: + +- ipv4:port +- ipv6:port +- example.com:port + +#### endpoint.through: string + +TCP: Bind a specific `ip` before openning a connection + +UDP: Bind a specific `ip` or `address` before sending packet + +Supported formats: + +- ipv4/ipv6 (tcp/udp) +- ipv4/ipv6:port (udp) + +#### endpoint.network + +The same as [network](#network), override global options. From 0d51be1ae78a53df6d715df05bbf5ddd0b4f1146 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 18:02:14 +0900 Subject: [PATCH 121/169] fix link --- readme.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/readme.md b/readme.md index b039f512..80b52052 100644 --- a/readme.md +++ b/readme.md @@ -152,7 +152,7 @@ remote = "www.google.com:443"
JSON Example -
+

```json { @@ -169,12 +169,12 @@ remote = "www.google.com:443" } ``` -

+

Recommended Configuration -
+

```toml [log] @@ -192,11 +192,9 @@ remote = "1.1.1.1:443" [[endpoints]] listen = "0.0.0.0:10000" remote = "www.google.com:443" - - ``` -

+

## global @@ -324,7 +322,7 @@ Require the `udp` feature Start listening on a udp endpoint and forward packets to the remote peer. -It will dynamically allocate local endpoints and establish udp associations. Once timeout, the endpoints will be deallocated and the association will be terminated. See also: [network.udp_timeout](#networkudptimeout-unsigned-int) +It will dynamically allocate local endpoints and establish udp associations. Once timeout, the endpoints will be deallocated and the association will be terminated. See also: [network.udp_timeout](#networkudp_timeout-unsigned-int) Due to the receiver side not limiting access to the association, the relay works like a full-cone NAT. @@ -368,7 +366,7 @@ default: 30 Requires the `proxy-protocol` feature -Send haproxy PROXY header once the connection established. Both `v1` and `v2` are supported, see [send_proxy_version](#networksendproxyversion-unsigned-int). +Send haproxy PROXY header once the connection established. Both `v1` and `v2` are supported, see [send_proxy_version](#networksend_proxy_version-unsigned-int). You should make sure the remote peer also speaks proxy-protocol. @@ -378,7 +376,7 @@ default: false Requires the `proxy-protocol` feature -This option has no effect unless [send_proxy](#networksendproxy-bool) is enabled. +This option has no effect unless [send_proxy](#networksend_proxy-bool) is enabled. value: From 85471951501d98af15b72897918efe0482c7cf6d Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 18:03:10 +0900 Subject: [PATCH 122/169] remove tcp timeout --- src/relay/tcp/mod.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 1ad42302..93592e27 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -17,7 +17,6 @@ cfg_if! { use std::io::Result; use std::net::SocketAddr; -use std::time::Duration; use log::{warn, debug}; @@ -38,7 +37,6 @@ pub async fn proxy( conn_opts: ConnectOpts, ) -> Result<(u64, u64)> { let ConnectOpts { - tcp_timeout: timeout, fast_open, zero_copy, send_through, @@ -50,12 +48,6 @@ pub async fn proxy( debug!("[tcp]remote resolved as {}", &remote); - let timeout = if timeout != 0 { - Some(Duration::from_secs(timeout)) - } else { - None - }; - let mut outbound = match send_through { Some(x) => { let socket = match x { From e100d5b5e6a29184cfd6415d1bc326612f4511d6 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 19:32:10 +0900 Subject: [PATCH 123/169] add proxy-protocol timeout option --- src/conf/net.rs | 76 ++++++++++++++++++++++------------------ src/relay/tcp/haproxy.rs | 22 ++++++++---- src/relay/tcp/mod.rs | 2 +- src/utils/consts.rs | 5 +++ src/utils/types.rs | 6 ++-- 5 files changed, 67 insertions(+), 44 deletions(-) diff --git a/src/conf/net.rs b/src/conf/net.rs index f444ef3d..460a39dd 100644 --- a/src/conf/net.rs +++ b/src/conf/net.rs @@ -3,6 +3,7 @@ use super::Config; use crate::utils::{ConnectOpts, HaproxyOpts}; use crate::utils::{TCP_TIMEOUT, UDP_TIMEOUT}; use crate::utils::PROXY_PROTOCOL_VERSION; +use crate::utils::PROXY_PROTOCOL_TIMEOUT; #[derive(Serialize, Debug, Deserialize, Clone, Copy, Default)] pub struct NetConf { @@ -24,6 +25,9 @@ pub struct NetConf { #[serde(default)] pub send_proxy_version: Option, + #[serde(default)] + pub accept_proxy_timeout: Option, + #[serde(default)] pub tcp_timeout: Option, @@ -52,12 +56,11 @@ impl Config for NetConf { let tcp_timeout = unbox!(tcp_timeout, TCP_TIMEOUT); let udp_timeout = unbox!(udp_timeout, UDP_TIMEOUT); - let accept_proxy = unbox!(accept_proxy) as usize; - let send_proxy = if unbox!(send_proxy) { - unbox!(send_proxy_version, PROXY_PROTOCOL_VERSION) - } else { - 0_usize - }; + let send_proxy = unbox!(send_proxy); + let send_proxy_version = unbox!(send_proxy_version, PROXY_PROTOCOL_VERSION); + + let accept_proxy = unbox!(accept_proxy); + let accept_proxy_timeout = unbox!(accept_proxy_timeout, PROXY_PROTOCOL_TIMEOUT); ConnectOpts { use_udp, @@ -69,6 +72,8 @@ impl Config for NetConf { haproxy_opts: HaproxyOpts { send_proxy, accept_proxy, + send_proxy_version, + accept_proxy_timeout }, } } @@ -85,6 +90,7 @@ impl Config for NetConf { rst!(self, send_proxy, other); rst!(self, accept_proxy, other); rst!(self, send_proxy_version, other); + rst!(self, accept_proxy_timeout, other); self } @@ -100,45 +106,47 @@ impl Config for NetConf { take!(self, send_proxy, other); take!(self, accept_proxy, other); take!(self, send_proxy_version, other); + take!(self, accept_proxy_timeout, other); self } fn from_cmd_args(matches: &clap::ArgMatches) -> Self { - let use_udp = matches.is_present("use_udp"); - - let fast_open = matches.is_present("fast_open"); - let zero_copy = matches.is_present("zero_copy"); - - let tcp_timeout = matches - .value_of("tcp_timeout") - .map(|x| x.parse::().unwrap()); - let udp_timeout = matches - .value_of("udp_timeout") - .map(|x| x.parse::().unwrap()); - - let send_proxy = matches.is_present("send_proxy"); - let accept_proxy = matches.is_present("accept_proxy"); - let send_proxy_version = matches - .value_of("send_proxy_version") - .map(|x| x.parse::().unwrap()); - - const fn bool_to_opt(b: bool) -> Option { - if b { - Some(true) - } else { - None + macro_rules! unpack { + ($key: expr) => { + if matches.is_present($key) { + Some(true) + } else { + None + } + }; + ($key: expr, $t: ident) => { + matches.value_of($key).map(|x| x.parse::<$t>().unwrap()) } } + let use_udp = unpack!("use_udp"); + let fast_open = unpack!("fast_open"); + let zero_copy = unpack!("zero_copy"); + + let tcp_timeout = unpack!("tcp_timeout", u64); + let udp_timeout = unpack!("udp_timeout", u64); + + let send_proxy = unpack!("send_proxy"); + let send_proxy_version = unpack!("send_proxy_version", usize); + + let accept_proxy = unpack!("accept_proxy"); + let accept_proxy_timeout = unpack!("accept_proxy_timeout", usize); + Self { - use_udp: bool_to_opt(use_udp), - fast_open: bool_to_opt(fast_open), - zero_copy: bool_to_opt(zero_copy), + use_udp, + fast_open, + zero_copy, tcp_timeout, udp_timeout, - send_proxy: bool_to_opt(send_proxy), - accept_proxy: bool_to_opt(accept_proxy), + send_proxy, + accept_proxy, send_proxy_version, + accept_proxy_timeout } } } diff --git a/src/relay/tcp/haproxy.rs b/src/relay/tcp/haproxy.rs index 43be7c3d..4c529959 100644 --- a/src/relay/tcp/haproxy.rs +++ b/src/relay/tcp/haproxy.rs @@ -14,6 +14,7 @@ use tokio::io::{AsyncReadExt, AsyncWriteExt}; use super::TcpStream; use crate::utils; use crate::utils::HaproxyOpts; +use crate::utils::timeoutfut; // client -> relay -> server @@ -25,6 +26,8 @@ pub async fn handle_proxy_protocol( let HaproxyOpts { send_proxy, accept_proxy, + send_proxy_version, + accept_proxy_timeout } = opts; let mut client_addr = MaybeUninit::::uninit(); @@ -38,11 +41,16 @@ pub async fn handle_proxy_protocol( // parse PROXY header from client and write log // may not get src and dst addr - if accept_proxy != 0 { + if accept_proxy { let buf = buf.write(BytesMut::with_capacity(256)); // FIXME: may not read the entire header - let n = src.read_buf(buf).await?; + + // The receiver may apply a short timeout and decide to + // abort the connection if the protocol header is not seen + // within a few seconds (at least 3 seconds to cover a TCP retransmit). + let n = timeoutfut(src.read_buf(buf), accept_proxy_timeout).await??; + let _ = buf.split_off(n); debug!("[tcp]recv initial {} bytes: {:?}", n, buf); @@ -57,7 +65,7 @@ pub async fn handle_proxy_protocol( // header has been parsed // do not send header to server - if send_proxy != 1 && send_proxy != 2 { + if !send_proxy { // write left bytes if !buf.is_empty() { debug!("[tcp]send left {} bytes: {:?}", buf.len(), buf); @@ -85,14 +93,14 @@ pub async fn handle_proxy_protocol( let server_addr = unsafe { server_addr.assume_init() }; // write header - let header = encode(make_header(client_addr, server_addr, send_proxy)) + let header = encode(make_header(client_addr, server_addr, send_proxy_version)) .map_err(|e| Error::new(ErrorKind::Other, e))?; debug!("[tcp]send initial {} bytes: {:?}", header.len(), &header); dst.write_all(&header).await?; // write left bytes // Safety: buf is initialized, filled with PROXY header - if accept_proxy != 0 { + if accept_proxy { let buf = unsafe { buf.assume_init() }; if !buf.is_empty() { debug!("[tcp]send left {} bytes: {:?}", buf.len(), &buf); @@ -121,9 +129,9 @@ macro_rules! unpack { fn make_header( client_addr: SocketAddr, server_addr: SocketAddr, - send_proxy: usize, + send_proxy_version: usize, ) -> ProxyHeader { - match send_proxy { + match send_proxy_version { 2 => make_header_v2(client_addr, server_addr), 1 => make_header_v1(client_addr, server_addr), _ => unreachable!(), diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 93592e27..04eef645 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -79,7 +79,7 @@ pub async fn proxy( setsockopt_warn!(outbound.set_nodelay(true), "nodelay"); #[cfg(feature = "proxy-protocol")] - if haproxy_opts.send_proxy != 0 || haproxy_opts.accept_proxy != 0 { + if haproxy_opts.send_proxy || haproxy_opts.accept_proxy { haproxy::handle_proxy_protocol( &mut inbound, &mut outbound, diff --git a/src/utils/consts.rs b/src/utils/consts.rs index e720aedb..6d837697 100644 --- a/src/utils/consts.rs +++ b/src/utils/consts.rs @@ -8,8 +8,13 @@ pub const TCP_TIMEOUT: u64 = 300; pub const UDP_TIMEOUT: u64 = 30; // default haproxy proxy-protocol version +#[cfg(feature = "proxy-protocol")] pub const PROXY_PROTOCOL_VERSION: usize = 2; +// default haproxy proxy-protocol version +#[cfg(feature = "proxy-protocol")] +pub const PROXY_PROTOCOL_TIMEOUT: usize = 5; + // https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1 pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { 512 diff --git a/src/utils/types.rs b/src/utils/types.rs index 72d4c923..273309ff 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -12,8 +12,10 @@ pub enum RemoteAddr { #[derive(Clone, Copy)] pub struct HaproxyOpts { - pub send_proxy: usize, - pub accept_proxy: usize, + pub send_proxy: bool, + pub accept_proxy: bool, + pub send_proxy_version: usize, + pub accept_proxy_timeout: usize, } #[derive(Clone, Copy)] From 5c279f8d7f76e455a103d4e43456e10d9b65abe1 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 19:41:49 +0900 Subject: [PATCH 124/169] refine timeout --- src/conf/net.rs | 22 ++++++++++++---------- src/relay/tcp/haproxy.rs | 15 ++++++++------- src/relay/udp.rs | 10 ++-------- src/utils/consts.rs | 4 ++-- src/utils/timeout.rs | 16 +++++++++------- src/utils/types.rs | 4 ++-- 6 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/conf/net.rs b/src/conf/net.rs index 460a39dd..e58a9b5a 100644 --- a/src/conf/net.rs +++ b/src/conf/net.rs @@ -29,10 +29,10 @@ pub struct NetConf { pub accept_proxy_timeout: Option, #[serde(default)] - pub tcp_timeout: Option, + pub tcp_timeout: Option, #[serde(default)] - pub udp_timeout: Option, + pub udp_timeout: Option, } impl Config for NetConf { @@ -57,10 +57,12 @@ impl Config for NetConf { let udp_timeout = unbox!(udp_timeout, UDP_TIMEOUT); let send_proxy = unbox!(send_proxy); - let send_proxy_version = unbox!(send_proxy_version, PROXY_PROTOCOL_VERSION); + let send_proxy_version = + unbox!(send_proxy_version, PROXY_PROTOCOL_VERSION); let accept_proxy = unbox!(accept_proxy); - let accept_proxy_timeout = unbox!(accept_proxy_timeout, PROXY_PROTOCOL_TIMEOUT); + let accept_proxy_timeout = + unbox!(accept_proxy_timeout, PROXY_PROTOCOL_TIMEOUT); ConnectOpts { use_udp, @@ -73,7 +75,7 @@ impl Config for NetConf { send_proxy, accept_proxy, send_proxy_version, - accept_proxy_timeout + accept_proxy_timeout, }, } } @@ -121,15 +123,15 @@ impl Config for NetConf { }; ($key: expr, $t: ident) => { matches.value_of($key).map(|x| x.parse::<$t>().unwrap()) - } + }; } - let use_udp = unpack!("use_udp"); + let use_udp = unpack!("use_udp"); let fast_open = unpack!("fast_open"); let zero_copy = unpack!("zero_copy"); - let tcp_timeout = unpack!("tcp_timeout", u64); - let udp_timeout = unpack!("udp_timeout", u64); + let tcp_timeout = unpack!("tcp_timeout", usize); + let udp_timeout = unpack!("udp_timeout", usize); let send_proxy = unpack!("send_proxy"); let send_proxy_version = unpack!("send_proxy_version", usize); @@ -146,7 +148,7 @@ impl Config for NetConf { send_proxy, accept_proxy, send_proxy_version, - accept_proxy_timeout + accept_proxy_timeout, } } } diff --git a/src/relay/tcp/haproxy.rs b/src/relay/tcp/haproxy.rs index 4c529959..5c729220 100644 --- a/src/relay/tcp/haproxy.rs +++ b/src/relay/tcp/haproxy.rs @@ -27,7 +27,7 @@ pub async fn handle_proxy_protocol( send_proxy, accept_proxy, send_proxy_version, - accept_proxy_timeout + accept_proxy_timeout, } = opts; let mut client_addr = MaybeUninit::::uninit(); @@ -41,13 +41,13 @@ pub async fn handle_proxy_protocol( // parse PROXY header from client and write log // may not get src and dst addr - if accept_proxy { + if accept_proxy { let buf = buf.write(BytesMut::with_capacity(256)); // FIXME: may not read the entire header - // The receiver may apply a short timeout and decide to - // abort the connection if the protocol header is not seen + // The receiver may apply a short timeout and decide to + // abort the connection if the protocol header is not seen // within a few seconds (at least 3 seconds to cover a TCP retransmit). let n = timeoutfut(src.read_buf(buf), accept_proxy_timeout).await??; @@ -93,14 +93,15 @@ pub async fn handle_proxy_protocol( let server_addr = unsafe { server_addr.assume_init() }; // write header - let header = encode(make_header(client_addr, server_addr, send_proxy_version)) - .map_err(|e| Error::new(ErrorKind::Other, e))?; + let header = + encode(make_header(client_addr, server_addr, send_proxy_version)) + .map_err(|e| Error::new(ErrorKind::Other, e))?; debug!("[tcp]send initial {} bytes: {:?}", header.len(), &header); dst.write_all(&header).await?; // write left bytes // Safety: buf is initialized, filled with PROXY header - if accept_proxy { + if accept_proxy { let buf = unsafe { buf.assume_init() }; if !buf.is_empty() { debug!("[tcp]send left {} bytes: {:?}", buf.len(), &buf); diff --git a/src/relay/udp.rs b/src/relay/udp.rs index fc618f41..e4cf6c3d 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -1,5 +1,4 @@ use std::io::Result; -use std::time::Duration; use std::net::SocketAddr; use std::sync::{Arc, RwLock}; use std::collections::HashMap; @@ -29,11 +28,6 @@ pub async fn proxy( } = conn_opts; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let listen_sock = Arc::new(UdpSocket::bind(&listen).await?); - let timeout = if timeout != 0 { - Some(Duration::from_secs(timeout)) - } else { - None - }; let mut buf = vec![0u8; BUF_SIZE]; @@ -92,7 +86,7 @@ async fn send_back( client_addr: SocketAddr, listen_sock: Arc, alloc_sock: Arc, - timeout: Option, + timeout: usize, ) { let mut buf = vec![0u8; BUF_SIZE]; @@ -142,7 +136,7 @@ async fn alloc_new_socket( remote_addr: &SocketAddr, send_through: &Option, listen_sock: Arc, - timeout: Option, + timeout: usize, ) -> Arc { // pick a random port let alloc_sock = Arc::new(match send_through { diff --git a/src/utils/consts.rs b/src/utils/consts.rs index 6d837697..cc15137c 100644 --- a/src/utils/consts.rs +++ b/src/utils/consts.rs @@ -4,8 +4,8 @@ use std::fmt::{Display, Formatter}; pub const DEFAULT_LOG_FILE: &str = "stdout"; // default timeout -pub const TCP_TIMEOUT: u64 = 300; -pub const UDP_TIMEOUT: u64 = 30; +pub const TCP_TIMEOUT: usize = 300; +pub const UDP_TIMEOUT: usize = 30; // default haproxy proxy-protocol version #[cfg(feature = "proxy-protocol")] diff --git a/src/utils/timeout.rs b/src/utils/timeout.rs index 91a753ea..0e008379 100644 --- a/src/utils/timeout.rs +++ b/src/utils/timeout.rs @@ -2,7 +2,7 @@ use std::pin::Pin; use std::task::{Poll, Context}; use std::future::Future; use std::time::Duration; -use std::io::{Result, Error, ErrorKind}; +use std::io::{Result, ErrorKind}; use tokio::time::Sleep; @@ -40,7 +40,7 @@ impl Future for Timeout { if let DelayP::Some(delay) = this.delay.project() { let delay: Pin<&mut Sleep> = delay; if delay.poll(cx).is_ready() { - return Ready(Err(Error::new(ErrorKind::TimedOut, "timeout"))); + return Ready(Err(ErrorKind::TimedOut.into())); } } @@ -48,12 +48,14 @@ impl Future for Timeout { } } -pub fn timeoutfut( - future: F, - duration: Option, -) -> Timeout { +// timeout = 0 means never timeout +// instead of timeout immediately +pub fn timeoutfut(future: F, timeout: usize) -> Timeout { use tokio::time::sleep; - let delay = duration.map_or(Delay::None, |d| Delay::Some(sleep(d))); + let delay = match timeout { + 0 => Delay::None, + x @ _ => Delay::Some(sleep(Duration::from_secs(x as u64))), + }; Timeout { value: future, delay, diff --git a/src/utils/types.rs b/src/utils/types.rs index 273309ff..4a9b3279 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -23,8 +23,8 @@ pub struct ConnectOpts { pub use_udp: bool, pub fast_open: bool, pub zero_copy: bool, - pub tcp_timeout: u64, - pub udp_timeout: u64, + pub tcp_timeout: usize, + pub udp_timeout: usize, pub haproxy_opts: HaproxyOpts, pub send_through: Option, } From 986f9ac12e5d5fd1d442f20f8cdd6256f48d3308 Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 19:52:47 +0900 Subject: [PATCH 125/169] add accept-proxy-timeout cmd opt --- src/cmd/mod.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index a4dcb595..dc845aa0 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -69,21 +69,21 @@ fn add_options(app: App) -> App { .short('l') .long("listen") .help("listen address") - .value_name("addr") + .value_name("address") .takes_value(true) .display_order(2), Arg::new("remote") .short('r') .long("remote") .help("remote address") - .value_name("addr") + .value_name("address") .takes_value(true) .display_order(3), Arg::new("through") .short('x') .long("through") .help("send through ip or address") - .value_name("addr") + .value_name("address") .takes_value(true) .display_order(4), ]) @@ -150,18 +150,24 @@ fn add_global_options(app: App) -> App { let app = app.help_heading("PROXY OPTIONS").args([ Arg::new("send_proxy") .long("send-proxy") - .help("send haproxy proxy protocol") + .help("send proxy protocol header") .display_order(0), - Arg::new("accept_proxy") - .long("accept-proxy") - .help("accept haproxy proxy protocol") - .display_order(1), Arg::new("send_proxy_version") .long("send-proxy-version") - .help("haproxy proxy protocol version") + .help("send proxy protocol version") .value_name("version") .takes_value(true) + .display_order(1), + Arg::new("accept_proxy") + .long("accept-proxy") + .help("accept proxy protocol header") .display_order(2), + Arg::new("accept_proxy_timeout") + .long("accept-proxy-timeout") + .help("accept proxy protocol timeout") + .value_name("second") + .takes_value(true) + .display_order(3), ]); // timeout belogs to network From 066e9ce705e1397d69e40d3b80aa0dc7e03e0b4a Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 19:53:34 +0900 Subject: [PATCH 126/169] clippy --- src/utils/timeout.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/timeout.rs b/src/utils/timeout.rs index 0e008379..3e0477cd 100644 --- a/src/utils/timeout.rs +++ b/src/utils/timeout.rs @@ -54,7 +54,7 @@ pub fn timeoutfut(future: F, timeout: usize) -> Timeout { use tokio::time::sleep; let delay = match timeout { 0 => Delay::None, - x @ _ => Delay::Some(sleep(Duration::from_secs(x as u64))), + x => Delay::Some(sleep(Duration::from_secs(x as u64))), }; Timeout { value: future, From 329f1b5101ec1babdeb41c21b8e62f0b16c6199d Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 19:58:21 +0900 Subject: [PATCH 127/169] fix build --- src/relay/tcp/tfo.rs | 4 ++++ src/utils/consts.rs | 2 -- src/utils/timeout.rs | 1 + 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/relay/tcp/tfo.rs b/src/relay/tcp/tfo.rs index e71b4d5a..b621c9db 100644 --- a/src/relay/tcp/tfo.rs +++ b/src/relay/tcp/tfo.rs @@ -50,6 +50,10 @@ impl TcpStream { .map(TcpStream) } + pub fn peer_addr(&self) -> Result { + self.0.peer_addr() + } + pub fn set_nodelay(&self, nodelay: bool) -> Result<()> { self.0.set_nodelay(nodelay) } diff --git a/src/utils/consts.rs b/src/utils/consts.rs index cc15137c..4d55a976 100644 --- a/src/utils/consts.rs +++ b/src/utils/consts.rs @@ -8,11 +8,9 @@ pub const TCP_TIMEOUT: usize = 300; pub const UDP_TIMEOUT: usize = 30; // default haproxy proxy-protocol version -#[cfg(feature = "proxy-protocol")] pub const PROXY_PROTOCOL_VERSION: usize = 2; // default haproxy proxy-protocol version -#[cfg(feature = "proxy-protocol")] pub const PROXY_PROTOCOL_TIMEOUT: usize = 5; // https://github.com/rust-lang/rust/blob/master/library/std/src/sys_common/io.rs#L1 diff --git a/src/utils/timeout.rs b/src/utils/timeout.rs index 3e0477cd..61e15888 100644 --- a/src/utils/timeout.rs +++ b/src/utils/timeout.rs @@ -50,6 +50,7 @@ impl Future for Timeout { // timeout = 0 means never timeout // instead of timeout immediately +#[allow(unused)] pub fn timeoutfut(future: F, timeout: usize) -> Timeout { use tokio::time::sleep; let delay = match timeout { From d02bd6039219afb310fc8d18dd34a783a8f9521e Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 20:32:45 +0900 Subject: [PATCH 128/169] update examples --- examples/full.json | 6 +++++- examples/full.toml | 4 ++++ examples/gen.sh | 7 +++++++ examples/good.json | 20 ++++++++++++++++++++ examples/good.toml | 15 +++++++++++++++ readme.md | 18 ++++++++++++------ 6 files changed, 63 insertions(+), 7 deletions(-) create mode 100644 examples/gen.sh create mode 100644 examples/good.json create mode 100644 examples/good.toml diff --git a/examples/full.json b/examples/full.json index 52e695e6..00bdea11 100644 --- a/examples/full.json +++ b/examples/full.json @@ -19,7 +19,11 @@ "zero_copy": true, "fast_open": true, "tcp_timeout": 300, - "udp_timeout": 30 + "udp_timeout": 30, + "send_proxy": true, + "send_proxy_version": 2, + "accept_proxy": true, + "accept_proxy_timeout": 5 }, "endpoints": [ { diff --git a/examples/full.toml b/examples/full.toml index ed8a5ca6..7e1bb84d 100644 --- a/examples/full.toml +++ b/examples/full.toml @@ -16,6 +16,10 @@ zero_copy = true fast_open = true tcp_timeout = 300 udp_timeout = 30 +send_proxy = true +send_proxy_version = 2 +accept_proxy = true +accept_proxy_timeout = 5 [[endpoints]] listen = "0.0.0.0:5000" diff --git a/examples/gen.sh b/examples/gen.sh new file mode 100644 index 00000000..27c1b877 --- /dev/null +++ b/examples/gen.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +for toml in ./*.toml; do + json="${toml%.toml}.json" + echo convert ${toml} into ${json} + cat ${toml}| tomlq > ${json} +done diff --git a/examples/good.json b/examples/good.json new file mode 100644 index 00000000..91b559d5 --- /dev/null +++ b/examples/good.json @@ -0,0 +1,20 @@ +{ + "log": { + "level": "warn", + "output": "/var/log/realm.log" + }, + "network": { + "use_udp": true, + "zero_copy": true + }, + "endpoints": [ + { + "listen": "0.0.0.0:5000", + "remote": "1.1.1.1:443" + }, + { + "listen": "0.0.0.0:10000", + "remote": "www.google.com:443" + } + ] +} diff --git a/examples/good.toml b/examples/good.toml new file mode 100644 index 00000000..1bf28a88 --- /dev/null +++ b/examples/good.toml @@ -0,0 +1,15 @@ +[log] +level = "warn" +output = "/var/log/realm.log" + +[network] +use_udp = true +zero_copy = true + +[[endpoints]] +listen = "0.0.0.0:5000" +remote = "1.1.1.1:443" + +[[endpoints]] +listen = "0.0.0.0:10000" +remote = "www.google.com:443" diff --git a/readme.md b/readme.md index 80b52052..3627bd75 100644 --- a/readme.md +++ b/readme.md @@ -70,7 +70,7 @@ Using [Cross](https://github.com/cross-rs/cross) is also a simple and good enoug ## Usage ```shell -Realm 1.5.x [udp][zero-copy][trust-dns][multi-thread] +Realm 1.5.x [udp][zero-copy][trust-dns][proxy-protocol][multi-thread] A high efficiency relay tool USAGE: @@ -85,11 +85,11 @@ FLAGS: -z, --splice force enable tcp zero copy OPTIONS: - -n, --nofile set nofile limit - -c, --config use config file - -l, --listen listen address - -r, --remote remote address - -x, --through send through ip or address + -n, --nofile set nofile limit + -c, --config use config file + -l, --listen
listen address + -r, --remote
remote address + -x, --through
send through ip or address LOG OPTIONS: --log-level override log level @@ -103,6 +103,12 @@ DNS OPTIONS: --dns-protocol override dns protocol --dns-servers override dns servers +PROXY OPTIONS: + --send-proxy send proxy protocol header + --send-proxy-version send proxy protocol version + --accept-proxy accept proxy protocol header + --accept-proxy-timeout accept proxy protocol timeout + TIMEOUT OPTIONS: --tcp-timeout override tcp timeout --udp-timeout override udp timeout From 205ed9064966e0caeb260b03615417a5dc25f5fa Mon Sep 17 00:00:00 2001 From: zephyr Date: Fri, 18 Feb 2022 23:49:29 +0900 Subject: [PATCH 129/169] rc12 --- readme.md | 58 +++++++++++++++++++++---------------------------- src/conf/mod.rs | 6 ++++- src/main.rs | 2 +- 3 files changed, 31 insertions(+), 35 deletions(-) diff --git a/readme.md b/readme.md index 3627bd75..e33bbfa7 100644 --- a/readme.md +++ b/readme.md @@ -11,7 +11,7 @@ Realm is a simple, high performance relay server written in rust. ## Features -- ~~Zero configuration.~~ Setup and run in one command. +- Zero configuration. Setup and run in one command. - Concurrency. Bidirectional concurrent traffic leads to high performance. - Low resources cost. @@ -61,11 +61,9 @@ cargo build --release --no-default-features --features udp, tfo, zero-copy, trus ### Cross compile -Please refer to [https://rust-lang.github.io/rustup/cross-compilation.html](https://rust-lang.github.io/rustup/cross-compilation.html). +Please refer to [https://rust-lang.github.io/rustup/cross-compilation.html](https://rust-lang.github.io/rustup/cross-compilation.html). You may need to install cross-compilers or other SDKs, and specify them when building the project. -You may need to install cross-compilers or other SDKs, and specify them when building the project. - -Using [Cross](https://github.com/cross-rs/cross) is also a simple and good enough solution. +Or have a look at [Cross](https://github.com/cross-rs/cross), it makes things easier. ## Usage @@ -142,11 +140,17 @@ realm ## Configuration -See [examples](./examples) - -Basic TOML Example +TOML Example ```toml +[log] +level = "warn" +output = "/var/log/realm.log" + +[network] +use_udp = true +zero_copy = true + [[endpoints]] listen = "0.0.0.0:5000" remote = "1.1.1.1:443" @@ -154,6 +158,7 @@ remote = "1.1.1.1:443" [[endpoints]] listen = "0.0.0.0:10000" remote = "www.google.com:443" + ```
@@ -162,6 +167,14 @@ remote = "www.google.com:443" ```json { + "log": { + "level": "warn", + "output": "/var/log/realm.log" + }, + "network": { + "use_udp": true, + "zero_copy": true + }, "endpoints": [ { "listen": "0.0.0.0:5000", @@ -173,36 +186,14 @@ remote = "www.google.com:443" } ] } -``` - -

-
- -
-Recommended Configuration -

-```toml -[log] -level = "warn" -output = "/var/log/realm.log" - -[network] -use_udp = true -zero_copy = true - -[[endpoints]] -listen = "0.0.0.0:5000" -remote = "1.1.1.1:443" - -[[endpoints]] -listen = "0.0.0.0:10000" -remote = "www.google.com:443" ```

+[See other examples here](./examples) + ## global ```shell @@ -232,7 +223,7 @@ remote = "www.google.com:443" └── network ``` -You should provide at least [endpoint.listen](#endpointlisten-string) and [endpoint.remote](#endpointremote-string), other fields will apply default values. +You should provide at least [endpoint.listen](#endpointlisten-string) and [endpoint.remote](#endpointremote-string), the left fields will take their default values. Option priority: cmd override > endpoint config > global config @@ -244,6 +235,7 @@ values: - off - error +- warn - info - debug - trace diff --git a/src/conf/mod.rs b/src/conf/mod.rs index e2476597..3bb0b31b 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -11,7 +11,7 @@ mod dns; pub use dns::{DnsMode, DnsProtocol, DnsConf}; mod net; -pub use net::{NetConf}; +pub use net::NetConf; mod endpoint; pub use endpoint::EndpointConf; @@ -21,8 +21,12 @@ pub trait Config { fn build(self) -> Self::Output; + // override self if other not empty + // usage: cmd argument overrides global and local option fn rst_field(&mut self, other: &Self) -> &mut Self; + // take other only if self empty & other not empty + // usage: local field takes global option fn take_field(&mut self, other: &Self) -> &mut Self; fn from_cmd_args(matches: &ArgMatches) -> Self; diff --git a/src/main.rs b/src/main.rs index 91db73a1..55368df9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,7 +11,7 @@ use cmd::CmdInput; use conf::{Config, FullConf, LogConf, DnsConf}; use utils::Endpoint; -const VERSION: &str = "1.5.0-rc11"; +const VERSION: &str = "1.5.0-rc12"; const ENV_CONFIG: &str = "REALM_CONF"; cfg_if! { From 1f1ec6490bcd2806fef2c1e5930e404c1f9428d3 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 19 Feb 2022 00:26:14 +0900 Subject: [PATCH 130/169] extract lib --- Cargo.toml | 8 +++++++- src/{main.rs => bin.rs} | 19 +++++++------------ src/cmd/mod.rs | 2 +- src/lib.rs | 8 ++++++++ 4 files changed, 23 insertions(+), 14 deletions(-) rename src/{main.rs => bin.rs} (92%) create mode 100644 src/lib.rs diff --git a/Cargo.toml b/Cargo.toml index 5b5034fb..740a2ec8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,13 @@ version = "1.5.0" authors = ["zhboner "] edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "realm" +path = "src/lib.rs" + +[[bin]] +name = "realm" +path = "src/bin.rs" [dependencies] cfg-if = "1" diff --git a/src/main.rs b/src/bin.rs similarity index 92% rename from src/main.rs rename to src/bin.rs index 55368df9..501f4e79 100644 --- a/src/main.rs +++ b/src/bin.rs @@ -1,18 +1,12 @@ -mod cmd; -mod dns; -mod conf; -mod utils; -mod relay; - use std::env; - use cfg_if::cfg_if; -use cmd::CmdInput; -use conf::{Config, FullConf, LogConf, DnsConf}; -use utils::Endpoint; -const VERSION: &str = "1.5.0-rc12"; -const ENV_CONFIG: &str = "REALM_CONF"; +use realm::cmd; +use realm::dns; +use realm::conf::{Config, FullConf, LogConf, DnsConf}; +use realm::utils::Endpoint; +use realm::relay; +use realm::ENV_CONFIG; cfg_if! { if #[cfg(all(feature = "mi-malloc"))] { @@ -34,6 +28,7 @@ fn main() { } }; + use cmd::CmdInput; match cmd::scan() { CmdInput::Endpoint(ep, opts) => { let mut conf = FullConf::default(); diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index dc845aa0..6b8c3eb5 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -5,7 +5,7 @@ use crate::conf::CmdOverride; use crate::conf::EndpointConf; use crate::conf::{Config, LogConf, DnsConf, NetConf}; -use super::VERSION; +use crate::VERSION; use crate::utils::FEATURES; pub enum CmdInput { diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000..98a65cfc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,8 @@ +pub mod cmd; +pub mod dns; +pub mod conf; +pub mod utils; +pub mod relay; + +pub const VERSION: &str = "1.5.0-rc12"; +pub const ENV_CONFIG: &str = "REALM_CONF"; From 9a194ed918524c7e223f84ad9a7a550360382b03 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 19 Feb 2022 01:39:01 +0900 Subject: [PATCH 131/169] use raw ptr(do something evil) --- src/relay/mod.rs | 25 ++++++++++++++----------- src/relay/tcp/mod.rs | 19 ++++++++++--------- src/relay/udp.rs | 14 ++++++++------ src/utils/ex_types.rs | 41 +++++++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 3 +++ 5 files changed, 76 insertions(+), 26 deletions(-) create mode 100644 src/utils/ex_types.rs diff --git a/src/relay/mod.rs b/src/relay/mod.rs index a42a8497..58b36197 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -4,28 +4,32 @@ use futures::future::join_all; mod tcp; use tcp::TcpListener; use crate::utils::Endpoint; +use crate::utils::{EndpointX, RemoteAddrX, ConnectOptsX}; pub async fn run(eps: Vec) { let mut workers = Vec::with_capacity(compute_workers(&eps)); - for ep in eps.into_iter() { + for ep in eps.iter() { #[cfg(feature = "udp")] if ep.opts.use_udp { - workers.push(tokio::spawn(proxy_udp(ep.clone()))) + workers.push(tokio::spawn(proxy_udp(ep.into()))) } - workers.push(tokio::spawn(proxy_tcp(ep))); + workers.push(tokio::spawn(proxy_tcp(ep.into()))); } join_all(workers).await; } -async fn proxy_tcp(ep: Endpoint) { +async fn proxy_tcp(ep: EndpointX) { let Endpoint { listen, remote, opts, .. - } = ep; + } = ep.as_ref(); - let lis = TcpListener::bind(listen) + let remote: RemoteAddrX = remote.into(); + let opts: ConnectOptsX = opts.into(); + + let lis = TcpListener::bind(*listen) .await .unwrap_or_else(|e| panic!("unable to bind {}: {}", &listen, &e)); @@ -38,10 +42,9 @@ async fn proxy_tcp(ep: Endpoint) { } }; - let msg = format!("{} => {}", &addr, &remote); + let msg = format!("{} => {}", &addr, remote.as_ref()); info!("[tcp]{}", &msg); - let remote = remote.clone(); tokio::spawn(async move { match tcp::proxy(stream, remote, opts).await { Ok((up, dl)) => info!( @@ -58,15 +61,15 @@ async fn proxy_tcp(ep: Endpoint) { mod udp; #[cfg(feature = "udp")] -async fn proxy_udp(ep: Endpoint) { +async fn proxy_udp(ep: EndpointX) { let Endpoint { listen, remote, opts, .. - } = ep; + } = ep.as_ref(); - if let Err(e) = udp::proxy(listen, remote, opts).await { + if let Err(e) = udp::proxy(listen, remote, opts.into()).await { panic!("udp forward exit: {}", &e); } } diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 04eef645..188c09fc 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -22,7 +22,8 @@ use log::{warn, debug}; use tokio::net::TcpSocket; -use crate::utils::{RemoteAddr, ConnectOpts}; +use crate::utils::ConnectOpts; +use crate::utils::{RemoteAddrX, ConnectOptsX}; macro_rules! setsockopt_warn { ($op: expr, $opt: expr) => {{ @@ -33,8 +34,8 @@ macro_rules! setsockopt_warn { #[allow(unused_variables)] pub async fn proxy( mut inbound: TcpStream, - remote: RemoteAddr, - conn_opts: ConnectOpts, + remote: RemoteAddrX, + conn_opts: ConnectOptsX, ) -> Result<(u64, u64)> { let ConnectOpts { fast_open, @@ -42,9 +43,9 @@ pub async fn proxy( send_through, haproxy_opts, .. - } = conn_opts; + } = conn_opts.as_ref(); - let remote = remote.into_sockaddr().await?; + let remote = remote.to_sockaddr().await?; debug!("[tcp]remote resolved as {}", &remote); @@ -60,10 +61,10 @@ pub async fn proxy( #[cfg(unix)] setsockopt_warn!(socket.set_reuseport(true), "reuseport"); - socket.bind(x)?; + socket.bind(*x)?; #[cfg(feature = "tfo")] - if fast_open { + if *fast_open { TcpStream::connect_with_socket(socket, remote).await? } else { socket.connect(remote).await?.into() @@ -83,13 +84,13 @@ pub async fn proxy( haproxy::handle_proxy_protocol( &mut inbound, &mut outbound, - haproxy_opts, + *haproxy_opts, ) .await?; } #[cfg(all(target_os = "linux", feature = "zero-copy"))] - let res = if zero_copy { + let res = if *zero_copy { zio::bidi_copy_pipe(&mut inbound, &mut outbound).await } else { zio::bidi_copy_buffer(&mut inbound, &mut outbound).await diff --git a/src/relay/udp.rs b/src/relay/udp.rs index e4cf6c3d..9266315b 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -9,6 +9,7 @@ use tokio::net::UdpSocket; use crate::utils::DEFAULT_BUF_SIZE; use crate::utils::{RemoteAddr, ConnectOpts}; +use crate::utils::ConnectOptsX; use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; use crate::utils::timeoutfut; @@ -17,15 +18,16 @@ type SockMap = Arc>>>; const BUF_SIZE: usize = DEFAULT_BUF_SIZE; pub async fn proxy( - listen: SocketAddr, - remote: RemoteAddr, - conn_opts: ConnectOpts, + listen: &SocketAddr, + remote: &RemoteAddr, + conn_opts: ConnectOptsX, ) -> Result<()> { let ConnectOpts { send_through, udp_timeout: timeout, .. - } = conn_opts; + } = conn_opts.as_ref(); + let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let listen_sock = Arc::new(UdpSocket::bind(&listen).await?); @@ -65,9 +67,9 @@ pub async fn proxy( &sock_map, client_addr, &remote_addr, - &send_through, + send_through, listen_sock.clone(), - timeout, + *timeout, ) .await } diff --git a/src/utils/ex_types.rs b/src/utils/ex_types.rs new file mode 100644 index 00000000..0f877853 --- /dev/null +++ b/src/utils/ex_types.rs @@ -0,0 +1,41 @@ +use core::ops::Deref; + +use super::{Endpoint, RemoteAddr, ConnectOpts}; + +macro_rules! ptr_wrap { + ($old: ident,$new: ident) => { + #[derive(Clone, Copy)] + pub struct $new { + ptr: *const $old, + } + + unsafe impl Send for $new {} + unsafe impl Sync for $new {} + + impl AsRef<$old> for $new { + #[inline] + fn as_ref(&self) -> &$old { + unsafe { &*self.ptr } + } + } + + impl Deref for $new { + type Target = $old; + + #[inline] + fn deref(&self) -> &Self::Target { + self.as_ref() + } + } + + impl From<&$old> for $new { + fn from(ptr: &$old) -> Self { + $new { ptr } + } + } + }; +} + +ptr_wrap!(Endpoint, EndpointX); +ptr_wrap!(RemoteAddr, RemoteAddrX); +ptr_wrap!(ConnectOpts, ConnectOptsX); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 79b4cd8c..b7465788 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,6 +7,9 @@ pub use consts::*; mod timeout; pub use timeout::*; +mod ex_types; +pub use ex_types::*; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; #[allow(unused)] From 2062877d1114435942d1533d254fac4ae55ec303 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 19 Feb 2022 07:33:47 +0900 Subject: [PATCH 132/169] support bind to iface, both tcp and udp socket --- Cargo.lock | 1 + Cargo.toml | 1 + conf_tree | 9 +++- examples/full.json | 6 ++- examples/full.toml | 2 + readme.md | 19 +++++--- src/cmd/mod.rs | 7 +++ src/conf/endpoint.rs | 9 ++++ src/conf/net.rs | 4 ++ src/relay/mod.rs | 10 +++- src/relay/tcp/mod.rs | 50 ++++++-------------- src/relay/tcp/tfo.rs | 4 -- src/relay/udp.rs | 56 +++++++++++------------ src/utils/mod.rs | 39 ++++++++++++++-- src/utils/socket.rs | 106 +++++++++++++++++++++++++++++++++++++++++++ src/utils/types.rs | 8 +++- 16 files changed, 248 insertions(+), 83 deletions(-) create mode 100644 src/utils/socket.rs diff --git a/Cargo.lock b/Cargo.lock index 0477f2c9..899bc755 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -705,6 +705,7 @@ dependencies = [ "proxy-protocol", "serde", "serde_json", + "socket2 0.4.4", "tokio", "tokio-tfo", "toml", diff --git a/Cargo.toml b/Cargo.toml index 740a2ec8..a20a0438 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,7 @@ futures = "0.3" log = "0.4" libc = "0.2" bytes = "1" +socket2 = "0.4" clap = "3" toml = "0.5" diff --git a/conf_tree b/conf_tree index 8f19e6be..0ea0c4cc 100644 --- a/conf_tree +++ b/conf_tree @@ -15,14 +15,17 @@ ### tcp_timeout ### udp_timeout ### send_proxy -### accept_proxy ### send_proxy_version +### accept_proxy +### accept_proxy_timeout ## endpoints ### listen ### remote ### through +### interface ### network + ├── log │ ├── level │ └── output @@ -40,10 +43,12 @@ │ ├── tcp_timeout │ ├── udp_timeout │ ├── send_proxy +│ ├── send_proxy_version │ ├── accept_proxy -│ └── send_proxy_version +│ └── accept_proxy_timeout └── endpoints ├── listen ├── remote ├── through + ├── interface └── network diff --git a/examples/full.json b/examples/full.json index 00bdea11..3569325d 100644 --- a/examples/full.json +++ b/examples/full.json @@ -29,12 +29,14 @@ { "listen": "0.0.0.0:5000", "remote": "1.1.1.1:443", - "through": "0.0.0.0" + "through": "0.0.0.0", + "interface": "lo" }, { "listen": "0.0.0.0:10000", "remote": "www.google.com:443", - "through": "0.0.0.0" + "through": "0.0.0.0", + "interface": "wlan0" } ] } diff --git a/examples/full.toml b/examples/full.toml index 7e1bb84d..14d6f847 100644 --- a/examples/full.toml +++ b/examples/full.toml @@ -25,8 +25,10 @@ accept_proxy_timeout = 5 listen = "0.0.0.0:5000" remote = "1.1.1.1:443" through = "0.0.0.0" +interface = "lo" [[endpoints]] listen = "0.0.0.0:10000" remote = "www.google.com:443" through = "0.0.0.0" +interface = "wlan0" diff --git a/readme.md b/readme.md index e33bbfa7..c88b4566 100644 --- a/readme.md +++ b/readme.md @@ -83,11 +83,12 @@ FLAGS: -z, --splice force enable tcp zero copy OPTIONS: - -n, --nofile set nofile limit - -c, --config use config file - -l, --listen
listen address - -r, --remote
remote address - -x, --through
send through ip or address + -n, --nofile set nofile limit + -c, --config use config file + -l, --listen
listen address + -r, --remote
remote address + -x, --through
send through ip or address + -i, --interface bind to interface LOG OPTIONS: --log-level override log level @@ -214,12 +215,14 @@ remote = "www.google.com:443" │ ├── tcp_timeout │ ├── udp_timeout │ ├── send_proxy +│ ├── send_proxy_version │ ├── accept_proxy -│ └── send_proxy_version +│ └── accept_proxy_timeout └── endpoints ├── listen ├── remote ├── through + ├── interface └── network ``` @@ -421,6 +424,10 @@ Supported formats: - ipv4/ipv6 (tcp/udp) - ipv4/ipv6:port (udp) +#### endpoint.interface: string + +Bind to a specific interface + #### endpoint.network The same as [network](#network), override global options. diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 6b8c3eb5..89ad894a 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -86,6 +86,13 @@ fn add_options(app: App) -> App { .value_name("address") .takes_value(true) .display_order(4), + Arg::new("interface") + .short('i') + .long("interface") + .help("bind to interface") + .value_name("device") + .takes_value(true) + .display_order(5), ]) } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index 0a35d706..21732e55 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -12,6 +12,9 @@ pub struct EndpointConf { #[serde(default)] pub through: Option, + #[serde(default)] + pub interface: Option, + #[serde(default)] pub network: NetConf, } @@ -64,9 +67,13 @@ impl Config for EndpointConf { fn build(self) -> Self::Output { let local = self.build_local(); let remote = self.build_remote(); + let through = self.build_send_through(); + // iface is untested + let mut conn_opts = self.network.build(); conn_opts.send_through = through; + conn_opts.bind_interface = self.interface; Endpoint::new(local, remote, conn_opts) } @@ -82,11 +89,13 @@ impl Config for EndpointConf { let listen = matches.value_of("local").unwrap().to_string(); let remote = matches.value_of("remote").unwrap().to_string(); let through = matches.value_of("through").map(String::from); + let interface = matches.value_of("interface").map(String::from); EndpointConf { listen, remote, through, + interface, network: Default::default(), } } diff --git a/src/conf/net.rs b/src/conf/net.rs index e58a9b5a..f9e3cfe8 100644 --- a/src/conf/net.rs +++ b/src/conf/net.rs @@ -70,7 +70,11 @@ impl Config for NetConf { zero_copy, tcp_timeout, udp_timeout, + + // from endpoint send_through: None, + bind_interface: None, + haproxy_opts: HaproxyOpts { send_proxy, accept_proxy, diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 58b36197..66535929 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -1,8 +1,9 @@ -use log::{info, error}; +use log::{info, warn, error}; use futures::future::join_all; mod tcp; use tcp::TcpListener; + use crate::utils::Endpoint; use crate::utils::{EndpointX, RemoteAddrX, ConnectOptsX}; @@ -45,6 +46,13 @@ async fn proxy_tcp(ep: EndpointX) { let msg = format!("{} => {}", &addr, remote.as_ref()); info!("[tcp]{}", &msg); + if let Err(e) = stream.set_nodelay(true) { + warn!( + "[tcp]failed to set no_delay option for incoming stream: {}", + e + ); + } + tokio::spawn(async move { match tcp::proxy(stream, remote, opts).await { Ok((up, dl)) => info!( diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 188c09fc..61cd563f 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -16,21 +16,15 @@ cfg_if! { } use std::io::Result; -use std::net::SocketAddr; -use log::{warn, debug}; +use log::debug; use tokio::net::TcpSocket; +use crate::utils::socket; use crate::utils::ConnectOpts; use crate::utils::{RemoteAddrX, ConnectOptsX}; -macro_rules! setsockopt_warn { - ($op: expr, $opt: expr) => {{ - let _ = $op.map_err(|e| warn!("[tcp]failed to setsockopt $opt: {}", e)); - }}; -} - #[allow(unused_variables)] pub async fn proxy( mut inbound: TcpStream, @@ -41,44 +35,30 @@ pub async fn proxy( fast_open, zero_copy, send_through, + bind_interface, haproxy_opts, .. } = conn_opts.as_ref(); + // before connect let remote = remote.to_sockaddr().await?; - debug!("[tcp]remote resolved as {}", &remote); - let mut outbound = match send_through { - Some(x) => { - let socket = match x { - SocketAddr::V4(_) => TcpSocket::new_v4()?, - SocketAddr::V6(_) => TcpSocket::new_v6()?, - }; - - setsockopt_warn!(socket.set_reuseaddr(true), "reuseaddr"); + let socket = socket::new_socket(socket::Type::STREAM, &remote, &conn_opts)?; + let socket = TcpSocket::from_std_stream(socket.into()); - #[cfg(unix)] - setsockopt_warn!(socket.set_reuseport(true), "reuseport"); + // connect! + #[cfg(not(feature = "tfo"))] + let mut outbound = socket.connect(remote).await?; - socket.bind(*x)?; - - #[cfg(feature = "tfo")] - if *fast_open { - TcpStream::connect_with_socket(socket, remote).await? - } else { - socket.connect(remote).await?.into() - } - - #[cfg(not(feature = "tfo"))] - socket.connect(remote).await? - } - None => TcpStream::connect(remote).await?, + #[cfg(feature = "tfo")] + let mut outbound = if *fast_open { + TcpStream::connect_with_socket(socket, remote).await? + } else { + socket.connect(remote).await?.into() }; - setsockopt_warn!(inbound.set_nodelay(true), "nodelay"); - setsockopt_warn!(outbound.set_nodelay(true), "nodelay"); - + // after connected #[cfg(feature = "proxy-protocol")] if haproxy_opts.send_proxy || haproxy_opts.accept_proxy { haproxy::handle_proxy_protocol( diff --git a/src/relay/tcp/tfo.rs b/src/relay/tcp/tfo.rs index b621c9db..079de3fe 100644 --- a/src/relay/tcp/tfo.rs +++ b/src/relay/tcp/tfo.rs @@ -37,10 +37,6 @@ impl TcpListener { } impl TcpStream { - pub async fn connect(addr: SocketAddr) -> Result { - TfoStream::connect(addr).await.map(TcpStream) - } - pub async fn connect_with_socket( socket: TcpSocket, addr: SocketAddr, diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 9266315b..3964b951 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -10,8 +10,8 @@ use tokio::net::UdpSocket; use crate::utils::DEFAULT_BUF_SIZE; use crate::utils::{RemoteAddr, ConnectOpts}; use crate::utils::ConnectOptsX; -use crate::utils::{new_sockaddr_v4, new_sockaddr_v6}; use crate::utils::timeoutfut; +use crate::utils::socket; // client <--> allocated socket type SockMap = Arc>>>; @@ -23,7 +23,6 @@ pub async fn proxy( conn_opts: ConnectOptsX, ) -> Result<()> { let ConnectOpts { - send_through, udp_timeout: timeout, .. } = conn_opts.as_ref(); @@ -63,15 +62,30 @@ pub async fn proxy( "[udp]new association {} => {}", &client_addr, &remote_addr ); + + let socket = match socket::new_socket( + socket::Type::DGRAM, + &remote_addr, + &conn_opts, + ) { + Ok(x) => x, + Err(e) => { + error!("[udp]failed to open new socket: {}", e); + continue; + } + }; + // from_std panics only when tokio runtime not setup + let new_sock = + Arc::new(UdpSocket::from_std(socket.into()).unwrap()); + alloc_new_socket( &sock_map, client_addr, - &remote_addr, - send_through, - listen_sock.clone(), + &new_sock, + &listen_sock, *timeout, - ) - .await + ); + new_sock } }; @@ -132,39 +146,25 @@ fn get_socket( // drop the lock } -async fn alloc_new_socket( +fn alloc_new_socket( sock_map: &SockMap, client_addr: SocketAddr, - remote_addr: &SocketAddr, - send_through: &Option, - listen_sock: Arc, + new_sock: &Arc, + listen_sock: &Arc, timeout: usize, -) -> Arc { - // pick a random port - let alloc_sock = Arc::new(match send_through { - Some(x) => UdpSocket::bind(x).await.unwrap(), - None => match remote_addr { - SocketAddr::V4(_) => { - UdpSocket::bind(new_sockaddr_v4()).await.unwrap() - } - SocketAddr::V6(_) => { - UdpSocket::bind(new_sockaddr_v6()).await.unwrap() - } - }, - }); +) { // new send back task tokio::spawn(send_back( sock_map.clone(), client_addr, - listen_sock, - alloc_sock.clone(), + listen_sock.clone(), + new_sock.clone(), timeout, )); sock_map .write() .unwrap() - .insert(client_addr, alloc_sock.clone()); - alloc_sock + .insert(client_addr, new_sock.clone()); // drop the lock } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b7465788..b99b5765 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,15 +1,18 @@ -mod types; +pub mod types; pub use types::*; -mod consts; +pub mod consts; pub use consts::*; -mod timeout; +pub mod timeout; pub use timeout::*; -mod ex_types; +pub mod ex_types; pub use ex_types::*; +pub mod socket; +pub use socket::*; + use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; #[allow(unused)] @@ -81,3 +84,31 @@ pub fn get_nofile_limit() -> Option<(u64, u64)> { Some((lim.rlim_cur as u64, lim.rlim_max as u64)) } + +// from dear shadowoscks-rust: +// https://docs.rs/shadowsocks/1.13.1/src/shadowsocks/net/sys/unix/linux/mod.rs.html#256-276 +// seems bsd does not support SO_BINDTODEVICE +// should use IP_SENDIF instead +// ref: https://lists.freebsd.org/pipermail/freebsd-net/2012-April/032064.html +#[cfg(target_os = "linux")] +pub fn bind_to_device( + socket: &T, + iface: &str, +) -> std::io::Result<()> { + let iface_bytes = iface.as_bytes(); + + if unsafe { + libc::setsockopt( + socket.as_raw_fd(), + libc::SOL_SOCKET, + libc::SO_BINDTODEVICE, + iface_bytes.as_ptr() as *const _ as *const libc::c_void, + iface_bytes.len() as libc::socklen_t, + ) + } < 0 + { + Err(std::io::Error::last_os_error()) + } else { + Ok(()) + } +} diff --git a/src/utils/socket.rs b/src/utils/socket.rs new file mode 100644 index 00000000..40e9931c --- /dev/null +++ b/src/utils/socket.rs @@ -0,0 +1,106 @@ +mod detail { + + use std::io::Result; + use socket2::{Socket, Domain, Type}; + + #[cfg(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + ))] + pub fn new_socket(domain: Domain, ty: Type) -> Result { + use std::os::unix::prelude::FromRawFd; + + use libc::{SOCK_NONBLOCK, SOCK_CLOEXEC}; + + let ty = libc::c_int::from(ty) | SOCK_NONBLOCK | SOCK_CLOEXEC; + + let fd = unsafe { + libc::socket(domain.into(), ty | SOCK_NONBLOCK | SOCK_CLOEXEC, 0) + }; + + if fd < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(unsafe { Socket::from_raw_fd(fd) }) + } + } + + #[cfg(not(any( + target_os = "android", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "illumos", + target_os = "linux", + target_os = "netbsd", + target_os = "openbsd" + )))] + pub fn new_socket(domain: Domain, ty: Type) -> Result { + let socket = Socket::new(domain, ty, None)?; + socket.set_nonblocking(true)?; + Ok(socket) + } +} + +use std::io::Result; +use std::net::SocketAddr; + +use log::warn; +use socket2::{Socket, SockAddr}; +use super::ConnectOpts; + +pub use socket2::{Type, Domain}; + +pub fn new_socket( + ty: Type, + addr: &SocketAddr, + opts: &ConnectOpts, +) -> Result { + let domain = match addr { + SocketAddr::V4(..) => Domain::IPV4, + SocketAddr::V6(..) => Domain::IPV6, + }; + + macro_rules! try_opt { + ($op: expr, $msg: expr) => {{ + if let Err(e) = $op { + warn!("[sys]$msg: {}", e); + } + }}; + } + + let socket = detail::new_socket(domain, ty)?; + + try_opt!( + socket.set_reuse_address(true), + "failed to set reuse_addr option for new socket" + ); + + if ty == Type::STREAM { + try_opt!( + socket.set_nodelay(true), + "failed to set no_delay option for new socket" + ); + } + + #[cfg(target_os = "linux")] + if let Some(iface) = &opts.bind_interface { + try_opt!( + crate::utils::bind_to_device(&socket, iface), + "failed to bind new socket to device" + ); + } + + if let Some(addr) = &opts.send_through { + try_opt!( + socket.bind(&SockAddr::from(*addr)), + "failed to bind new socket to address" + ) + } + + Ok(socket) +} diff --git a/src/utils/types.rs b/src/utils/types.rs index 4a9b3279..828a5859 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -18,7 +18,7 @@ pub struct HaproxyOpts { pub accept_proxy_timeout: usize, } -#[derive(Clone, Copy)] +#[derive(Clone)] pub struct ConnectOpts { pub use_udp: bool, pub fast_open: bool, @@ -27,6 +27,7 @@ pub struct ConnectOpts { pub udp_timeout: usize, pub haproxy_opts: HaproxyOpts, pub send_through: Option, + pub bind_interface: Option, } #[derive(Clone)] @@ -105,6 +106,11 @@ impl Display for ConnectOpts { "off" } } + + if let Some(bind_interface) = &self.bind_interface { + write!(f, "bind-iface={}, ", bind_interface)?; + } + if let Some(send_through) = &self.send_through { write!(f, "send-through={}, ", send_through)?; } From 7b5649ea921e5762b7e4a861befe116954bcdcc5 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 00:36:32 +0900 Subject: [PATCH 133/169] rename some functions --- src/bin.rs | 2 +- src/relay/mod.rs | 13 +++++++------ src/relay/tcp/haproxy.rs | 6 +++--- src/relay/tcp/mod.rs | 2 +- src/relay/udp.rs | 12 ++++-------- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 501f4e79..24a44480 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -77,7 +77,7 @@ fn setup_log(log: LogConf) { fern::Dispatch::new() .format(|out, message, record| { out.finish(format_args!( - "{}[{}][{}] {}", + "{}[{}][{}]{}", chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), record.target(), record.level(), diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 66535929..8615e1ee 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -12,14 +12,14 @@ pub async fn run(eps: Vec) { for ep in eps.iter() { #[cfg(feature = "udp")] if ep.opts.use_udp { - workers.push(tokio::spawn(proxy_udp(ep.into()))) + workers.push(tokio::spawn(run_udp(ep.into()))) } - workers.push(tokio::spawn(proxy_tcp(ep.into()))); + workers.push(tokio::spawn(run_tcp(ep.into()))); } join_all(workers).await; } -async fn proxy_tcp(ep: EndpointX) { +async fn run_tcp(ep: EndpointX) { let Endpoint { listen, remote, @@ -54,7 +54,7 @@ async fn proxy_tcp(ep: EndpointX) { } tokio::spawn(async move { - match tcp::proxy(stream, remote, opts).await { + match tcp::connect_and_relay(stream, remote, opts).await { Ok((up, dl)) => info!( "[tcp]{} finish, upload: {}b, download: {}b", msg, up, dl @@ -69,7 +69,7 @@ async fn proxy_tcp(ep: EndpointX) { mod udp; #[cfg(feature = "udp")] -async fn proxy_udp(ep: EndpointX) { +async fn run_udp(ep: EndpointX) { let Endpoint { listen, remote, @@ -77,7 +77,8 @@ async fn proxy_udp(ep: EndpointX) { .. } = ep.as_ref(); - if let Err(e) = udp::proxy(listen, remote, opts.into()).await { + if let Err(e) = udp::associate_and_relay(listen, remote, opts.into()).await + { panic!("udp forward exit: {}", &e); } } diff --git a/src/relay/tcp/haproxy.rs b/src/relay/tcp/haproxy.rs index 5c729220..5e26935d 100644 --- a/src/relay/tcp/haproxy.rs +++ b/src/relay/tcp/haproxy.rs @@ -52,7 +52,7 @@ pub async fn handle_proxy_protocol( let n = timeoutfut(src.read_buf(buf), accept_proxy_timeout).await??; let _ = buf.split_off(n); - debug!("[tcp]recv initial {} bytes: {:?}", n, buf); + debug!("[tcp]recv initial {} bytes: {:#x}", n, buf); let header = parse(buf).map_err(|e| Error::new(ErrorKind::Other, e))?; debug!("[tcp]proxy-protocol parsed, {} bytes left", buf.remaining()); @@ -68,7 +68,7 @@ pub async fn handle_proxy_protocol( if !send_proxy { // write left bytes if !buf.is_empty() { - debug!("[tcp]send left {} bytes: {:?}", buf.len(), buf); + debug!("[tcp]send left {} bytes: {:#x}", buf.len(), buf); dst.write_all(buf).await?; } return Ok(()); @@ -96,7 +96,7 @@ pub async fn handle_proxy_protocol( let header = encode(make_header(client_addr, server_addr, send_proxy_version)) .map_err(|e| Error::new(ErrorKind::Other, e))?; - debug!("[tcp]send initial {} bytes: {:?}", header.len(), &header); + debug!("[tcp]send initial {} bytes: {:#x}", header.len(), &header); dst.write_all(&header).await?; // write left bytes diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 61cd563f..9625e871 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -26,7 +26,7 @@ use crate::utils::ConnectOpts; use crate::utils::{RemoteAddrX, ConnectOptsX}; #[allow(unused_variables)] -pub async fn proxy( +pub async fn connect_and_relay( mut inbound: TcpStream, remote: RemoteAddrX, conn_opts: ConnectOptsX, diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 3964b951..b3ade40a 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -8,7 +8,7 @@ use log::{debug, info, error}; use tokio::net::UdpSocket; use crate::utils::DEFAULT_BUF_SIZE; -use crate::utils::{RemoteAddr, ConnectOpts}; +use crate::utils::RemoteAddr; use crate::utils::ConnectOptsX; use crate::utils::timeoutfut; use crate::utils::socket; @@ -17,16 +17,12 @@ use crate::utils::socket; type SockMap = Arc>>>; const BUF_SIZE: usize = DEFAULT_BUF_SIZE; -pub async fn proxy( +pub async fn associate_and_relay( listen: &SocketAddr, remote: &RemoteAddr, conn_opts: ConnectOptsX, ) -> Result<()> { - let ConnectOpts { - udp_timeout: timeout, - .. - } = conn_opts.as_ref(); - + let timeout = conn_opts.udp_timeout; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); let listen_sock = Arc::new(UdpSocket::bind(&listen).await?); @@ -83,7 +79,7 @@ pub async fn proxy( client_addr, &new_sock, &listen_sock, - *timeout, + timeout, ); new_sock } From 411768523a8c876548634b984d745c5e993cb4c5 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 05:02:45 +0900 Subject: [PATCH 134/169] better option display --- src/bin.rs | 12 +++++++----- src/conf/mod.rs | 21 ++++++++++++++++----- src/utils/types.rs | 12 +++++++----- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index 24a44480..f310fcda 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -21,7 +21,7 @@ cfg_if! { } fn main() { - let conf = || { + let conf = (|| { if let Ok(conf_str) = env::var(ENV_CONFIG) { if let Ok(conf) = FullConf::from_conf_str(&conf_str) { return conf; @@ -32,19 +32,21 @@ fn main() { match cmd::scan() { CmdInput::Endpoint(ep, opts) => { let mut conf = FullConf::default(); - conf.add_endpoint(ep).apply_global_opts(opts); + conf.add_endpoint(ep) + .apply_global_opts() + .apply_cmd_opts(opts); conf } CmdInput::Config(conf, opts) => { let mut conf = FullConf::from_conf_file(&conf); - conf.apply_global_opts(opts); + conf.apply_global_opts().apply_cmd_opts(opts); conf } CmdInput::None => std::process::exit(0), } - }; + })(); - start_from_conf(conf()); + start_from_conf(conf); } fn start_from_conf(full: FullConf) { diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 3bb0b31b..cb763dcc 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -16,6 +16,8 @@ pub use net::NetConf; mod endpoint; pub use endpoint::EndpointConf; +mod legacy; + pub trait Config { type Output; @@ -48,7 +50,7 @@ pub struct FullConf { pub dns: DnsConf, #[serde(default)] - pub network: Option, + pub network: NetConf, pub endpoints: Vec, } @@ -58,7 +60,7 @@ impl FullConf { pub fn new( log: LogConf, dns: DnsConf, - network: Option, + network: NetConf, endpoints: Vec, ) -> Self { FullConf { @@ -102,18 +104,27 @@ impl FullConf { self } - pub fn apply_global_opts(&mut self, opts: CmdOverride) -> &mut Self { + // override + pub fn apply_cmd_opts(&mut self, opts: CmdOverride) -> &mut Self { let CmdOverride { ref log, ref dns, ref network, } = opts; - let global_network = self.network.unwrap_or_default(); self.log.rst_field(log); self.dns.rst_field(dns); self.endpoints.iter_mut().for_each(|x| { - x.network.take_field(&global_network).rst_field(network); + x.network.rst_field(network); + }); + + self + } + + // take inner global opts + pub fn apply_global_opts(&mut self) -> &mut Self { + self.endpoints.iter_mut().for_each(|x| { + x.network.take_field(&self.network); }); self diff --git a/src/utils/types.rs b/src/utils/types.rs index 828a5859..6c46f515 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -112,20 +112,22 @@ impl Display for ConnectOpts { } if let Some(send_through) = &self.send_through { - write!(f, "send-through={}, ", send_through)?; + write!(f, "send-through={}; ", send_through)?; } write!( f, - "udp-forward={}, tcp-fast-open={}, tcp-zero-copy={}, ", + "udp-forward={}, tcp-fast-open={}, tcp-zero-copy={}; ", on_off(self.use_udp), on_off(self.fast_open), on_off(self.zero_copy) )?; + write!( f, - "send-proxy={}, accept-proxy={}, ", - self.haproxy_opts.send_proxy, self.haproxy_opts.accept_proxy + "send-proxy={0}, send-proxy-version={2}, accept-proxy={1}, accept-proxy-timeout={3}s; ", + on_off(self.haproxy_opts.send_proxy), on_off(self.haproxy_opts.accept_proxy), self.haproxy_opts.send_proxy_version, self.haproxy_opts.accept_proxy_timeout )?; + write!( f, "tcp-timeout={}s, udp-timeout={}s", @@ -138,7 +140,7 @@ impl Display for Endpoint { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, - "{} -> {}, options: {}", + "{} -> {}; options: {}", &self.listen, &self.remote, &self.opts ) } From aa34a0d3be7d4f7b7c2bcc45feae4a5e29375731 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 08:31:05 +0900 Subject: [PATCH 135/169] keep compatible with old versions --- src/conf/legacy/mod.rs | 153 +++++++++++++++++++++++++++++++++++++++++ src/conf/mod.rs | 18 +++-- 2 files changed, 166 insertions(+), 5 deletions(-) create mode 100644 src/conf/legacy/mod.rs diff --git a/src/conf/legacy/mod.rs b/src/conf/legacy/mod.rs new file mode 100644 index 00000000..2f7a5f7c --- /dev/null +++ b/src/conf/legacy/mod.rs @@ -0,0 +1,153 @@ +use serde::{Serialize, Deserialize}; + +use super::{FullConf, EndpointConf}; + +// from https://github.com/zhboner/realm/blob/8ad8f0405e97cc470ba8b76c059c203b7381d2fb/src/lib.rs#L58-L63 +// pub struct ConfigFile { +// pub listening_addresses: Vec, +// pub listening_ports: Vec, +// pub remote_addresses: Vec, +// pub remote_ports: Vec, +// } +#[derive(Serialize, Deserialize)] +pub struct LegacyConf { + pub listen_addrs: Vec, + pub listen_ports: Vec, + pub remote_addrs: Vec, + pub remote_ports: Vec, +} + +fn flatten_ports(ports: Vec) -> Vec { + ports + .into_iter() + .flat_map(|range| { + match (range.split('-').next(), range.split('-').nth(1)) { + (Some(start), Some(end)) => { + start.parse::().unwrap() + ..end.parse::().unwrap() + 1 + } + (Some(start), None) => { + start.parse::().unwrap() + ..start.parse::().unwrap() + 1 + } + _ => panic!("failed to parse ports"), + } + }) + .collect() +} + +fn join_addr_port( + addrs: Vec, + ports: Vec, + len: usize, +) -> Vec { + use std::iter::repeat; + + let port0 = ports[0]; + let addr0 = addrs[0].clone(); + + let port_iter = ports.into_iter().take(len).chain(repeat(port0)).take(len); + let addr_iter = addrs.into_iter().take(len).chain(repeat(addr0)).take(len); + + addr_iter + .zip(port_iter) + .map(|(addr, port)| format!("{}:{}", addr, port)) + .collect() +} + +impl From for FullConf { + fn from(x: LegacyConf) -> Self { + let LegacyConf { + listen_addrs, + listen_ports, + remote_addrs, + remote_ports, + } = x; + + let listen_ports = flatten_ports(listen_ports); + let remote_ports = flatten_ports(remote_ports); + + let len = listen_ports.len(); + + let listen = join_addr_port(listen_addrs, listen_ports, len); + let remote = join_addr_port(remote_addrs, remote_ports, len); + + let endpoints = listen + .into_iter() + .zip(remote.into_iter()) + .map(|(listen, remote)| EndpointConf { + listen, + remote, + through: None, + interface: None, + network: Default::default(), + }) + .collect(); + + FullConf { + endpoints, + ..Default::default() + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + macro_rules! strvec { + ( $( $x: expr ),+ ) => { + vec![ + $( + String::from($x), + )+ + ] + }; + } + + #[test] + fn test_flatten_ports() { + let v1 = strvec!["1-4"]; + let v2 = strvec!["1-2", "3-4"]; + let v3 = strvec!["1-3", "4"]; + let v4 = strvec!["1", "2", "3", "4"]; + assert_eq!(flatten_ports(v1), [1, 2, 3, 4]); + assert_eq!(flatten_ports(v2), [1, 2, 3, 4]); + assert_eq!(flatten_ports(v3), [1, 2, 3, 4]); + assert_eq!(flatten_ports(v4), [1, 2, 3, 4]); + } + + #[test] + fn test_join_addr_port() { + let addrs = strvec!["a.com", "b.com", "c.com"]; + let ports = vec![1, 2, 3]; + let result = vec!["a.com:1", "b.com:2", "c.com:3"]; + assert_eq!(join_addr_port(addrs, ports, 3), result); + + let addrs = strvec!["a.com", "b.com", "c.com"]; + let ports = vec![1, 2, 3]; + let result = vec!["a.com:1", "b.com:2", "c.com:3"]; + assert_eq!(join_addr_port(addrs, ports, 2), result[..2]); + + let addrs = strvec!["a.com", "b.com", "c.com"]; + let ports = vec![1, 2, 3]; + let result = vec!["a.com:1", "b.com:2", "c.com:3", "a.com:1"]; + assert_eq!(join_addr_port(addrs, ports, 4), result); + + let addrs = strvec!["a.com", "b.com", "c.com"]; + let ports = vec![1, 2, 3, 4, 5, 6]; + let result = vec!["a.com:1", "b.com:2", "c.com:3", "a.com:4"]; + assert_eq!(join_addr_port(addrs, ports, 4), result); + + let addrs = strvec!["a.com", "b.com", "c.com", "d.com", "e.com"]; + let ports = vec![1, 2, 3]; + let result = vec!["a.com:1", "b.com:2", "c.com:3", "d.com:1"]; + assert_eq!(join_addr_port(addrs, ports, 4), result); + + let addrs = strvec!["a.com", "b.com", "c.com"]; + let ports = vec![1, 2, 3]; + let result = + vec!["a.com:1", "b.com:2", "c.com:3", "a.com:1", "a.com:1"]; + assert_eq!(join_addr_port(addrs, ports, 5), result); + } +} diff --git a/src/conf/mod.rs b/src/conf/mod.rs index cb763dcc..01f88402 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -17,6 +17,7 @@ mod endpoint; pub use endpoint::EndpointConf; mod legacy; +use legacy::LegacyConf; pub trait Config { type Output; @@ -80,21 +81,28 @@ impl FullConf { } } - pub fn from_conf_str(conf: &str) -> Result { - let toml_err = match toml::from_str(conf) { + pub fn from_conf_str(s: &str) -> Result { + let toml_err = match toml::from_str(s) { Ok(x) => return Ok(x), Err(e) => e, }; - let json_err = match serde_json::from_str(conf) { + + let json_err = match serde_json::from_str(s) { Ok(x) => return Ok(x), Err(e) => e, }; + // to be compatible with old version + let legacy_err = match serde_json::from_str::(s) { + Ok(x) => return Ok(x.into()), + Err(e) => e, + }; + Err(Error::new( ErrorKind::Other, format!( - "parse as toml: {0}; parse as json: {1}", - &toml_err, &json_err + "parse as toml: {0}; parse as json: {1}; parse as legacy: {2}", + toml_err, json_err, legacy_err ), )) } From 86ac778c4741004261f6220096bd05775994ff0f Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 08:35:20 +0900 Subject: [PATCH 136/169] beautify code --- src/bin.rs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index f310fcda..b13240d8 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -62,11 +62,8 @@ fn start_from_conf(full: FullConf) { let eps: Vec = eps_conf .into_iter() - .map(|epc| { - let ep = epc.build(); - println!("inited: {}", &ep); - ep - }) + .map(|epc| epc.build()) + .inspect(|x| println!("inited: {}", &x)) .collect(); execute(eps); From c9f69fae744fc9b98eed6ab81a0c5f8232c0d100 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 08:49:37 +0900 Subject: [PATCH 137/169] print warnings when using a legacy config file --- examples/legacy.json | 6 ++++++ src/conf/legacy/mod.rs | 4 ++++ src/conf/mod.rs | 5 ++++- 3 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 examples/legacy.json diff --git a/examples/legacy.json b/examples/legacy.json new file mode 100644 index 00000000..abb9cb57 --- /dev/null +++ b/examples/legacy.json @@ -0,0 +1,6 @@ +{ + "listening_addresses": ["0.0.0.0"], + "listening_ports": ["8080-8088"], + "remote_addresses": ["1.1.1.1","2.2.2.2"], + "remote_ports": ["443"] +} diff --git a/src/conf/legacy/mod.rs b/src/conf/legacy/mod.rs index 2f7a5f7c..0316b9a0 100644 --- a/src/conf/legacy/mod.rs +++ b/src/conf/legacy/mod.rs @@ -11,9 +11,13 @@ use super::{FullConf, EndpointConf}; // } #[derive(Serialize, Deserialize)] pub struct LegacyConf { + #[serde(rename = "listening_addresses")] pub listen_addrs: Vec, + #[serde(rename = "listening_ports")] pub listen_ports: Vec, + #[serde(rename = "remote_addresses")] pub remote_addrs: Vec, + #[serde(rename = "remote_ports")] pub remote_ports: Vec, } diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 01f88402..cb988511 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -94,7 +94,10 @@ impl FullConf { // to be compatible with old version let legacy_err = match serde_json::from_str::(s) { - Ok(x) => return Ok(x.into()), + Ok(x) => { + eprintln!("attention: you are using a legacy config file!"); + return Ok(x.into()) + }, Err(e) => e, }; From 6e272e8e55286c1082c26b283e30d26f3870a33a Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 09:38:33 +0900 Subject: [PATCH 138/169] update to clap 3.1.0, replace deprecated code --- Cargo.lock | 4 +- Cargo.toml | 2 +- src/cmd/flag.rs | 190 ++++++++++++++++++++++++++++++++++++++++++ src/cmd/mod.rs | 216 ++++-------------------------------------------- src/cmd/sub.rs | 15 ++++ src/conf/mod.rs | 4 +- 6 files changed, 228 insertions(+), 203 deletions(-) create mode 100644 src/cmd/flag.rs create mode 100644 src/cmd/sub.rs diff --git a/Cargo.lock b/Cargo.lock index 899bc755..9032447f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -84,9 +84,9 @@ dependencies = [ [[package]] name = "clap" -version = "3.0.14" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63edc3f163b3c71ec8aa23f9bd6070f77edbf3d1d198b164afa90ff00e4ec62" +checksum = "e5f1fea81f183005ced9e59cdb01737ef2423956dac5a6d731b06b2ecfaa3467" dependencies = [ "atty", "bitflags", diff --git a/Cargo.toml b/Cargo.toml index a20a0438..7bb2dcd5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,7 +20,7 @@ libc = "0.2" bytes = "1" socket2 = "0.4" -clap = "3" +clap = "3.1" toml = "0.5" serde = { version = "1", features = ["derive"] } serde_json = "1" diff --git a/src/cmd/flag.rs b/src/cmd/flag.rs new file mode 100644 index 00000000..6525c787 --- /dev/null +++ b/src/cmd/flag.rs @@ -0,0 +1,190 @@ +use clap::{Command, Arg}; + +pub fn add_all(app: Command) -> Command { + let app = add_flags(app); + let app = add_options(app); + let app = add_global_options(app); + app +} + +pub fn add_flags(app: Command) -> Command { + app.next_help_heading("FLAGS").args(&[ + Arg::new("help") + .short('h') + .long("help") + .help("show help") + .display_order(0), + Arg::new("version") + .short('v') + .long("version") + .help("show version") + .display_order(1), + Arg::new("daemon") + .short('d') + .long("daemon") + .help("run as a unix daemon") + .display_order(2), + Arg::new("use_udp") + .short('u') + .long("udp") + .help("force enable udp forward") + .display_order(3), + Arg::new("fast_open") + .short('f') + .long("tfo") + .help("force enable tcp fast open") + .display_order(4), + Arg::new("zero_copy") + .short('z') + .long("splice") + .help("force enable tcp zero copy") + .display_order(5), + ]) +} + +pub fn add_options(app: Command) -> Command { + app.next_help_heading("OPTIONS").args(&[ + Arg::new("nofile") + .short('n') + .long("nofile") + .help("set nofile limit") + .value_name("limit") + .takes_value(true) + .display_order(0), + Arg::new("config") + .short('c') + .long("config") + .help("use config file") + .value_name("path") + .takes_value(true) + .display_order(1), + Arg::new("local") + .short('l') + .long("listen") + .help("listen address") + .value_name("address") + .takes_value(true) + .display_order(2), + Arg::new("remote") + .short('r') + .long("remote") + .help("remote address") + .value_name("address") + .takes_value(true) + .display_order(3), + Arg::new("through") + .short('x') + .long("through") + .help("send through ip or address") + .value_name("address") + .takes_value(true) + .display_order(4), + Arg::new("interface") + .short('i') + .long("interface") + .help("bind to interface") + .value_name("device") + .takes_value(true) + .display_order(5), + ]) +} + +pub fn add_global_options(app: Command) -> Command { + // log + let app = app.next_help_heading("LOG OPTIONS").args(&[ + Arg::new("log_level") + .long("log-level") + .help("override log level") + .value_name("level") + .takes_value(true) + .display_order(0), + Arg::new("log_output") + .long("log-output") + .help("override log output") + .value_name("path") + .takes_value(true) + .display_order(1), + ]); + + // dns + let app = app.next_help_heading("DNS OPTIONS").args(&[ + Arg::new("dns_mode") + .long("dns-mode") + .help("override dns mode") + .value_name("mode") + .takes_value(true) + .display_order(0), + Arg::new("dns_min_ttl") + .long("dns-min-ttl") + .help("override dns min ttl") + .value_name("second") + .takes_value(true) + .display_order(1), + Arg::new("dns_max_ttl") + .long("dns-max-ttl") + .help("override dns max ttl") + .value_name("second") + .takes_value(true) + .display_order(2), + Arg::new("dns_cache_size") + .long("dns-cache-size") + .help("override dns cache size") + .value_name("number") + .takes_value(true) + .display_order(3), + Arg::new("dns_protocol") + .long("dns-protocol") + .help("override dns protocol") + .value_name("protocol") + .takes_value(true) + .display_order(4), + Arg::new("dns_servers") + .long("dns-servers") + .help("override dns servers") + .value_name("servers") + .takes_value(true) + .display_order(5), + ]); + + // proxy-protocol belogs to network + let app = app.next_help_heading("PROXY OPTIONS").args([ + Arg::new("send_proxy") + .long("send-proxy") + .help("send proxy protocol header") + .display_order(0), + Arg::new("send_proxy_version") + .long("send-proxy-version") + .help("send proxy protocol version") + .value_name("version") + .takes_value(true) + .display_order(1), + Arg::new("accept_proxy") + .long("accept-proxy") + .help("accept proxy protocol header") + .display_order(2), + Arg::new("accept_proxy_timeout") + .long("accept-proxy-timeout") + .help("accept proxy protocol timeout") + .value_name("second") + .takes_value(true) + .display_order(3), + ]); + + // timeout belogs to network + let app = app.next_help_heading("TIMEOUT OPTIONS").args([ + Arg::new("tcp_timeout") + .long("tcp-timeout") + .help("override tcp timeout") + .value_name("second") + .takes_value(true) + .display_order(0), + Arg::new("udp_timeout") + .long("udp-timeout") + .help("override udp timeout") + .value_name("second") + .takes_value(true) + .display_order(1), + ]); + + app +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 89ad894a..7da5ec74 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -1,5 +1,4 @@ -use clap::{App, AppSettings}; -use clap::{Arg, ArgMatches}; +use clap::{Command, ArgMatches}; use crate::conf::CmdOverride; use crate::conf::EndpointConf; @@ -8,229 +7,50 @@ use crate::conf::{Config, LogConf, DnsConf, NetConf}; use crate::VERSION; use crate::utils::FEATURES; +mod sub; +mod flag; + pub enum CmdInput { Config(String, CmdOverride), Endpoint(EndpointConf, CmdOverride), None, } -fn add_flags(app: App) -> App { - app.help_heading("FLAGS").args(&[ - Arg::new("help") - .short('h') - .long("help") - .help("show help") - .display_order(0), - Arg::new("version") - .short('v') - .long("version") - .help("show version") - .display_order(1), - Arg::new("daemon") - .short('d') - .long("daemon") - .help("run as a unix daemon") - .display_order(2), - Arg::new("use_udp") - .short('u') - .long("udp") - .help("force enable udp forward") - .display_order(3), - Arg::new("fast_open") - .short('f') - .long("tfo") - .help("force enable tcp fast open") - .display_order(4), - Arg::new("zero_copy") - .short('z') - .long("splice") - .help("force enable tcp zero copy") - .display_order(5), - ]) -} - -fn add_options(app: App) -> App { - app.help_heading("OPTIONS").args(&[ - Arg::new("nofile") - .short('n') - .long("nofile") - .help("set nofile limit") - .value_name("limit") - .takes_value(true) - .display_order(0), - Arg::new("config") - .short('c') - .long("config") - .help("use config file") - .value_name("path") - .takes_value(true) - .display_order(1), - Arg::new("local") - .short('l') - .long("listen") - .help("listen address") - .value_name("address") - .takes_value(true) - .display_order(2), - Arg::new("remote") - .short('r') - .long("remote") - .help("remote address") - .value_name("address") - .takes_value(true) - .display_order(3), - Arg::new("through") - .short('x') - .long("through") - .help("send through ip or address") - .value_name("address") - .takes_value(true) - .display_order(4), - Arg::new("interface") - .short('i') - .long("interface") - .help("bind to interface") - .value_name("device") - .takes_value(true) - .display_order(5), - ]) -} - -fn add_global_options(app: App) -> App { - // log - let app = app.help_heading("LOG OPTIONS").args(&[ - Arg::new("log_level") - .long("log-level") - .help("override log level") - .value_name("level") - .takes_value(true) - .display_order(0), - Arg::new("log_output") - .long("log-output") - .help("override log output") - .value_name("path") - .takes_value(true) - .display_order(1), - ]); - - // dns - let app = app.help_heading("DNS OPTIONS").args(&[ - Arg::new("dns_mode") - .long("dns-mode") - .help("override dns mode") - .value_name("mode") - .takes_value(true) - .display_order(0), - Arg::new("dns_min_ttl") - .long("dns-min-ttl") - .help("override dns min ttl") - .value_name("second") - .takes_value(true) - .display_order(1), - Arg::new("dns_max_ttl") - .long("dns-max-ttl") - .help("override dns max ttl") - .value_name("second") - .takes_value(true) - .display_order(2), - Arg::new("dns_cache_size") - .long("dns-cache-size") - .help("override dns cache size") - .value_name("number") - .takes_value(true) - .display_order(3), - Arg::new("dns_protocol") - .long("dns-protocol") - .help("override dns protocol") - .value_name("protocol") - .takes_value(true) - .display_order(4), - Arg::new("dns_servers") - .long("dns-servers") - .help("override dns servers") - .value_name("servers") - .takes_value(true) - .display_order(5), - ]); - - // proxy-protocol belogs to network - let app = app.help_heading("PROXY OPTIONS").args([ - Arg::new("send_proxy") - .long("send-proxy") - .help("send proxy protocol header") - .display_order(0), - Arg::new("send_proxy_version") - .long("send-proxy-version") - .help("send proxy protocol version") - .value_name("version") - .takes_value(true) - .display_order(1), - Arg::new("accept_proxy") - .long("accept-proxy") - .help("accept proxy protocol header") - .display_order(2), - Arg::new("accept_proxy_timeout") - .long("accept-proxy-timeout") - .help("accept proxy protocol timeout") - .value_name("second") - .takes_value(true) - .display_order(3), - ]); - - // timeout belogs to network - let app = app.help_heading("TIMEOUT OPTIONS").args([ - Arg::new("tcp_timeout") - .long("tcp-timeout") - .help("override tcp timeout") - .value_name("second") - .takes_value(true) - .display_order(0), - Arg::new("udp_timeout") - .long("udp-timeout") - .help("override udp timeout") - .value_name("second") - .takes_value(true) - .display_order(1), - ]); - - app -} - pub fn scan() -> CmdInput { let version = format!("{} {}", VERSION, FEATURES); - let app = App::new("Realm") + let app = Command::new("Realm") .about("A high efficiency relay tool") .version(version.as_str()); let app = app - .setting(AppSettings::ArgRequiredElseHelp) - .setting(AppSettings::DisableVersionFlag) - .setting( - AppSettings::DisableHelpFlag | AppSettings::DisableHelpSubcommand, - ) + .disable_help_flag(true) + .disable_help_subcommand(true) + .disable_version_flag(true) + .arg_required_else_help(true) .override_usage("realm [FLAGS] [OPTIONS]"); - let app = add_flags(app); - let app = add_options(app); - let app = add_global_options(app); + let app = flag::add_all(app); + let app = sub::add_all(app); - let mut xapp = app.clone(); + // do other things + let mut app2 = app.clone(); let matches = app.get_matches(); if matches.is_present("help") { - xapp.print_help().unwrap(); + app2.print_help().unwrap(); return CmdInput::None; } if matches.is_present("version") { - print!("{}", xapp.render_version()); + print!("{}", app2.render_version()); return CmdInput::None; } - parse_matches(matches) + // start + handle_matches(matches) } -fn parse_matches(matches: ArgMatches) -> CmdInput { +fn handle_matches(matches: ArgMatches) -> CmdInput { #[cfg(unix)] if matches.is_present("daemon") { crate::utils::daemonize(); diff --git a/src/cmd/sub.rs b/src/cmd/sub.rs new file mode 100644 index 00000000..16a68c72 --- /dev/null +++ b/src/cmd/sub.rs @@ -0,0 +1,15 @@ +use clap::{Command, Arg, ArgMatches}; + +pub fn add_all(app: Command) -> Command { + let app = add_convert(app); + app +} + +pub fn add_convert(app: Command) -> Command { + let cvt = Command::new("convert") + .version("0.0.1") + .about("Convert your legacy configuration into an advanced one"); + + let app = app.subcommand(cvt); + app +} diff --git a/src/conf/mod.rs b/src/conf/mod.rs index cb988511..392aaa55 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -96,8 +96,8 @@ impl FullConf { let legacy_err = match serde_json::from_str::(s) { Ok(x) => { eprintln!("attention: you are using a legacy config file!"); - return Ok(x.into()) - }, + return Ok(x.into()); + } Err(e) => e, }; From 1741e9d09534349ae2014ab2eee41b5bf1ad3aa2 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 10:50:37 +0900 Subject: [PATCH 139/169] skip empty field when serializing --- src/conf/dns.rs | 23 ++++++++++++----------- src/conf/endpoint.rs | 7 +++++++ src/conf/log.rs | 6 ++++++ src/conf/mod.rs | 25 ++++++++++++++++++++++++- src/conf/net.rs | 12 ++++++++++++ 5 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/conf/dns.rs b/src/conf/dns.rs index 44d36f85..599d956b 100644 --- a/src/conf/dns.rs +++ b/src/conf/dns.rs @@ -125,24 +125,30 @@ impl From for Vec { pub struct DnsConf { // ResolverOpts #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub mode: Option, // MAX_TTL: u32 = 86400_u32 // https://docs.rs/trust-dns-resolver/latest/src/trust_dns_resolver/dns_lru.rs.html#26 #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub min_ttl: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub max_ttl: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub cache_size: Option, // ResolverConfig #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub protocol: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub nameservers: Option>, } @@ -211,6 +217,7 @@ impl Config for DnsConf { #[cfg(feature = "trust-dns")] fn build(self) -> Self::Output { + use crate::empty; use std::time::Duration; let DnsConf { @@ -226,17 +233,7 @@ impl Config for DnsConf { // default value: // https://docs.rs/trust-dns-resolver/latest/src/trust_dns_resolver/config.rs.html#681-737 - macro_rules! all_none { - ( $( $x: expr ),* ) => {{ - let mut res = true; - $( - res = res && $x.is_none(); - )* - res - }} - } - - let opts = if all_none![mode, min_ttl, max_ttl, cache_size] { + let opts = if empty![mode, min_ttl, max_ttl, cache_size] { None } else { let ip_strategy: LookupIpStrategy = @@ -355,4 +352,8 @@ impl Config for DnsConf { nameservers, } } + + fn is_empty(&self) -> bool { + crate::empty![self => mode, min_ttl, max_ttl, cache_size] + } } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index 21732e55..cc20e3cc 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -10,12 +10,15 @@ pub struct EndpointConf { pub remote: String, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub through: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub interface: Option, #[serde(default)] + #[serde(skip_serializing_if = "Config::is_empty")] pub network: NetConf, } @@ -64,6 +67,10 @@ impl EndpointConf { impl Config for EndpointConf { type Output = Endpoint; + fn is_empty(&self) -> bool { + false + } + fn build(self) -> Self::Output { let local = self.build_local(); let remote = self.build_remote(); diff --git a/src/conf/log.rs b/src/conf/log.rs index 7583a228..62fb4968 100644 --- a/src/conf/log.rs +++ b/src/conf/log.rs @@ -68,15 +68,21 @@ impl Display for LogLevel { #[derive(Default, Debug, Serialize, Deserialize, Clone)] pub struct LogConf { #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub level: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub output: Option, } impl Config for LogConf { type Output = (LevelFilter, fern::Output); + fn is_empty(&self) -> bool { + crate::empty![self => level, output] + } + fn build(self) -> Self::Output { use std::io; use std::fs::OpenOptions; diff --git a/src/conf/mod.rs b/src/conf/mod.rs index 392aaa55..e13bdd9d 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -17,11 +17,13 @@ mod endpoint; pub use endpoint::EndpointConf; mod legacy; -use legacy::LegacyConf; +pub use legacy::LegacyConf; pub trait Config { type Output; + fn is_empty(&self) -> bool; + fn build(self) -> Self::Output; // override self if other not empty @@ -45,12 +47,15 @@ pub struct CmdOverride { #[derive(Debug, Default, Serialize, Deserialize)] pub struct FullConf { #[serde(default)] + #[serde(skip_serializing_if = "Config::is_empty")] pub log: LogConf, #[serde(default)] + #[serde(skip_serializing_if = "Config::is_empty")] pub dns: DnsConf, #[serde(default)] + #[serde(skip_serializing_if = "Config::is_empty")] pub network: NetConf, pub endpoints: Vec, @@ -161,3 +166,21 @@ macro_rules! take { } }; } + +#[macro_export] +macro_rules! empty { + ( $this:expr => $( $field: ident ),* ) => {{ + let mut res = true; + $( + res = res && $this.$field.is_none(); + )* + res + }}; + ( $( $value: expr ),* ) => {{ + let mut res = true; + $( + res = res && $value.is_none(); + )* + res + }} +} diff --git a/src/conf/net.rs b/src/conf/net.rs index f9e3cfe8..ff41d40f 100644 --- a/src/conf/net.rs +++ b/src/conf/net.rs @@ -8,36 +8,48 @@ use crate::utils::PROXY_PROTOCOL_TIMEOUT; #[derive(Serialize, Debug, Deserialize, Clone, Copy, Default)] pub struct NetConf { #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub use_udp: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub fast_open: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub zero_copy: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub send_proxy: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub accept_proxy: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub send_proxy_version: Option, #[serde(default)] pub accept_proxy_timeout: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub tcp_timeout: Option, #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub udp_timeout: Option, } impl Config for NetConf { type Output = ConnectOpts; + fn is_empty(&self) -> bool { + crate::empty![self => use_udp, fast_open, zero_copy, send_proxy, accept_proxy, send_proxy_version, accept_proxy_timeout, tcp_timeout, udp_timeout] + } + fn build(self) -> Self::Output { macro_rules! unbox { ($field: ident) => { From bec662f179fa7f32ce77c26f0c25549c04e18fa1 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 10:59:25 +0900 Subject: [PATCH 140/169] add config converter --- src/cmd/flag.rs | 1 + src/cmd/mod.rs | 9 +++++++++ src/cmd/sub.rs | 46 +++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/cmd/flag.rs b/src/cmd/flag.rs index 6525c787..3e504cac 100644 --- a/src/cmd/flag.rs +++ b/src/cmd/flag.rs @@ -1,5 +1,6 @@ use clap::{Command, Arg}; +#[allow(clippy::let_and_return)] pub fn add_all(app: Command) -> Command { let app = add_flags(app); let app = add_options(app); diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 7da5ec74..0d91905d 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -46,6 +46,15 @@ pub fn scan() -> CmdInput { return CmdInput::None; } + #[allow(clippy::single_match)] + match matches.subcommand() { + Some(("convert", sub_matches)) => { + sub::handle_convert(sub_matches); + return CmdInput::None; + } + _ => {} + }; + // start handle_matches(matches) } diff --git a/src/cmd/sub.rs b/src/cmd/sub.rs index 16a68c72..0c91d0f2 100644 --- a/src/cmd/sub.rs +++ b/src/cmd/sub.rs @@ -1,5 +1,8 @@ -use clap::{Command, Arg, ArgMatches}; +use std::fs; +use clap::{Command, ArgMatches}; +use crate::conf::{FullConf, LegacyConf}; +#[allow(clippy::let_and_return)] pub fn add_all(app: Command) -> Command { let app = add_convert(app); app @@ -7,9 +10,42 @@ pub fn add_all(app: Command) -> Command { pub fn add_convert(app: Command) -> Command { let cvt = Command::new("convert") - .version("0.0.1") - .about("Convert your legacy configuration into an advanced one"); + .version("0.1.0") + .about("convert your legacy configuration into an advanced one") + .allow_missing_positional(true) + .arg_required_else_help(true) + .arg(clap::arg!([config]).required(true)) + .arg( + clap::arg!(-t --type ) + .required(false) + .default_value("toml") + .display_order(0), + ) + .arg( + clap::arg!(-o --output ) + .required(false) + .display_order(1), + ); - let app = app.subcommand(cvt); - app + app.subcommand(cvt) +} + +pub fn handle_convert(matches: &ArgMatches) { + let old = matches.value_of("config").unwrap(); + let old = fs::read(old).unwrap(); + + let data: LegacyConf = serde_json::from_slice(&old).unwrap(); + let data: FullConf = data.into(); + + let data = match matches.value_of("type").unwrap() { + "toml" => toml::to_string(&data).unwrap(), + "json" => serde_json::to_string(&data).unwrap(), + _ => unreachable!(), + }; + + if let Some(out) = matches.value_of("output") { + fs::write(out, &data).unwrap(); + } else { + println!("{}", &data) + } } From 85c45b2b1e0640e0c9f4e6cac37fa2ec59cfeb2e Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 21:04:19 +0900 Subject: [PATCH 141/169] fix typo --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index c88b4566..f9543778 100644 --- a/readme.md +++ b/readme.md @@ -359,7 +359,7 @@ default: 300 Terminate udp association after `timeout`. -The timeout value mut be properly configured in case of memory leak. Do not use a large `timeout`! +The timeout value must be properly configured in case of memory leak. Do not use a large `timeout`! default: 30 @@ -415,7 +415,7 @@ Remote address, supported formats: #### endpoint.through: string -TCP: Bind a specific `ip` before openning a connection +TCP: Bind a specific `ip` before opening a connection UDP: Bind a specific `ip` or `address` before sending packet From 91e7cad651b946195161577f2210f6d0781767f6 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 21:08:52 +0900 Subject: [PATCH 142/169] docs --- readme.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/readme.md b/readme.md index f9543778..11a08341 100644 --- a/readme.md +++ b/readme.md @@ -111,6 +111,9 @@ PROXY OPTIONS: TIMEOUT OPTIONS: --tcp-timeout override tcp timeout --udp-timeout override udp timeout + +SUBCOMMANDS: + convert convert your legacy configuration into an advanced one ``` Start from command line arguments: @@ -139,6 +142,12 @@ export REALM_CONF=`cat config.json | jq -c ` realm ``` +Convert a legacy config file: + +```shell +realm convert old.json +``` + ## Configuration TOML Example From 3118d3ace902bdfa1e05e1fac267ecf3b9bb939e Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 21:35:12 +0900 Subject: [PATCH 143/169] rename raw ptr wrapper; export tcp, udp entrance --- src/relay/mod.rs | 10 +++++----- src/relay/tcp/mod.rs | 6 +++--- src/relay/udp.rs | 4 ++-- src/utils/mod.rs | 4 ++-- src/utils/{ex_types.rs => ref_types.rs} | 10 +++++++--- 5 files changed, 19 insertions(+), 15 deletions(-) rename src/utils/{ex_types.rs => ref_types.rs} (76%) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 8615e1ee..baac53ef 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -5,7 +5,7 @@ mod tcp; use tcp::TcpListener; use crate::utils::Endpoint; -use crate::utils::{EndpointX, RemoteAddrX, ConnectOptsX}; +use crate::utils::{EndpointRef, RemoteAddrRef, ConnectOptsRef}; pub async fn run(eps: Vec) { let mut workers = Vec::with_capacity(compute_workers(&eps)); @@ -19,7 +19,7 @@ pub async fn run(eps: Vec) { join_all(workers).await; } -async fn run_tcp(ep: EndpointX) { +pub async fn run_tcp(ep: EndpointRef) { let Endpoint { listen, remote, @@ -27,8 +27,8 @@ async fn run_tcp(ep: EndpointX) { .. } = ep.as_ref(); - let remote: RemoteAddrX = remote.into(); - let opts: ConnectOptsX = opts.into(); + let remote: RemoteAddrRef = remote.into(); + let opts: ConnectOptsRef = opts.into(); let lis = TcpListener::bind(*listen) .await @@ -69,7 +69,7 @@ async fn run_tcp(ep: EndpointX) { mod udp; #[cfg(feature = "udp")] -async fn run_udp(ep: EndpointX) { +pub async fn run_udp(ep: EndpointRef) { let Endpoint { listen, remote, diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 9625e871..69a30d3e 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -23,13 +23,13 @@ use tokio::net::TcpSocket; use crate::utils::socket; use crate::utils::ConnectOpts; -use crate::utils::{RemoteAddrX, ConnectOptsX}; +use crate::utils::{RemoteAddrRef, ConnectOptsRef}; #[allow(unused_variables)] pub async fn connect_and_relay( mut inbound: TcpStream, - remote: RemoteAddrX, - conn_opts: ConnectOptsX, + remote: RemoteAddrRef, + conn_opts: ConnectOptsRef, ) -> Result<(u64, u64)> { let ConnectOpts { fast_open, diff --git a/src/relay/udp.rs b/src/relay/udp.rs index b3ade40a..c52abb06 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -9,7 +9,7 @@ use tokio::net::UdpSocket; use crate::utils::DEFAULT_BUF_SIZE; use crate::utils::RemoteAddr; -use crate::utils::ConnectOptsX; +use crate::utils::ConnectOptsRef; use crate::utils::timeoutfut; use crate::utils::socket; @@ -20,7 +20,7 @@ const BUF_SIZE: usize = DEFAULT_BUF_SIZE; pub async fn associate_and_relay( listen: &SocketAddr, remote: &RemoteAddr, - conn_opts: ConnectOptsX, + conn_opts: ConnectOptsRef, ) -> Result<()> { let timeout = conn_opts.udp_timeout; let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); diff --git a/src/utils/mod.rs b/src/utils/mod.rs index b99b5765..71f5c7c2 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -7,8 +7,8 @@ pub use consts::*; pub mod timeout; pub use timeout::*; -pub mod ex_types; -pub use ex_types::*; +pub mod ref_types; +pub use ref_types::*; pub mod socket; pub use socket::*; diff --git a/src/utils/ex_types.rs b/src/utils/ref_types.rs similarity index 76% rename from src/utils/ex_types.rs rename to src/utils/ref_types.rs index 0f877853..93fa442c 100644 --- a/src/utils/ex_types.rs +++ b/src/utils/ref_types.rs @@ -2,6 +2,10 @@ use core::ops::Deref; use super::{Endpoint, RemoteAddr, ConnectOpts}; +// Safety: +// pointer is not null once inited(comes from an immutable ref) +// pointee memory is always valid during the eventloop + macro_rules! ptr_wrap { ($old: ident,$new: ident) => { #[derive(Clone, Copy)] @@ -36,6 +40,6 @@ macro_rules! ptr_wrap { }; } -ptr_wrap!(Endpoint, EndpointX); -ptr_wrap!(RemoteAddr, RemoteAddrX); -ptr_wrap!(ConnectOpts, ConnectOptsX); +ptr_wrap!(Endpoint, EndpointRef); +ptr_wrap!(RemoteAddr, RemoteAddrRef); +ptr_wrap!(ConnectOpts, ConnectOptsRef); From 71f22f467efbe4de4bd5b8743a101fb07bd6b90a Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 23:31:03 +0900 Subject: [PATCH 144/169] add tcp test --- Cargo.toml | 1 + src/utils/types.rs | 12 ++++++++++-- tests/tcp.rs | 47 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/tcp.rs diff --git a/Cargo.toml b/Cargo.toml index 7bb2dcd5..fe310267 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -72,3 +72,4 @@ mi-malloc = ["mimalloc"] [dev-dependencies] env_logger = "0.9" +tokio = {version = "1", features = ["macros"]} diff --git a/src/utils/types.rs b/src/utils/types.rs index 6c46f515..705fcebe 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -10,7 +10,7 @@ pub enum RemoteAddr { DomainName(String, u16), } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Default)] pub struct HaproxyOpts { pub send_proxy: bool, pub accept_proxy: bool, @@ -18,7 +18,7 @@ pub struct HaproxyOpts { pub accept_proxy_timeout: usize, } -#[derive(Clone)] +#[derive(Clone, Default)] pub struct ConnectOpts { pub use_udp: bool, pub fast_open: bool, @@ -73,6 +73,12 @@ impl RemoteAddr { } } +impl From for RemoteAddr { + fn from(addr: SocketAddr) -> Self { + RemoteAddr::SocketAddr(addr) + } +} + impl Endpoint { pub fn new( listen: SocketAddr, @@ -87,6 +93,8 @@ impl Endpoint { } } +// display impl below + impl Display for RemoteAddr { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { use RemoteAddr::*; diff --git a/tests/tcp.rs b/tests/tcp.rs new file mode 100644 index 00000000..75088bc1 --- /dev/null +++ b/tests/tcp.rs @@ -0,0 +1,47 @@ +use std::net::SocketAddr; + +use tokio::net::{TcpStream, TcpListener}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use realm::relay::run_tcp; +use realm::utils::Endpoint; +use realm::utils::timeoutfut; + +#[tokio::test] +async fn test_tcp() { + env_logger::init(); + let endpoint = Endpoint { + listen: "127.0.0.1:10000".parse().unwrap(), + remote: "127.0.0.1:20000".parse::().unwrap().into(), + opts: Default::default(), + }; + + tokio::spawn(async { + let mut stream = TcpStream::connect("127.0.0.1:10000").await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + stream.write(b"Ping Ping Ping").await.unwrap(); + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("a got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Pong Pong Pong", &buf[..n]); + } + }); + + tokio::spawn(async { + let lis = TcpListener::bind("127.0.0.1:20000").await.unwrap(); + let (mut stream, _) = lis.accept().await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("b got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Ping Ping Ping", &buf[..n]); + stream.write(b"Pong Pong Pong").await.unwrap(); + } + }); + + let _ = timeoutfut(run_tcp((&endpoint).into()), 3).await; +} From bb86cefdf772bb5cf7bc6f1f39a1f82b7ce5925c Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 23:35:37 +0900 Subject: [PATCH 145/169] add zero_copy test --- tests/zero_copy.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/zero_copy.rs diff --git a/tests/zero_copy.rs b/tests/zero_copy.rs new file mode 100644 index 00000000..ff1db7c0 --- /dev/null +++ b/tests/zero_copy.rs @@ -0,0 +1,50 @@ +use std::net::SocketAddr; + +use tokio::net::{TcpStream, TcpListener}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use realm::relay::run_tcp; +use realm::utils::{Endpoint, ConnectOpts}; +use realm::utils::timeoutfut; + +#[tokio::test] +async fn test_zero_copy() { + env_logger::init(); + let endpoint = Endpoint { + listen: "127.0.0.1:10000".parse().unwrap(), + remote: "127.0.0.1:20000".parse::().unwrap().into(), + opts: ConnectOpts { + zero_copy: true, + ..Default::default() + }, + }; + + tokio::spawn(async { + let mut stream = TcpStream::connect("127.0.0.1:10000").await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + stream.write(b"Ping Ping Ping").await.unwrap(); + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("a got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Pong Pong Pong", &buf[..n]); + } + }); + + tokio::spawn(async { + let lis = TcpListener::bind("127.0.0.1:20000").await.unwrap(); + let (mut stream, _) = lis.accept().await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("b got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Ping Ping Ping", &buf[..n]); + stream.write(b"Pong Pong Pong").await.unwrap(); + } + }); + + let _ = timeoutfut(run_tcp((&endpoint).into()), 3).await; +} From 0c0fc90aad274a459743783e862dc1efb3d1f166 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sun, 20 Feb 2022 23:45:02 +0900 Subject: [PATCH 146/169] add proxy_protocol test --- tests/proxy_protocol_v1.rs | 73 ++++++++++++++++++++++++++++++++++++++ tests/proxy_protocol_v2.rs | 73 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 tests/proxy_protocol_v1.rs create mode 100644 tests/proxy_protocol_v2.rs diff --git a/tests/proxy_protocol_v1.rs b/tests/proxy_protocol_v1.rs new file mode 100644 index 00000000..a86fdffe --- /dev/null +++ b/tests/proxy_protocol_v1.rs @@ -0,0 +1,73 @@ +use std::net::SocketAddr; +use futures::future::join; + +use tokio::net::{TcpStream, TcpListener}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use realm::relay::run_tcp; +use realm::utils::{Endpoint, ConnectOpts, HaproxyOpts}; +use realm::utils::timeoutfut; + +#[tokio::test] +async fn test_proxy_protocol_v1() { + env_logger::init(); + + let endpoint1 = Endpoint { + listen: "127.0.0.1:10000".parse().unwrap(), + remote: "127.0.0.1:15000".parse::().unwrap().into(), + opts: ConnectOpts { + haproxy_opts: HaproxyOpts { + send_proxy: true, + send_proxy_version: 1, + ..Default::default() + }, + ..Default::default() + }, + }; + + let endpoint2 = Endpoint { + listen: "127.0.0.1:15000".parse().unwrap(), + remote: "127.0.0.1:20000".parse::().unwrap().into(), + opts: ConnectOpts { + haproxy_opts: HaproxyOpts { + accept_proxy: true, + accept_proxy_timeout: 5, + ..Default::default() + }, + ..Default::default() + }, + }; + + tokio::spawn(async { + let mut stream = TcpStream::connect("127.0.0.1:10000").await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + stream.write(b"Ping Ping Ping").await.unwrap(); + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("a got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Pong Pong Pong", &buf[..n]); + } + }); + + tokio::spawn(async { + let lis = TcpListener::bind("127.0.0.1:20000").await.unwrap(); + let (mut stream, _) = lis.accept().await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("b got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Ping Ping Ping", &buf[..n]); + stream.write(b"Pong Pong Pong").await.unwrap(); + } + }); + + let _ = join( + timeoutfut(run_tcp((&endpoint1).into()), 3), + timeoutfut(run_tcp((&endpoint2).into()), 3), + ) + .await; +} diff --git a/tests/proxy_protocol_v2.rs b/tests/proxy_protocol_v2.rs new file mode 100644 index 00000000..3886c6e6 --- /dev/null +++ b/tests/proxy_protocol_v2.rs @@ -0,0 +1,73 @@ +use std::net::SocketAddr; +use futures::future::join; + +use tokio::net::{TcpStream, TcpListener}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; + +use realm::relay::run_tcp; +use realm::utils::{Endpoint, ConnectOpts, HaproxyOpts}; +use realm::utils::timeoutfut; + +#[tokio::test] +async fn test_proxy_protocol_v1() { + env_logger::init(); + + let endpoint1 = Endpoint { + listen: "127.0.0.1:10000".parse().unwrap(), + remote: "127.0.0.1:15000".parse::().unwrap().into(), + opts: ConnectOpts { + haproxy_opts: HaproxyOpts { + send_proxy: true, + send_proxy_version: 2, + ..Default::default() + }, + ..Default::default() + }, + }; + + let endpoint2 = Endpoint { + listen: "127.0.0.1:15000".parse().unwrap(), + remote: "127.0.0.1:20000".parse::().unwrap().into(), + opts: ConnectOpts { + haproxy_opts: HaproxyOpts { + accept_proxy: true, + accept_proxy_timeout: 5, + ..Default::default() + }, + ..Default::default() + }, + }; + + tokio::spawn(async { + let mut stream = TcpStream::connect("127.0.0.1:10000").await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + stream.write(b"Ping Ping Ping").await.unwrap(); + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("a got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Pong Pong Pong", &buf[..n]); + } + }); + + tokio::spawn(async { + let lis = TcpListener::bind("127.0.0.1:20000").await.unwrap(); + let (mut stream, _) = lis.accept().await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + let n = stream.read(&mut buf).await.unwrap(); + log::debug!("b got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Ping Ping Ping", &buf[..n]); + stream.write(b"Pong Pong Pong").await.unwrap(); + } + }); + + let _ = join( + timeoutfut(run_tcp((&endpoint1).into()), 3), + timeoutfut(run_tcp((&endpoint2).into()), 3), + ) + .await; +} From 6d2eda7d394463591fd68f1670ab6776a1e39be4 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 21 Feb 2022 00:02:05 +0900 Subject: [PATCH 147/169] add udp test --- tests/udp.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 tests/udp.rs diff --git a/tests/udp.rs b/tests/udp.rs new file mode 100644 index 00000000..5aae1a8c --- /dev/null +++ b/tests/udp.rs @@ -0,0 +1,50 @@ +use std::net::SocketAddr; + +use tokio::net::UdpSocket; + +use realm::relay::run_udp; +use realm::utils::{Endpoint, ConnectOpts}; +use realm::utils::timeoutfut; + +#[tokio::test] +async fn test_udp() { + env_logger::init(); + let endpoint = Endpoint { + listen: "127.0.0.1:10000".parse().unwrap(), + remote: "127.0.0.1:20000".parse::().unwrap().into(), + opts: ConnectOpts { + udp_timeout: 20, + ..Default::default() + }, + }; + + tokio::spawn(async { + let socket = UdpSocket::bind("127.0.0.1:0").await.unwrap(); + + let mut buf = vec![0; 32]; + let peer: SocketAddr = "127.0.0.1:10000".parse().unwrap(); + + for _ in 0..20 { + socket.send_to(b"Ping Ping Ping", &peer).await.unwrap(); + let (n, peer2) = socket.recv_from(&mut buf).await.unwrap(); + assert_eq!(peer, peer2); + log::debug!("a got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Pong Pong Pong", &buf[..n]); + } + }); + + tokio::spawn(async { + let socket = UdpSocket::bind("127.0.0.1:20000").await.unwrap(); + + let mut buf = vec![0; 32]; + + for _ in 0..20 { + let (n, peer) = socket.recv_from(&mut buf).await.unwrap(); + log::debug!("b got: {:?}", std::str::from_utf8(&buf[..n]).unwrap()); + assert_eq!(b"Ping Ping Ping", &buf[..n]); + socket.send_to(b"Pong Pong Pong", peer).await.unwrap(); + } + }); + + let _ = timeoutfut(run_udp((&endpoint).into()), 3).await; +} From 1b6f26014d6c7e31fa49cf2fe4d965478ad8782a Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 21 Feb 2022 00:05:41 +0900 Subject: [PATCH 148/169] rename tests --- src/conf/legacy/mod.rs | 26 ++++++++++----------- tests/{proxy_protocol_v1.rs => proxy_v1.rs} | 2 +- tests/{proxy_protocol_v2.rs => proxy_v2.rs} | 2 +- tests/tcp.rs | 2 +- tests/udp.rs | 2 +- tests/zero_copy.rs | 2 +- 6 files changed, 17 insertions(+), 19 deletions(-) rename tests/{proxy_protocol_v1.rs => proxy_v1.rs} (98%) rename tests/{proxy_protocol_v2.rs => proxy_v2.rs} (98%) diff --git a/src/conf/legacy/mod.rs b/src/conf/legacy/mod.rs index 0316b9a0..8de621b5 100644 --- a/src/conf/legacy/mod.rs +++ b/src/conf/legacy/mod.rs @@ -97,8 +97,6 @@ impl From for FullConf { #[cfg(test)] mod tests { - use super::*; - macro_rules! strvec { ( $( $x: expr ),+ ) => { vec![ @@ -110,48 +108,48 @@ mod tests { } #[test] - fn test_flatten_ports() { + fn flatten_ports() { let v1 = strvec!["1-4"]; let v2 = strvec!["1-2", "3-4"]; let v3 = strvec!["1-3", "4"]; let v4 = strvec!["1", "2", "3", "4"]; - assert_eq!(flatten_ports(v1), [1, 2, 3, 4]); - assert_eq!(flatten_ports(v2), [1, 2, 3, 4]); - assert_eq!(flatten_ports(v3), [1, 2, 3, 4]); - assert_eq!(flatten_ports(v4), [1, 2, 3, 4]); + assert_eq!(super::flatten_ports(v1), [1, 2, 3, 4]); + assert_eq!(super::flatten_ports(v2), [1, 2, 3, 4]); + assert_eq!(super::flatten_ports(v3), [1, 2, 3, 4]); + assert_eq!(super::flatten_ports(v4), [1, 2, 3, 4]); } #[test] - fn test_join_addr_port() { + fn join_addr_port() { let addrs = strvec!["a.com", "b.com", "c.com"]; let ports = vec![1, 2, 3]; let result = vec!["a.com:1", "b.com:2", "c.com:3"]; - assert_eq!(join_addr_port(addrs, ports, 3), result); + assert_eq!(super::join_addr_port(addrs, ports, 3), result); let addrs = strvec!["a.com", "b.com", "c.com"]; let ports = vec![1, 2, 3]; let result = vec!["a.com:1", "b.com:2", "c.com:3"]; - assert_eq!(join_addr_port(addrs, ports, 2), result[..2]); + assert_eq!(super::join_addr_port(addrs, ports, 2), result[..2]); let addrs = strvec!["a.com", "b.com", "c.com"]; let ports = vec![1, 2, 3]; let result = vec!["a.com:1", "b.com:2", "c.com:3", "a.com:1"]; - assert_eq!(join_addr_port(addrs, ports, 4), result); + assert_eq!(super::join_addr_port(addrs, ports, 4), result); let addrs = strvec!["a.com", "b.com", "c.com"]; let ports = vec![1, 2, 3, 4, 5, 6]; let result = vec!["a.com:1", "b.com:2", "c.com:3", "a.com:4"]; - assert_eq!(join_addr_port(addrs, ports, 4), result); + assert_eq!(super::join_addr_port(addrs, ports, 4), result); let addrs = strvec!["a.com", "b.com", "c.com", "d.com", "e.com"]; let ports = vec![1, 2, 3]; let result = vec!["a.com:1", "b.com:2", "c.com:3", "d.com:1"]; - assert_eq!(join_addr_port(addrs, ports, 4), result); + assert_eq!(super::join_addr_port(addrs, ports, 4), result); let addrs = strvec!["a.com", "b.com", "c.com"]; let ports = vec![1, 2, 3]; let result = vec!["a.com:1", "b.com:2", "c.com:3", "a.com:1", "a.com:1"]; - assert_eq!(join_addr_port(addrs, ports, 5), result); + assert_eq!(super::join_addr_port(addrs, ports, 5), result); } } diff --git a/tests/proxy_protocol_v1.rs b/tests/proxy_v1.rs similarity index 98% rename from tests/proxy_protocol_v1.rs rename to tests/proxy_v1.rs index a86fdffe..6d1f9848 100644 --- a/tests/proxy_protocol_v1.rs +++ b/tests/proxy_v1.rs @@ -9,7 +9,7 @@ use realm::utils::{Endpoint, ConnectOpts, HaproxyOpts}; use realm::utils::timeoutfut; #[tokio::test] -async fn test_proxy_protocol_v1() { +async fn proxy_v1() { env_logger::init(); let endpoint1 = Endpoint { diff --git a/tests/proxy_protocol_v2.rs b/tests/proxy_v2.rs similarity index 98% rename from tests/proxy_protocol_v2.rs rename to tests/proxy_v2.rs index 3886c6e6..6e9428af 100644 --- a/tests/proxy_protocol_v2.rs +++ b/tests/proxy_v2.rs @@ -9,7 +9,7 @@ use realm::utils::{Endpoint, ConnectOpts, HaproxyOpts}; use realm::utils::timeoutfut; #[tokio::test] -async fn test_proxy_protocol_v1() { +async fn proxy_v1() { env_logger::init(); let endpoint1 = Endpoint { diff --git a/tests/tcp.rs b/tests/tcp.rs index 75088bc1..0f8c934f 100644 --- a/tests/tcp.rs +++ b/tests/tcp.rs @@ -8,7 +8,7 @@ use realm::utils::Endpoint; use realm::utils::timeoutfut; #[tokio::test] -async fn test_tcp() { +async fn tcp() { env_logger::init(); let endpoint = Endpoint { listen: "127.0.0.1:10000".parse().unwrap(), diff --git a/tests/udp.rs b/tests/udp.rs index 5aae1a8c..4208243a 100644 --- a/tests/udp.rs +++ b/tests/udp.rs @@ -7,7 +7,7 @@ use realm::utils::{Endpoint, ConnectOpts}; use realm::utils::timeoutfut; #[tokio::test] -async fn test_udp() { +async fn udp() { env_logger::init(); let endpoint = Endpoint { listen: "127.0.0.1:10000".parse().unwrap(), diff --git a/tests/zero_copy.rs b/tests/zero_copy.rs index ff1db7c0..4fad9c9b 100644 --- a/tests/zero_copy.rs +++ b/tests/zero_copy.rs @@ -8,7 +8,7 @@ use realm::utils::{Endpoint, ConnectOpts}; use realm::utils::timeoutfut; #[tokio::test] -async fn test_zero_copy() { +async fn zero_copy() { env_logger::init(); let endpoint = Endpoint { listen: "127.0.0.1:10000".parse().unwrap(), From b6df4b1ed65291b7831ab8535a698546fb5f2b59 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 21 Feb 2022 00:44:07 +0900 Subject: [PATCH 149/169] beautify code --- src/bin.rs | 8 ++++---- src/relay/mod.rs | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/bin.rs b/src/bin.rs index b13240d8..0b133546 100644 --- a/src/bin.rs +++ b/src/bin.rs @@ -53,20 +53,20 @@ fn start_from_conf(full: FullConf) { let FullConf { log: log_conf, dns: dns_conf, - endpoints: eps_conf, + endpoints: endpoints_conf, .. } = full; setup_log(log_conf); setup_dns(dns_conf); - let eps: Vec = eps_conf + let endpoints: Vec = endpoints_conf .into_iter() - .map(|epc| epc.build()) + .map(|x| x.build()) .inspect(|x| println!("inited: {}", &x)) .collect(); - execute(eps); + execute(endpoints); } fn setup_log(log: LogConf) { diff --git a/src/relay/mod.rs b/src/relay/mod.rs index baac53ef..cceb6ece 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -7,25 +7,25 @@ use tcp::TcpListener; use crate::utils::Endpoint; use crate::utils::{EndpointRef, RemoteAddrRef, ConnectOptsRef}; -pub async fn run(eps: Vec) { - let mut workers = Vec::with_capacity(compute_workers(&eps)); - for ep in eps.iter() { +pub async fn run(endpoints: Vec) { + let mut workers = Vec::with_capacity(compute_workers(&endpoints)); + for endpoint in endpoints.iter() { #[cfg(feature = "udp")] - if ep.opts.use_udp { - workers.push(tokio::spawn(run_udp(ep.into()))) + if endpoint.opts.use_udp { + workers.push(tokio::spawn(run_udp(endpoint.into()))) } - workers.push(tokio::spawn(run_tcp(ep.into()))); + workers.push(tokio::spawn(run_tcp(endpoint.into()))); } join_all(workers).await; } -pub async fn run_tcp(ep: EndpointRef) { +pub async fn run_tcp(endpoint: EndpointRef) { let Endpoint { listen, remote, opts, .. - } = ep.as_ref(); + } = endpoint.as_ref(); let remote: RemoteAddrRef = remote.into(); let opts: ConnectOptsRef = opts.into(); @@ -69,13 +69,13 @@ pub async fn run_tcp(ep: EndpointRef) { mod udp; #[cfg(feature = "udp")] -pub async fn run_udp(ep: EndpointRef) { +pub async fn run_udp(endpoint: EndpointRef) { let Endpoint { listen, remote, opts, .. - } = ep.as_ref(); + } = endpoint.as_ref(); if let Err(e) = udp::associate_and_relay(listen, remote, opts.into()).await { From 468e9db67dd7051c9ad459af803b717a5b1ee79e Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 21 Feb 2022 00:57:54 +0900 Subject: [PATCH 150/169] update workflow --- .github/workflows/ci.yml | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 923e3607..a6999d01 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,13 +4,24 @@ jobs: clippy_check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 + - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: toolchain: nightly components: clippy + default: true override: true - uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} args: --all-features + run_test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + default: true + override: true + - run: cargo test -v --no-fail-fast From a8d285c05145a5b41703e03e73b77cec81c7a001 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 21 Feb 2022 02:18:03 +0900 Subject: [PATCH 151/169] rebase --- Cargo.lock | 2 +- Cargo.toml | 2 +- readme.md | 2 +- src/lib.rs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9032447f..44dfdb40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -686,7 +686,7 @@ dependencies = [ [[package]] name = "realm" -version = "1.5.0" +version = "2.0.0" dependencies = [ "bytes", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index fe310267..ac1f2a4e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "realm" -version = "1.5.0" +version = "2.0.0" authors = ["zhboner "] edition = "2021" diff --git a/readme.md b/readme.md index 11a08341..6747de84 100644 --- a/readme.md +++ b/readme.md @@ -68,7 +68,7 @@ Or have a look at [Cross](https://github.com/cross-rs/cross), it makes things ea ## Usage ```shell -Realm 1.5.x [udp][zero-copy][trust-dns][proxy-protocol][multi-thread] +Realm 2.0.0 [udp][zero-copy][trust-dns][proxy-protocol][multi-thread] A high efficiency relay tool USAGE: diff --git a/src/lib.rs b/src/lib.rs index 98a65cfc..ecb91843 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,5 +4,5 @@ pub mod conf; pub mod utils; pub mod relay; -pub const VERSION: &str = "1.5.0-rc12"; +pub const VERSION: &str = "2.0.0"; pub const ENV_CONFIG: &str = "REALM_CONF"; From 7f86db4b1196bb239bd7a4a5c24432e4583ad733 Mon Sep 17 00:00:00 2001 From: zephyr Date: Mon, 21 Feb 2022 05:11:55 +0900 Subject: [PATCH 152/169] update badge link --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 6747de84..b6243f64 100644 --- a/readme.md +++ b/readme.md @@ -1,5 +1,5 @@ -![realm](https://github.com/zephyrchien/realm/workflows/ci/badge.svg) -![realm](https://github.com/zephyrchien/realm/workflows/release/badge.svg) +![realm](../../workflows/ci/badge.svg) +![realm](../../workflows/release/badge.svg) [中文说明](https://zhb.me/realm) From 1489b40239e3efca6f8f3e50c8648c245aa9d9a3 Mon Sep 17 00:00:00 2001 From: zephyr <51367850+zephyrchien@users.noreply.github.com> Date: Mon, 21 Feb 2022 05:18:04 +0900 Subject: [PATCH 153/169] Update ci.yml --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a6999d01..bf5f27c3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,9 @@ name: ci -on: push +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] jobs: clippy_check: runs-on: ubuntu-latest From 26d802a7d20f729064f52bf3f6559558ae28941a Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 23 Feb 2022 02:45:13 +0900 Subject: [PATCH 154/169] move some log from info to debug level --- src/relay/mod.rs | 7 ++----- src/relay/tcp/mod.rs | 8 ++++++-- src/relay/tcp/zio.rs | 38 +++++++++++++++++++++----------------- 3 files changed, 29 insertions(+), 24 deletions(-) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index cceb6ece..0e9dec03 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -1,4 +1,4 @@ -use log::{info, warn, error}; +use log::{debug, info, warn, error}; use futures::future::join_all; mod tcp; @@ -55,10 +55,7 @@ pub async fn run_tcp(endpoint: EndpointRef) { tokio::spawn(async move { match tcp::connect_and_relay(stream, remote, opts).await { - Ok((up, dl)) => info!( - "[tcp]{} finish, upload: {}b, download: {}b", - msg, up, dl - ), + Ok(..) => debug!("[tcp]{}, finish", msg), Err(e) => error!("[tcp]{}, error: {}", msg, e), } }); diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 69a30d3e..f4160d73 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -30,7 +30,7 @@ pub async fn connect_and_relay( mut inbound: TcpStream, remote: RemoteAddrRef, conn_opts: ConnectOptsRef, -) -> Result<(u64, u64)> { +) -> Result<()> { let ConnectOpts { fast_open, zero_copy, @@ -79,5 +79,9 @@ pub async fn connect_and_relay( #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] let res = zio::bidi_copy_buffer(&mut inbound, &mut outbound).await; - res + if let Err(e) = res { + debug!("[tcp]forward error: {}, ignored", e); + } + + Ok(()) } diff --git a/src/relay/tcp/zio.rs b/src/relay/tcp/zio.rs index db87a43e..1901356b 100644 --- a/src/relay/tcp/zio.rs +++ b/src/relay/tcp/zio.rs @@ -94,7 +94,7 @@ where cx: &mut Context<'_>, r: &mut TcpStream, w: &mut TcpStream, - ) -> Poll> { + ) -> Poll> { loop { // If our buffer is empty, then we need to read some data to // continue. @@ -123,6 +123,9 @@ where } // If our buffer has some data, let's write it out! + // Note: send may return ECONNRESET but splice wont, see + // https://man7.org/linux/man-pages/man2/send.2.html + // https://man7.org/linux/man-pages/man2/splice.2.html while self.pos < self.cap { let i = ready!(self.poll_write_tcp(cx, w))?; @@ -150,7 +153,7 @@ where // data and finish the transfer. if self.pos == self.cap && self.read_done { ready!(self.poll_flush_tcp(cx, w))?; - return Poll::Ready(Ok(self.amt)); + return Poll::Ready(Ok(())); } } } @@ -158,8 +161,8 @@ where enum TransferState { Running(CopyBuffer), - ShuttingDown(u64), - Done(u64), + ShuttingDown, + Done, } struct BidiCopy<'a, B> { @@ -174,22 +177,23 @@ fn transfer_one_direction( state: &mut TransferState, r: &mut TcpStream, w: &mut TcpStream, -) -> Poll> +) -> Poll> where CopyBuffer: Require, { loop { match state { TransferState::Running(buf) => { - let count = ready!(buf.poll_copy(cx, r, w))?; - *state = TransferState::ShuttingDown(count); + ready!(buf.poll_copy(cx, r, w))?; + + *state = TransferState::ShuttingDown; } - TransferState::ShuttingDown(count) => { + TransferState::ShuttingDown => { ready!(Pin::new(&mut *w).poll_shutdown(cx))?; - *state = TransferState::Done(*count); + *state = TransferState::Done; } - TransferState::Done(count) => return Poll::Ready(Ok(*count)), + TransferState::Done => return Poll::Ready(Ok(())), } } } @@ -199,7 +203,7 @@ where B: Unpin, CopyBuffer: Require, { - type Output = Result<(u64, u64)>; + type Output = Result<()>; fn poll( mut self: Pin<&mut Self>, @@ -218,17 +222,17 @@ where // It is not a problem if ready! returns early because transfer_one_direction for the // other direction will keep returning TransferState::Done(count) in future calls to poll - let a_to_b = ready!(a_to_b); - let b_to_a = ready!(b_to_a); + ready!(a_to_b); + ready!(b_to_a); - Poll::Ready(Ok((a_to_b, b_to_a))) + Poll::Ready(Ok(())) } } // async fn bidi_copy( // a: &mut TcpStream, // b: &mut TcpStream, -// ) -> Result<(u64, u64)> +// ) -> Result<(())> // where // B: Unpin, // CopyBuffer: Require, @@ -247,7 +251,7 @@ where pub async fn bidi_copy_buffer( a: &mut TcpStream, b: &mut TcpStream, -) -> Result<(u64, u64)> { +) -> Result<()> { let a_to_b = TransferState::Running(CopyBuffer::>::new().unwrap()); let b_to_a = @@ -265,7 +269,7 @@ pub async fn bidi_copy_buffer( pub async fn bidi_copy_pipe( a: &mut TcpStream, b: &mut TcpStream, -) -> Result<(u64, u64)> { +) -> Result<()> { use zero_copy::Pipe; let a_to_b = TransferState::Running(CopyBuffer::::new()?); let b_to_a = TransferState::Running(CopyBuffer::::new()?); From 0a84af49f10a0e1ad7411d08932bebcbb644aa23 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 23 Feb 2022 03:56:58 +0900 Subject: [PATCH 155/169] optimize udp --- src/relay/mod.rs | 25 +++++-- src/relay/udp.rs | 148 +++++++++++++++++------------------------ src/utils/ref_types.rs | 17 +++++ 3 files changed, 97 insertions(+), 93 deletions(-) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 0e9dec03..171194af 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -32,13 +32,13 @@ pub async fn run_tcp(endpoint: EndpointRef) { let lis = TcpListener::bind(*listen) .await - .unwrap_or_else(|e| panic!("unable to bind {}: {}", &listen, &e)); + .unwrap_or_else(|e| panic!("[tcp]unable to bind {}: {}", &listen, e)); loop { let (stream, addr) = match lis.accept().await { Ok(x) => x, Err(e) => { - error!("[tcp]failed to accept: {}", &e); + error!("[tcp]failed to accept: {}", e); continue; } }; @@ -67,6 +67,8 @@ mod udp; #[cfg(feature = "udp")] pub async fn run_udp(endpoint: EndpointRef) { + use tokio::net::UdpSocket; + let Endpoint { listen, remote, @@ -74,9 +76,22 @@ pub async fn run_udp(endpoint: EndpointRef) { .. } = endpoint.as_ref(); - if let Err(e) = udp::associate_and_relay(listen, remote, opts.into()).await - { - panic!("udp forward exit: {}", &e); + let sock_map = udp::new_sock_map(); + let listen_sock = UdpSocket::bind(listen) + .await + .unwrap_or_else(|e| panic!("[udp]unable to bind {}: {}", &listen, e)); + + loop { + if let Err(e) = udp::associate_and_relay( + &sock_map, + &listen_sock, + remote, + opts.into(), + ) + .await + { + error!("[udp]error: {}", e); + } } } diff --git a/src/relay/udp.rs b/src/relay/udp.rs index c52abb06..b4e74897 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -1,103 +1,81 @@ use std::io::Result; use std::net::SocketAddr; -use std::sync::{Arc, RwLock}; +use std::sync::RwLock; use std::collections::HashMap; use log::{debug, info, error}; -use tokio::net::UdpSocket; - use crate::utils::DEFAULT_BUF_SIZE; + use crate::utils::RemoteAddr; use crate::utils::ConnectOptsRef; + use crate::utils::timeoutfut; use crate::utils::socket; // client <--> allocated socket -type SockMap = Arc>>>; +use tokio::net::UdpSocket; +use crate::utils::UdpSocketRef; +use crate::utils::{SockMap, SockMapRef}; + const BUF_SIZE: usize = DEFAULT_BUF_SIZE; +pub fn new_sock_map() -> SockMap { + RwLock::new(HashMap::new()) +} + pub async fn associate_and_relay( - listen: &SocketAddr, - remote: &RemoteAddr, + sock_map: &SockMap, + listen_sock: &UdpSocket, + remote_addr: &RemoteAddr, conn_opts: ConnectOptsRef, ) -> Result<()> { let timeout = conn_opts.udp_timeout; - let sock_map: SockMap = Arc::new(RwLock::new(HashMap::new())); - let listen_sock = Arc::new(UdpSocket::bind(&listen).await?); - let mut buf = vec![0u8; BUF_SIZE]; loop { - let (n, client_addr) = match listen_sock.recv_from(&mut buf).await { - Ok(x) => x, - Err(e) => { - error!("[udp]failed to recvfrom client: {}", e); - continue; - } - }; + let (n, client_addr) = listen_sock.recv_from(&mut buf).await?; debug!("[udp]recvfrom client {}", &client_addr); - let remote_addr = match remote.to_sockaddr().await { - Ok(x) => { - debug!("[udp]remote resolved as {}", &x); - x - } - Err(e) => { - error!("[udp]failed to resolve remote: {}", e); - continue; - } - }; + let remote_addr = remote_addr.to_sockaddr().await?; - // the old/new socket associated with a unique client - let alloc_sock = match get_socket(&sock_map, &client_addr) { - Some(x) => x, - None => { - info!( - "[udp]new association {} => {}", - &client_addr, &remote_addr - ); - - let socket = match socket::new_socket( - socket::Type::DGRAM, - &remote_addr, - &conn_opts, - ) { - Ok(x) => x, - Err(e) => { - error!("[udp]failed to open new socket: {}", e); - continue; - } - }; - // from_std panics only when tokio runtime not setup - let new_sock = - Arc::new(UdpSocket::from_std(socket.into()).unwrap()); - - alloc_new_socket( - &sock_map, - client_addr, - &new_sock, - &listen_sock, - timeout, - ); - new_sock - } - }; + // get the socket associated with a unique client + let alloc_sock = find_socket(sock_map, &client_addr).unwrap_or({ + info!("[udp]{} => {}", &client_addr, &remote_addr); - if let Err(e) = alloc_sock.send_to(&buf[..n], &remote_addr).await { - error!("[udp]failed to sendto remote {}: {}", &remote_addr, e); - } - } + let socket = socket::new_socket( + socket::Type::DGRAM, + &remote_addr, + &conn_opts, + )?; + + // from_std panics only when tokio runtime not setup + let new_sock = UdpSocket::from_std(socket.into()).unwrap(); + + let new_sock_ref: UdpSocketRef = (&new_sock).into(); + + tokio::spawn(send_back( + sock_map.into(), + client_addr, + listen_sock.into(), + new_sock, + timeout, + )); - // Err(Error::new(ErrorKind::Other, "unknown error")) + insert_socket(sock_map, client_addr, new_sock_ref); + new_sock_ref + }); + + alloc_sock.send_to(&buf[..n], &remote_addr).await?; + } } async fn send_back( - sock_map: SockMap, + sock_map: SockMapRef, client_addr: SocketAddr, - listen_sock: Arc, - alloc_sock: Arc, + listen_sock: UdpSocketRef, + alloc_sock: UdpSocket, timeout: usize, ) { let mut buf = vec![0u8; BUF_SIZE]; @@ -129,38 +107,32 @@ async fn send_back( } sock_map.write().unwrap().remove(&client_addr); - info!("[udp]remove association for {}", &client_addr); + debug!("[udp]remove association for {}", &client_addr); } #[inline] -fn get_socket( +fn find_socket( sock_map: &SockMap, client_addr: &SocketAddr, -) -> Option> { +) -> Option { + // fetch the lock + let alloc_sock = sock_map.read().unwrap(); + alloc_sock.get(client_addr).cloned() + // drop the lock } -fn alloc_new_socket( +#[inline] +fn insert_socket( sock_map: &SockMap, client_addr: SocketAddr, - new_sock: &Arc, - listen_sock: &Arc, - timeout: usize, + new_sock: UdpSocketRef, ) { - // new send back task - tokio::spawn(send_back( - sock_map.clone(), - client_addr, - listen_sock.clone(), - new_sock.clone(), - timeout, - )); - - sock_map - .write() - .unwrap() - .insert(client_addr, new_sock.clone()); + // fetch the lock + + sock_map.write().unwrap().insert(client_addr, new_sock); + // drop the lock } diff --git a/src/utils/ref_types.rs b/src/utils/ref_types.rs index 93fa442c..bffe453a 100644 --- a/src/utils/ref_types.rs +++ b/src/utils/ref_types.rs @@ -1,5 +1,7 @@ use core::ops::Deref; +use cfg_if::cfg_if; + use super::{Endpoint, RemoteAddr, ConnectOpts}; // Safety: @@ -43,3 +45,18 @@ macro_rules! ptr_wrap { ptr_wrap!(Endpoint, EndpointRef); ptr_wrap!(RemoteAddr, RemoteAddrRef); ptr_wrap!(ConnectOpts, ConnectOptsRef); + +cfg_if! { + if #[cfg(feature = "udp")] { + use std::sync::RwLock; + use std::collections::HashMap; + use std::net::SocketAddr; + use tokio::net::UdpSocket; + + // client <--> allocated socket + pub type SockMap = RwLock>; + + ptr_wrap!(UdpSocket, UdpSocketRef); + ptr_wrap!(SockMap, SockMapRef); + } +} From 8c32ac3af90b43a5cdd305a1c4c198596e08e6f9 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 23 Feb 2022 20:59:01 +0900 Subject: [PATCH 156/169] avoid potential dangling ptr --- src/relay/udp.rs | 60 ++++++++++++++++++++++-------------------- src/utils/ref_types.rs | 4 +-- 2 files changed, 33 insertions(+), 31 deletions(-) diff --git a/src/relay/udp.rs b/src/relay/udp.rs index b4e74897..4b65a7b9 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -1,6 +1,6 @@ use std::io::Result; use std::net::SocketAddr; -use std::sync::RwLock; +use std::sync::{Arc, RwLock}; use std::collections::HashMap; use log::{debug, info, error}; @@ -41,31 +41,33 @@ pub async fn associate_and_relay( let remote_addr = remote_addr.to_sockaddr().await?; // get the socket associated with a unique client - let alloc_sock = find_socket(sock_map, &client_addr).unwrap_or({ - info!("[udp]{} => {}", &client_addr, &remote_addr); - - let socket = socket::new_socket( - socket::Type::DGRAM, - &remote_addr, - &conn_opts, - )?; - - // from_std panics only when tokio runtime not setup - let new_sock = UdpSocket::from_std(socket.into()).unwrap(); - - let new_sock_ref: UdpSocketRef = (&new_sock).into(); - - tokio::spawn(send_back( - sock_map.into(), - client_addr, - listen_sock.into(), - new_sock, - timeout, - )); - - insert_socket(sock_map, client_addr, new_sock_ref); - new_sock_ref - }); + let alloc_sock = match find_socket(sock_map, &client_addr) { + Some(x) => x, + None => { + info!("[udp]{} => {}", &client_addr, &remote_addr); + + let socket = socket::new_socket( + socket::Type::DGRAM, + &remote_addr, + &conn_opts, + )?; + + // from_std panics only when tokio runtime not setup + let new_sock = + Arc::new(UdpSocket::from_std(socket.into()).unwrap()); + + tokio::spawn(send_back( + sock_map.into(), + client_addr, + listen_sock.into(), + new_sock.clone(), + timeout, + )); + + insert_socket(sock_map, client_addr, new_sock.clone()); + new_sock + } + }; alloc_sock.send_to(&buf[..n], &remote_addr).await?; } @@ -75,7 +77,7 @@ async fn send_back( sock_map: SockMapRef, client_addr: SocketAddr, listen_sock: UdpSocketRef, - alloc_sock: UdpSocket, + alloc_sock: Arc, timeout: usize, ) { let mut buf = vec![0u8; BUF_SIZE]; @@ -114,7 +116,7 @@ async fn send_back( fn find_socket( sock_map: &SockMap, client_addr: &SocketAddr, -) -> Option { +) -> Option> { // fetch the lock let alloc_sock = sock_map.read().unwrap(); @@ -128,7 +130,7 @@ fn find_socket( fn insert_socket( sock_map: &SockMap, client_addr: SocketAddr, - new_sock: UdpSocketRef, + new_sock: Arc, ) { // fetch the lock diff --git a/src/utils/ref_types.rs b/src/utils/ref_types.rs index bffe453a..ca55cfd9 100644 --- a/src/utils/ref_types.rs +++ b/src/utils/ref_types.rs @@ -48,13 +48,13 @@ ptr_wrap!(ConnectOpts, ConnectOptsRef); cfg_if! { if #[cfg(feature = "udp")] { - use std::sync::RwLock; + use std::sync::{Arc,RwLock}; use std::collections::HashMap; use std::net::SocketAddr; use tokio::net::UdpSocket; // client <--> allocated socket - pub type SockMap = RwLock>; + pub type SockMap = RwLock>>; ptr_wrap!(UdpSocket, UdpSocketRef); ptr_wrap!(SockMap, SockMapRef); From bb851db3b52a71d20a19f50c0aa7f4f349ab3154 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 23 Feb 2022 21:46:49 +0900 Subject: [PATCH 157/169] replace macros with generics --- src/relay/mod.rs | 11 +++--- src/relay/tcp/mod.rs | 7 ++-- src/relay/udp.rs | 16 ++++----- src/utils/ref_types.rs | 76 +++++++++++++++--------------------------- 4 files changed, 43 insertions(+), 67 deletions(-) diff --git a/src/relay/mod.rs b/src/relay/mod.rs index 171194af..c21fe62f 100644 --- a/src/relay/mod.rs +++ b/src/relay/mod.rs @@ -4,8 +4,7 @@ use futures::future::join_all; mod tcp; use tcp::TcpListener; -use crate::utils::Endpoint; -use crate::utils::{EndpointRef, RemoteAddrRef, ConnectOptsRef}; +use crate::utils::{Ref, Endpoint, RemoteAddr, ConnectOpts}; pub async fn run(endpoints: Vec) { let mut workers = Vec::with_capacity(compute_workers(&endpoints)); @@ -19,7 +18,7 @@ pub async fn run(endpoints: Vec) { join_all(workers).await; } -pub async fn run_tcp(endpoint: EndpointRef) { +pub async fn run_tcp(endpoint: Ref) { let Endpoint { listen, remote, @@ -27,8 +26,8 @@ pub async fn run_tcp(endpoint: EndpointRef) { .. } = endpoint.as_ref(); - let remote: RemoteAddrRef = remote.into(); - let opts: ConnectOptsRef = opts.into(); + let remote: Ref = remote.into(); + let opts: Ref = opts.into(); let lis = TcpListener::bind(*listen) .await @@ -66,7 +65,7 @@ pub async fn run_tcp(endpoint: EndpointRef) { mod udp; #[cfg(feature = "udp")] -pub async fn run_udp(endpoint: EndpointRef) { +pub async fn run_udp(endpoint: Ref) { use tokio::net::UdpSocket; let Endpoint { diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index f4160d73..177b2c77 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -22,14 +22,13 @@ use log::debug; use tokio::net::TcpSocket; use crate::utils::socket; -use crate::utils::ConnectOpts; -use crate::utils::{RemoteAddrRef, ConnectOptsRef}; +use crate::utils::{Ref, RemoteAddr, ConnectOpts}; #[allow(unused_variables)] pub async fn connect_and_relay( mut inbound: TcpStream, - remote: RemoteAddrRef, - conn_opts: ConnectOptsRef, + remote: Ref, + conn_opts: Ref, ) -> Result<()> { let ConnectOpts { fast_open, diff --git a/src/relay/udp.rs b/src/relay/udp.rs index 4b65a7b9..7931aa30 100644 --- a/src/relay/udp.rs +++ b/src/relay/udp.rs @@ -5,18 +5,18 @@ use std::collections::HashMap; use log::{debug, info, error}; +use tokio::net::UdpSocket; + use crate::utils::DEFAULT_BUF_SIZE; -use crate::utils::RemoteAddr; -use crate::utils::ConnectOptsRef; +use crate::utils::{Ref, RemoteAddr, ConnectOpts}; use crate::utils::timeoutfut; use crate::utils::socket; // client <--> allocated socket -use tokio::net::UdpSocket; -use crate::utils::UdpSocketRef; -use crate::utils::{SockMap, SockMapRef}; + +type SockMap = RwLock>>; const BUF_SIZE: usize = DEFAULT_BUF_SIZE; @@ -28,7 +28,7 @@ pub async fn associate_and_relay( sock_map: &SockMap, listen_sock: &UdpSocket, remote_addr: &RemoteAddr, - conn_opts: ConnectOptsRef, + conn_opts: Ref, ) -> Result<()> { let timeout = conn_opts.udp_timeout; let mut buf = vec![0u8; BUF_SIZE]; @@ -74,9 +74,9 @@ pub async fn associate_and_relay( } async fn send_back( - sock_map: SockMapRef, + sock_map: Ref, client_addr: SocketAddr, - listen_sock: UdpSocketRef, + listen_sock: Ref, alloc_sock: Arc, timeout: usize, ) { diff --git a/src/utils/ref_types.rs b/src/utils/ref_types.rs index ca55cfd9..77ddd230 100644 --- a/src/utils/ref_types.rs +++ b/src/utils/ref_types.rs @@ -1,62 +1,40 @@ use core::ops::Deref; -use cfg_if::cfg_if; - -use super::{Endpoint, RemoteAddr, ConnectOpts}; - // Safety: // pointer is not null once inited(comes from an immutable ref) // pointee memory is always valid during the eventloop +#[repr(transparent)] +pub struct Ref(*const T); + +unsafe impl Send for Ref {} +unsafe impl Sync for Ref {} -macro_rules! ptr_wrap { - ($old: ident,$new: ident) => { - #[derive(Clone, Copy)] - pub struct $new { - ptr: *const $old, - } - - unsafe impl Send for $new {} - unsafe impl Sync for $new {} - - impl AsRef<$old> for $new { - #[inline] - fn as_ref(&self) -> &$old { - unsafe { &*self.ptr } - } - } - - impl Deref for $new { - type Target = $old; - - #[inline] - fn deref(&self) -> &Self::Target { - self.as_ref() - } - } - - impl From<&$old> for $new { - fn from(ptr: &$old) -> Self { - $new { ptr } - } - } - }; +impl Copy for Ref {} + +impl Clone for Ref { + fn clone(&self) -> Self { + *self + } } -ptr_wrap!(Endpoint, EndpointRef); -ptr_wrap!(RemoteAddr, RemoteAddrRef); -ptr_wrap!(ConnectOpts, ConnectOptsRef); +impl Deref for Ref { + type Target = T; -cfg_if! { - if #[cfg(feature = "udp")] { - use std::sync::{Arc,RwLock}; - use std::collections::HashMap; - use std::net::SocketAddr; - use tokio::net::UdpSocket; + #[inline] + fn deref(&self) -> &Self::Target { + unsafe { &*self.0 } + } +} - // client <--> allocated socket - pub type SockMap = RwLock>>; +impl AsRef for Ref { + #[inline] + fn as_ref(&self) -> &T { + unsafe { &*self.0 } + } +} - ptr_wrap!(UdpSocket, UdpSocketRef); - ptr_wrap!(SockMap, SockMapRef); +impl From<&T> for Ref { + fn from(x: &T) -> Self { + Ref(x as *const _) } } From 5f2b093cf129d02ccf1d6feeb5a265d74c291150 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 30 Mar 2022 20:50:05 +0900 Subject: [PATCH 158/169] add pipe cap option --- Cargo.lock | 17 ++++------------- src/cmd/flag.rs | 17 ++++++++++++----- src/cmd/mod.rs | 12 ++++++++++++ src/relay/tcp/zio.rs | 14 +++++++++++--- src/utils/consts.rs | 3 +++ src/utils/mod.rs | 4 ++++ 6 files changed, 46 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 44dfdb40..c5b8e6c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -121,9 +121,9 @@ checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "enum-as-inner" -version = "0.3.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" +checksum = "570d109b813e904becc80d8d5da38376818a143348413f7149f1340fe04754d4" dependencies = [ "heck", "proc-macro2", @@ -277,12 +277,9 @@ checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -1034,12 +1031,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - [[package]] name = "unicode-xid" version = "0.2.2" diff --git a/src/cmd/flag.rs b/src/cmd/flag.rs index 3e504cac..66f3f24c 100644 --- a/src/cmd/flag.rs +++ b/src/cmd/flag.rs @@ -52,41 +52,48 @@ pub fn add_options(app: Command) -> Command { .value_name("limit") .takes_value(true) .display_order(0), + Arg::new("pipe_page") + .short('p') + .long("page") + .help("set pipe capacity") + .value_name("number") + .takes_value(true) + .display_order(1), Arg::new("config") .short('c') .long("config") .help("use config file") .value_name("path") .takes_value(true) - .display_order(1), + .display_order(2), Arg::new("local") .short('l') .long("listen") .help("listen address") .value_name("address") .takes_value(true) - .display_order(2), + .display_order(3), Arg::new("remote") .short('r') .long("remote") .help("remote address") .value_name("address") .takes_value(true) - .display_order(3), + .display_order(4), Arg::new("through") .short('x') .long("through") .help("send through ip or address") .value_name("address") .takes_value(true) - .display_order(4), + .display_order(5), Arg::new("interface") .short('i') .long("interface") .help("bind to interface") .value_name("device") .takes_value(true) - .display_order(5), + .display_order(6), ]) } diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index 0d91905d..feca7936 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -85,6 +85,18 @@ fn handle_matches(matches: ArgMatches) -> CmdInput { } } + #[cfg(all(target_os = "linux", feature = "zero-copy"))] + { + use crate::utils::set_pipe_cap; + + if let Some(page) = matches.value_of("pipe_page") { + if let Ok(page) = page.parse::() { + set_pipe_cap(page * 0x1000); + println!("pipe capacity: {}", page * 0x1000); + } + } + } + let opts = parse_global_opts(&matches); if let Some(config) = matches.value_of("config") { diff --git a/src/relay/tcp/zio.rs b/src/relay/tcp/zio.rs index 1901356b..d2da3576 100644 --- a/src/relay/tcp/zio.rs +++ b/src/relay/tcp/zio.rs @@ -289,6 +289,7 @@ mod zero_copy { use std::os::unix::io::{RawFd, AsRawFd}; use tokio::io::Interest; use crate::utils::DEFAULT_PIPE_CAP; + use crate::utils::CUSTOM_PIPE_CAP; pub struct Pipe(RawFd, RawFd); @@ -308,10 +309,17 @@ mod zero_copy { unsafe { if libc::pipe2(pipe.as_mut_ptr() as *mut c_int, O_NONBLOCK) < 0 { - Err(Error::last_os_error()) - } else { - Ok(Pipe(pipe.assume_init()[0], pipe.assume_init()[1])) + return Err(Error::last_os_error()); } + + let [rd, wr] = pipe.assume_init(); + + // ignore errno + if CUSTOM_PIPE_CAP != DEFAULT_PIPE_CAP { + libc::fcntl(wr, libc::F_SETPIPE_SZ, CUSTOM_PIPE_CAP); + } + + Ok(Pipe(rd, wr)) } } } diff --git a/src/utils/consts.rs b/src/utils/consts.rs index 4d55a976..15e6ab6a 100644 --- a/src/utils/consts.rs +++ b/src/utils/consts.rs @@ -24,6 +24,9 @@ pub const DEFAULT_BUF_SIZE: usize = if cfg!(target_os = "espidf") { #[cfg(all(target_os = "linux", feature = "zero-copy"))] pub const DEFAULT_PIPE_CAP: usize = 16 * 4096; +#[cfg(all(target_os = "linux", feature = "zero-copy"))] +pub static mut CUSTOM_PIPE_CAP: usize = DEFAULT_PIPE_CAP; + // features macro_rules! def_feat { ($fet: ident, $name: expr) => { diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 71f5c7c2..2f76db98 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -27,6 +27,10 @@ pub fn new_sockaddr_v6() -> SocketAddr { SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0) } +pub fn set_pipe_cap(cap: usize) { + unsafe { consts::CUSTOM_PIPE_CAP = cap }; +} + #[cfg(unix)] pub fn daemonize() { use std::env::current_dir; From 50a92f59bdfbcefd8e61d00295329740fa48ec7d Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 30 Mar 2022 20:56:35 +0900 Subject: [PATCH 159/169] conditional compile --- src/utils/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 2f76db98..5cfff2ec 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -27,6 +27,7 @@ pub fn new_sockaddr_v6() -> SocketAddr { SocketAddr::new(IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), 0) } +#[cfg(all(target_os = "linux", feature = "zero-copy"))] pub fn set_pipe_cap(cap: usize) { unsafe { consts::CUSTOM_PIPE_CAP = cap }; } From 456d826fb5c9b45bee7375caabe1f04f8d47718c Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 2 Apr 2022 15:59:21 +0900 Subject: [PATCH 160/169] update workflows --- .github/workflows/cross_compile.yml | 40 +++++++++++++++++++++----- .github/workflows/release.yml | 44 +++++++++++++++++++++++++---- 2 files changed, 72 insertions(+), 12 deletions(-) diff --git a/.github/workflows/cross_compile.yml b/.github/workflows/cross_compile.yml index b25e0be7..5a2b38b3 100644 --- a/.github/workflows/cross_compile.yml +++ b/.github/workflows/cross_compile.yml @@ -13,21 +13,21 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: - - x86_64-unknown-linux-gnu - - x86_64-unknown-linux-musl - - x86_64-pc-windows-gnu include: - target: x86_64-unknown-linux-gnu output: realm - target: x86_64-unknown-linux-musl output: realm + - target: x86_64-linux-android + output: realm - target: x86_64-pc-windows-gnu output: realm.exe - target: aarch64-unknown-linux-gnu output: realm - target: aarch64-unknown-linux-musl output: realm + - target: aarch64-linux-android + output: realm steps: - uses: actions/checkout@v2 - name: install toolchain @@ -47,24 +47,51 @@ jobs: with: name: realm-${{ matrix.target }} path: target/${{ matrix.target }}/release/${{ matrix.output }} + build-windows: + runs-on: windows-latest + strategy: + matrix: + target: + - x86_64-pc-windows-msvc + steps: + - uses: actions/checkout@v2 + - name: install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + target: ${{ matrix.target }} + override: true + - name: compile + uses: actions-rs/cargo@v1 + with: + use-cross: false + command: build + args: --release --target=${{ matrix.target }} + - name: upload + uses: actions/upload-artifact@v2 + with: + name: realm-${{ matrix.target }} + path: target/${{ matrix.target }}/release/realm.exe build-apple: runs-on: macos-latest strategy: matrix: target: - x86_64-apple-darwin + - aarch64-apple-darwin + - aarch64-apple-ios steps: - uses: actions/checkout@v2 - name: install toolchain uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly target: ${{ matrix.target }} override: true - name: compile uses: actions-rs/cargo@v1 with: - use-cross: false + use-cross: true command: build args: --release --target=${{ matrix.target }} - name: upload @@ -72,4 +99,3 @@ jobs: with: name: realm-${{ matrix.target }} path: target/${{ matrix.target }}/release/realm - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index fac0661e..779fd5bc 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,21 +9,21 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - target: - - x86_64-unknown-linux-gnu - - x86_64-unknown-linux-musl - - x86_64-pc-windows-gnu include: - target: x86_64-unknown-linux-gnu output: realm - target: x86_64-unknown-linux-musl output: realm + - target: x86_64-linux-android + output: realm - target: x86_64-pc-windows-gnu output: realm.exe - target: aarch64-unknown-linux-gnu output: realm - target: aarch64-unknown-linux-musl output: realm + - target: aarch64-linux-android + output: realm steps: - uses: actions/checkout@v2 - name: install toolchain @@ -50,12 +50,46 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: files: release-${{ matrix.target }}/* + release-windows: + runs-on: windows-latest + strategy: + matrix: + target: + - x86_64-pc-windows-msvc + steps: + - uses: actions/checkout@v2 + - name: install toolchain + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + target: ${{ matrix.target }} + override: true + - name: compile + uses: actions-rs/cargo@v1 + with: + use-cross: true + command: build + args: --release --target=${{ matrix.target }} + - name: pack + run: | + mkdir -p release-${{ matrix.target }} + cd release-${{ matrix.target }} + tar -C ../target/${{ matrix.target }}/release/ -zcf realm-${{ matrix.target }}.tar.gz realm + openssl dgst -sha256 -r realm-${{ matrix.target }}.tar.gz > realm-${{ matrix.target }}.sha256 + - name: release + uses: softprops/action-gh-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + files: release-${{ matrix.target }}/* release-apple: runs-on: macos-latest strategy: matrix: target: - x86_64-apple-darwin + - aarch64-apple-darwin + - aarch64-apple-ios steps: - uses: actions/checkout@v2 - name: install toolchain @@ -67,7 +101,7 @@ jobs: - name: compile uses: actions-rs/cargo@v1 with: - use-cross: false + use-cross: true command: build args: --release --target=${{ matrix.target }} - name: pack From 03bd7e536436c0b2183b232a36e36b11b511bc05 Mon Sep 17 00:00:00 2001 From: zephyr Date: Sat, 2 Apr 2022 16:03:18 +0900 Subject: [PATCH 161/169] update readme --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index b6243f64..2d4ee861 100644 --- a/readme.md +++ b/readme.md @@ -84,6 +84,7 @@ FLAGS: OPTIONS: -n, --nofile set nofile limit + -p, --page set pipe capacity -c, --config use config file -l, --listen
listen address -r, --remote
remote address @@ -114,6 +115,7 @@ TIMEOUT OPTIONS: SUBCOMMANDS: convert convert your legacy configuration into an advanced one + ``` Start from command line arguments: From 1f66789c94d5ce687f63e99ccddafe7217221886 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 00:45:43 +0900 Subject: [PATCH 162/169] use libkaminari --- Cargo.lock | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 7 +- readme.md | 1 + 3 files changed, 343 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c5b8e6c8..dfaf5f61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,18 +39,45 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "base64" +version = "0.20.0-alpha.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "149ea5dc24cb11513350770afebba32b68e3d2e356f9221351a2a1ee89112a82" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" +dependencies = [ + "generic-array", +] + [[package]] name = "boxfnonce" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5988cb1d626264ac94100be357308f29ff7cbdd3b36bda27f450a4ee3f713426" +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + [[package]] name = "bytes" version = "1.1.0" @@ -78,7 +105,7 @@ dependencies = [ "libc", "num-integer", "num-traits", - "time", + "time 0.1.43", "winapi", ] @@ -97,6 +124,25 @@ dependencies = [ "textwrap", ] +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" +dependencies = [ + "generic-array", + "typenum", +] + [[package]] name = "daemonize" version = "0.4.1" @@ -113,6 +159,16 @@ version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" +[[package]] +name = "digest" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "doc-comment" version = "0.3.3" @@ -258,6 +314,16 @@ dependencies = [ "slab", ] +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "getrandom" version = "0.2.4" @@ -301,6 +367,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "httparse" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4" + [[package]] name = "humantime" version = "2.1.0" @@ -382,6 +454,30 @@ dependencies = [ "libc", ] +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kaminari" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df2ca3de88447dc338247c2c71c9b710c83b056dbc11fcf87b3a3fa05db455e0" +dependencies = [ + "lazy_static", + "lightws", + "rcgen", + "rustls-pemfile", + "tokio", + "tokio-rustls", + "webpki-roots", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -403,6 +499,20 @@ dependencies = [ "cc", ] +[[package]] +name = "lightws" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73223fefcf168b791b55b0d0a75605141993c3cdfed47cd65f3773ce6c4bb74f" +dependencies = [ + "base64 0.20.0-alpha.1", + "cfg-if", + "httparse", + "rand", + "sha1", + "tokio", +] + [[package]] name = "linked-hash-map" version = "0.5.4" @@ -523,6 +633,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num_threads" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0" +dependencies = [ + "libc", +] + [[package]] name = "once_cell" version = "1.9.0" @@ -563,6 +682,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "pem" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9a3b09a20e374558580a4914d3b7d89bd61b954a5a5e1dcbea98753addb1947" +dependencies = [ + "base64 0.13.0", +] + [[package]] name = "percent-encoding" version = "2.1.0" @@ -681,6 +809,18 @@ dependencies = [ "rand_core", ] +[[package]] +name = "rcgen" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7fa2d386df8533b02184941c76ae2e0d0c1d053f5d43339169d80f21275fc5e" +dependencies = [ + "pem", + "ring", + "time 0.3.9", + "yasna", +] + [[package]] name = "realm" version = "2.0.0" @@ -694,6 +834,7 @@ dependencies = [ "fern", "futures", "jemallocator", + "kaminari", "lazy_static", "libc", "log", @@ -745,6 +886,42 @@ dependencies = [ "quick-error", ] +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustls" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360" +dependencies = [ + "base64 0.13.0", +] + [[package]] name = "ryu" version = "1.0.9" @@ -757,6 +934,16 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] + [[package]] name = "serde" version = "1.0.136" @@ -788,6 +975,17 @@ dependencies = [ "serde", ] +[[package]] +name = "sha1" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "slab" version = "0.4.5" @@ -842,6 +1040,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + [[package]] name = "strsim" version = "0.10.0" @@ -904,6 +1108,16 @@ dependencies = [ "winapi", ] +[[package]] +name = "time" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd" +dependencies = [ + "libc", + "num_threads", +] + [[package]] name = "tinyvec" version = "1.5.1" @@ -946,6 +1160,17 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-rustls" +version = "0.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e" +dependencies = [ + "rustls", + "tokio", + "webpki", +] + [[package]] name = "tokio-tfo" version = "0.1.9" @@ -1016,6 +1241,12 @@ dependencies = [ "trust-dns-proto", ] +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + [[package]] name = "unicode-bidi" version = "0.3.7" @@ -1037,6 +1268,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3" +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" version = "2.2.2" @@ -1049,12 +1286,101 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + [[package]] name = "wasi" version = "0.10.2+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" +[[package]] +name = "wasm-bindgen" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[package]] +name = "web-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] + [[package]] name = "widestring" version = "0.4.3" @@ -1100,3 +1426,12 @@ checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" dependencies = [ "winapi", ] + +[[package]] +name = "yasna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346d34a236c9d3e5f3b9b74563f238f955bbd05fa0b8b4efa53c130c43982f4c" +dependencies = [ + "time 0.3.9", +] diff --git a/Cargo.toml b/Cargo.toml index ac1f2a4e..f9a7388a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ trust-dns-resolver = { version = "0.20", optional = true } pin-project = "1" lazy_static = "1" +# transport +kaminari = { version = "0.4.1", optional = true } + # tfo tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.9", optional = true } @@ -51,6 +54,7 @@ jemallocator = {version = "0.3", optional = true } [target.'cfg(unix)'.dependencies] daemonize = "0.4" + [profile.release] opt-level = 3 lto = true @@ -60,11 +64,12 @@ panic = "abort" opt-level = 0 [features] -default = ["udp", "zero-copy", "trust-dns", "multi-thread", "proxy-protocol"] +default = ["udp", "zero-copy", "trust-dns", "multi-thread", "proxy-protocol", "transport"] udp = [] tfo = ["tokio-tfo"] zero-copy = [] trust-dns = ["trust-dns-resolver"] +transport = ["kaminari"] proxy-protocol = ["haproxy"] multi-thread = ["tokio/rt-multi-thread"] jemalloc = ["jemallocator"] diff --git a/readme.md b/readme.md index 2d4ee861..c90640d6 100644 --- a/readme.md +++ b/readme.md @@ -42,6 +42,7 @@ cargo build --release - udp *(enabled by default)* - trust-dns *(enabled by default)* - zero-copy *(enabled on linux)* +- transport *(enabled by default)* - multi-thread *(enabled by default)* - tfo - mi-malloc From 7b7529a5e7e9ad3f20a779bd4c67b85386b70843 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 16:02:25 +0900 Subject: [PATCH 163/169] add basic transport support --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- src/relay/tcp/mod.rs | 47 +++++++++++++++++++++++++++++++++++--------- src/utils/types.rs | 3 +++ 4 files changed, 44 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dfaf5f61..e1243a31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -465,9 +465,9 @@ dependencies = [ [[package]] name = "kaminari" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df2ca3de88447dc338247c2c71c9b710c83b056dbc11fcf87b3a3fa05db455e0" +checksum = "876fa9809808aac741e45ab13e5777633c4f3ef24dc5b86dc12263c0b8863ec6" dependencies = [ "lazy_static", "lightws", diff --git a/Cargo.toml b/Cargo.toml index f9a7388a..599bc6c9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ pin-project = "1" lazy_static = "1" # transport -kaminari = { version = "0.4.1", optional = true } +kaminari = { version = "0.4.2", optional = true } # tfo tokio-tfo = { git = "https://github.com/zephyrchien/tokio-tfo", branch = "main", version = "0.1.9", optional = true } diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 177b2c77..0f17e403 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -36,6 +36,8 @@ pub async fn connect_and_relay( send_through, bind_interface, haproxy_opts, + #[cfg(feature = "transport")] + transport, .. } = conn_opts.as_ref(); @@ -68,19 +70,46 @@ pub async fn connect_and_relay( .await?; } - #[cfg(all(target_os = "linux", feature = "zero-copy"))] - let res = if *zero_copy { - zio::bidi_copy_pipe(&mut inbound, &mut outbound).await - } else { - zio::bidi_copy_buffer(&mut inbound, &mut outbound).await + let res = { + #[cfg(feature = "transport")] + { + use kaminari::{AsyncAccept, AsyncConnect}; + use kaminari::mix::{MixClientStream, MixServerStream}; + type Inbound = MixServerStream; + type Outbound = MixClientStream; + if let Some((ac, cc)) = transport { + let mut inbound: Inbound = ac.accept(inbound).await?; + let mut outbound: Outbound = cc.connect(outbound).await?; + tokio::io::copy_bidirectional(&mut inbound, &mut outbound) + .await + .map(|_| ()) + } else { + relay_plain(&mut inbound, &mut outbound, *zero_copy).await + } + } + #[cfg(not(feature = "transport"))] + relay_plain(&mut inbound, &mut outbound, *zero_copy).await }; - #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] - let res = zio::bidi_copy_buffer(&mut inbound, &mut outbound).await; - if let Err(e) = res { debug!("[tcp]forward error: {}, ignored", e); } - Ok(()) } + +#[inline] +async fn relay_plain( + inbound: &mut TcpStream, + outbound: &mut TcpStream, + zero_copy: bool, +) -> Result<()> { + #[cfg(all(target_os = "linux", feature = "zero-copy"))] + if zero_copy { + zio::bidi_copy_pipe(inbound, outbound).await + } else { + zio::bidi_copy_buffer(inbound, outbound).await + } + + #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] + zio::bidi_copy_buffer(&mut inbound, &mut outbound).await; +} diff --git a/src/utils/types.rs b/src/utils/types.rs index 705fcebe..c58de84b 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -3,6 +3,7 @@ use std::fmt::{Formatter, Display}; use std::net::SocketAddr; use crate::dns; +use kaminari::mix::{MixAccept, MixConnect}; #[derive(Clone)] pub enum RemoteAddr { @@ -28,6 +29,8 @@ pub struct ConnectOpts { pub haproxy_opts: HaproxyOpts, pub send_through: Option, pub bind_interface: Option, + #[cfg(feature = "transport")] + pub transport: Option<(MixAccept, MixConnect)>, } #[derive(Clone)] From effcdc423d5d7d695fa392fbea1695e271a855fe Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 17:25:13 +0900 Subject: [PATCH 164/169] add kaminari entry point from cmd/env/file --- src/cmd/flag.rs | 14 ++++++++ src/conf/endpoint.rs | 74 +++++++++++++++++++++++++++++++++++++++--- src/conf/legacy/mod.rs | 2 ++ src/conf/mod.rs | 7 ++++ src/conf/net.rs | 1 + src/utils/types.rs | 15 +++++++-- 6 files changed, 107 insertions(+), 6 deletions(-) diff --git a/src/cmd/flag.rs b/src/cmd/flag.rs index 66f3f24c..f9eec469 100644 --- a/src/cmd/flag.rs +++ b/src/cmd/flag.rs @@ -94,6 +94,20 @@ pub fn add_options(app: Command) -> Command { .value_name("device") .takes_value(true) .display_order(6), + Arg::new("listen_transport") + .short('a') + .long("listen-transport") + .help("listen transport") + .value_name("options") + .takes_value(true) + .display_order(7), + Arg::new("remote_transport") + .short('b') + .long("remote-transport") + .help("remote transport") + .value_name("options") + .takes_value(true) + .display_order(8), ]) } diff --git a/src/conf/endpoint.rs b/src/conf/endpoint.rs index cc20e3cc..a01d070c 100644 --- a/src/conf/endpoint.rs +++ b/src/conf/endpoint.rs @@ -3,6 +3,9 @@ use std::net::{IpAddr, SocketAddr, ToSocketAddrs}; use super::{NetConf, Config}; use crate::utils::{Endpoint, RemoteAddr}; +#[cfg(feature = "transport")] +use kaminari::mix::{MixAccept, MixConnect}; + #[derive(Debug, Serialize, Deserialize)] pub struct EndpointConf { pub listen: String, @@ -17,6 +20,14 @@ pub struct EndpointConf { #[serde(skip_serializing_if = "Option::is_none")] pub interface: Option, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub listen_transport: Option, + + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + pub remote_transport: Option, + #[serde(default)] #[serde(skip_serializing_if = "Config::is_empty")] pub network: NetConf, @@ -62,6 +73,47 @@ impl EndpointConf { } } } + + #[cfg(feature = "transport")] + fn build_transport(&self) -> Option<(MixAccept, MixConnect)> { + use kaminari::mix::{MixClientConf, MixServerConf}; + use kaminari::opt::get_ws_conf; + use kaminari::opt::get_tls_client_conf; + use kaminari::opt::get_tls_server_conf; + + let Self { + listen_transport, + remote_transport, + .. + } = self; + + let listen_ws = listen_transport.as_ref().and_then(|s| get_ws_conf(s)); + let listen_tls = listen_transport + .as_ref() + .and_then(|s| get_tls_server_conf(s)); + + let remote_ws = remote_transport.as_ref().and_then(|s| get_ws_conf(s)); + let remote_tls = remote_transport + .as_ref() + .and_then(|s| get_tls_client_conf(s)); + + if matches!( + (&listen_ws, &listen_tls, &remote_ws, &remote_tls), + (None, None, None, None) + ) { + None + } else { + let ac = MixAccept::new(MixServerConf { + ws: listen_ws, + tls: listen_tls, + }); + let cc = MixConnect::new(MixClientConf { + ws: remote_ws, + tls: remote_tls, + }); + Some((ac, cc)) + } + } } impl Config for EndpointConf { @@ -75,12 +127,20 @@ impl Config for EndpointConf { let local = self.build_local(); let remote = self.build_remote(); - let through = self.build_send_through(); - // iface is untested - + // build partial conn_opts from netconf let mut conn_opts = self.network.build(); - conn_opts.send_through = through; + + // build left fields of conn_opts + + conn_opts.send_through = self.build_send_through(); + + #[cfg(feature = "transport")] + { + conn_opts.transport = self.build_transport(); + } + conn_opts.bind_interface = self.interface; + Endpoint::new(local, remote, conn_opts) } @@ -97,12 +157,18 @@ impl Config for EndpointConf { let remote = matches.value_of("remote").unwrap().to_string(); let through = matches.value_of("through").map(String::from); let interface = matches.value_of("interface").map(String::from); + let listen_transport = + matches.value_of("listen_transport").map(String::from); + let remote_transport = + matches.value_of("remote_transport").map(String::from); EndpointConf { listen, remote, through, interface, + listen_transport, + remote_transport, network: Default::default(), } } diff --git a/src/conf/legacy/mod.rs b/src/conf/legacy/mod.rs index 8de621b5..9b2417bb 100644 --- a/src/conf/legacy/mod.rs +++ b/src/conf/legacy/mod.rs @@ -84,6 +84,8 @@ impl From for FullConf { remote, through: None, interface: None, + listen_transport: None, + remote_transport: None, network: Default::default(), }) .collect(); diff --git a/src/conf/mod.rs b/src/conf/mod.rs index e13bdd9d..448df61e 100644 --- a/src/conf/mod.rs +++ b/src/conf/mod.rs @@ -19,6 +19,13 @@ pub use endpoint::EndpointConf; mod legacy; pub use legacy::LegacyConf; +/// Conig Architecture +/// cmd | file => LogConf => { level, output } +/// cmd | file => DnsConf => { resolve cinfig, opts } +/// cmd | file => NetConf +/// \ +/// cmd | file => EndpointConf => { [local, remote, conn_opts] } + pub trait Config { type Output; diff --git a/src/conf/net.rs b/src/conf/net.rs index ff41d40f..438de903 100644 --- a/src/conf/net.rs +++ b/src/conf/net.rs @@ -86,6 +86,7 @@ impl Config for NetConf { // from endpoint send_through: None, bind_interface: None, + transport: None, haproxy_opts: HaproxyOpts { send_proxy, diff --git a/src/utils/types.rs b/src/utils/types.rs index c58de84b..5881e759 100644 --- a/src/utils/types.rs +++ b/src/utils/types.rs @@ -3,6 +3,8 @@ use std::fmt::{Formatter, Display}; use std::net::SocketAddr; use crate::dns; + +#[cfg(feature = "transport")] use kaminari::mix::{MixAccept, MixConnect}; #[derive(Clone)] @@ -141,9 +143,18 @@ impl Display for ConnectOpts { write!( f, - "tcp-timeout={}s, udp-timeout={}s", + "tcp-timeout={}s, udp-timeout={}s; ", self.tcp_timeout, self.udp_timeout - ) + )?; + + #[cfg(feature = "transport")] + if self.transport.is_some() { + write!(f, "transport=kaminari")?; + } else { + write!(f, "transport=none")?; + } + + Ok(()) } } From f17bf6f2c2d67030c58d405b790f581c934d9bad Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 17:33:51 +0900 Subject: [PATCH 165/169] conditional compilation --- src/conf/net.rs | 2 ++ src/relay/tcp/mod.rs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/conf/net.rs b/src/conf/net.rs index 438de903..779c66e6 100644 --- a/src/conf/net.rs +++ b/src/conf/net.rs @@ -86,6 +86,8 @@ impl Config for NetConf { // from endpoint send_through: None, bind_interface: None, + + #[cfg(feature = "transport")] transport: None, haproxy_opts: HaproxyOpts { diff --git a/src/relay/tcp/mod.rs b/src/relay/tcp/mod.rs index 0f17e403..54cf7655 100644 --- a/src/relay/tcp/mod.rs +++ b/src/relay/tcp/mod.rs @@ -88,7 +88,9 @@ pub async fn connect_and_relay( } } #[cfg(not(feature = "transport"))] - relay_plain(&mut inbound, &mut outbound, *zero_copy).await + { + relay_plain(&mut inbound, &mut outbound, *zero_copy).await + } }; if let Err(e) = res { @@ -111,5 +113,5 @@ async fn relay_plain( } #[cfg(not(all(target_os = "linux", feature = "zero-copy")))] - zio::bidi_copy_buffer(&mut inbound, &mut outbound).await; + zio::bidi_copy_buffer(inbound, outbound).await } From 215934c72a6713b440b61eff6bfb677bb16ea899 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 17:48:09 +0900 Subject: [PATCH 166/169] docs about transport --- readme.md | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/readme.md b/readme.md index c90640d6..a7063d83 100644 --- a/readme.md +++ b/readme.md @@ -15,7 +15,11 @@ Realm is a simple, high performance relay server written in rust. - Concurrency. Bidirectional concurrent traffic leads to high performance. - Low resources cost. -## Build +## Transports + +With `transport` feature, Realm is able to handle [ws/tls/wss] on both sides. This is powered by [libkaminari](https://github.com/zephyrchien/kaminari). + +## Build Guides Install rust toolchains with [rustup](https://rustup.rs/). @@ -37,7 +41,7 @@ git submodule sync && git submodule update --init --recursive cargo build --release ``` -### Build options +### Build Options - udp *(enabled by default)* - trust-dns *(enabled by default)* @@ -60,7 +64,7 @@ cargo build --release --no-default-features cargo build --release --no-default-features --features udp, tfo, zero-copy, trust-dns ``` -### Cross compile +### Cross Compile Please refer to [https://rust-lang.github.io/rustup/cross-compilation.html](https://rust-lang.github.io/rustup/cross-compilation.html). You may need to install cross-compilers or other SDKs, and specify them when building the project. @@ -84,13 +88,15 @@ FLAGS: -z, --splice force enable tcp zero copy OPTIONS: - -n, --nofile set nofile limit - -p, --page set pipe capacity - -c, --config use config file - -l, --listen
listen address - -r, --remote
remote address - -x, --through
send through ip or address - -i, --interface bind to interface + -n, --nofile set nofile limit + -p, --page set pipe capacity + -c, --config use config file + -l, --listen
listen address + -r, --remote
remote address + -x, --through
send through ip or address + -i, --interface bind to interface + -a, --listen-transport listen transport + -b, --remote-transport remote transport LOG OPTIONS: --log-level override log level @@ -116,7 +122,6 @@ TIMEOUT OPTIONS: SUBCOMMANDS: convert convert your legacy configuration into an advanced one - ``` Start from command line arguments: @@ -440,6 +445,18 @@ Supported formats: Bind to a specific interface +#### endpoint.listen_transport: string + +Require the `transport` feature + +See [Kaminari Options](https://github.com/zephyrchien/kaminari#options) + +#### endpoint.remote_transport: string + +Require the `transport` feature + +See [Kaminari Options](https://github.com/zephyrchien/kaminari#options) + #### endpoint.network The same as [network](#network), override global options. From 3c5ec32f151268eb15030fd12be273746e2dc4d2 Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 17:57:55 +0900 Subject: [PATCH 167/169] add config examples --- examples/tls.json | 14 ++++++++++++++ examples/tls.toml | 9 +++++++++ examples/ws.json | 14 ++++++++++++++ examples/ws.toml | 9 +++++++++ examples/wss.json | 14 ++++++++++++++ examples/wss.toml | 9 +++++++++ 6 files changed, 69 insertions(+) create mode 100644 examples/tls.json create mode 100644 examples/tls.toml create mode 100644 examples/ws.json create mode 100644 examples/ws.toml create mode 100644 examples/wss.json create mode 100644 examples/wss.toml diff --git a/examples/tls.json b/examples/tls.json new file mode 100644 index 00000000..d9286190 --- /dev/null +++ b/examples/tls.json @@ -0,0 +1,14 @@ +{ + "endpoints": [ + { + "listen": "127.0.0.1:10000", + "remote": "127.0.0.1:20000", + "remote_transport": "tls;sni=example.com;insecure" + }, + { + "listen": "127.0.0.1:20000", + "remote": "127.0.0.1:30000", + "listen_transport": "tls;servername=example.com" + } + ] +} diff --git a/examples/tls.toml b/examples/tls.toml new file mode 100644 index 00000000..1abfd939 --- /dev/null +++ b/examples/tls.toml @@ -0,0 +1,9 @@ +[[endpoints]] +listen = "127.0.0.1:10000" +remote = "127.0.0.1:20000" +remote_transport = "tls;sni=example.com;insecure" + +[[endpoints]] +listen = "127.0.0.1:20000" +remote = "127.0.0.1:30000" +listen_transport = "tls;servername=example.com" diff --git a/examples/ws.json b/examples/ws.json new file mode 100644 index 00000000..be1bfeda --- /dev/null +++ b/examples/ws.json @@ -0,0 +1,14 @@ +{ + "endpoints": [ + { + "listen": "127.0.0.1:10000", + "remote": "127.0.0.1:20000", + "remote_transport": "ws;host=example.com;path=/chat" + }, + { + "listen": "127.0.0.1:20000", + "remote": "127.0.0.1:30000", + "listen_transport": "ws;host=example.com;path=/chat" + } + ] +} diff --git a/examples/ws.toml b/examples/ws.toml new file mode 100644 index 00000000..bbcc1a02 --- /dev/null +++ b/examples/ws.toml @@ -0,0 +1,9 @@ +[[endpoints]] +listen = "127.0.0.1:10000" +remote = "127.0.0.1:20000" +remote_transport = "ws;host=example.com;path=/chat" + +[[endpoints]] +listen = "127.0.0.1:20000" +remote = "127.0.0.1:30000" +listen_transport = "ws;host=example.com;path=/chat" diff --git a/examples/wss.json b/examples/wss.json new file mode 100644 index 00000000..d449f7b8 --- /dev/null +++ b/examples/wss.json @@ -0,0 +1,14 @@ +{ + "endpoints": [ + { + "listen": "127.0.0.1:10000", + "remote": "127.0.0.1:20000", + "remote_transport": "ws;host=example.com;path=/chat;tls;sni=example.com;insecure" + }, + { + "listen": "127.0.0.1:20000", + "remote": "127.0.0.1:30000", + "listen_transport": "ws;host=example.com;path=/chat;tls;servername=example.com" + } + ] +} diff --git a/examples/wss.toml b/examples/wss.toml new file mode 100644 index 00000000..6467316b --- /dev/null +++ b/examples/wss.toml @@ -0,0 +1,9 @@ +[[endpoints]] +listen = "127.0.0.1:10000" +remote = "127.0.0.1:20000" +remote_transport = "ws;host=example.com;path=/chat;tls;sni=example.com;insecure" + +[[endpoints]] +listen = "127.0.0.1:20000" +remote = "127.0.0.1:30000" +listen_transport = "ws;host=example.com;path=/chat;tls;servername=example.com" From 0bc0fe912b5942e0605fb0141daa67f0699813ea Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 17:58:57 +0900 Subject: [PATCH 168/169] bump to v2.1.0 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1243a31..89a809cb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -823,7 +823,7 @@ dependencies = [ [[package]] name = "realm" -version = "2.0.0" +version = "2.1.0" dependencies = [ "bytes", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 599bc6c9..a4a2c720 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "realm" -version = "2.0.0" +version = "2.1.0" authors = ["zhboner "] edition = "2021" From 62799e673f050534f3a7e23dde0b8921a6251c0e Mon Sep 17 00:00:00 2001 From: zephyr Date: Wed, 13 Apr 2022 18:14:19 +0900 Subject: [PATCH 169/169] update workflow; use the latest compiler --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 779fd5bc..c764ecb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -61,7 +61,7 @@ jobs: - name: install toolchain uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly target: ${{ matrix.target }} override: true - name: compile @@ -95,7 +95,7 @@ jobs: - name: install toolchain uses: actions-rs/toolchain@v1 with: - toolchain: stable + toolchain: nightly target: ${{ matrix.target }} override: true - name: compile