diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9a9be4..f9cab7e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -18,11 +18,16 @@ jobs: - name: preparatino run: | + echo "build the program" cargo build && cp ./target/debug/tunl-relay . + echo "prepare config" + sed -i -e '/whitelist = \[/a\' -e ' "127.0.0.1",' config.toml + sed -i -e '/whitelist = \[/a\' -e ' "216.239.38.120",' config.toml + - name: test v1 header run: | - ./tunl-relay --version=v1 & + ./tunl-relay --config config.toml & PID=$! echo "tunl-relay started with PID $PID" sleep 2 @@ -34,7 +39,10 @@ jobs: - name: test v2 header run: | - ./tunl-relay --version=v2 & + echo "change config protocol version to v2" + sed -i -e 's/"v1"/"v2"/' config.toml + + ./tunl-relay --config config.toml & PID=$! echo "tunl-relay started with PID $PID" sleep 2 diff --git a/Cargo.lock b/Cargo.lock index 2b00222..c311265 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,6 +113,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "cidr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdf600c45bd958cf2945c445264471cca8b6c8e67bc87b71affd6d7e5682621" +dependencies = [ + "serde", +] + [[package]] name = "clap" version = "3.2.25" @@ -123,7 +132,7 @@ dependencies = [ "bitflags 1.3.2", "clap_derive", "clap_lex", - "indexmap", + "indexmap 1.9.3", "once_cell", "strsim", "termcolor", @@ -165,6 +174,12 @@ dependencies = [ "termcolor", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "gimli" version = "0.29.0" @@ -177,6 +192,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.4.1" @@ -211,7 +232,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", ] [[package]] @@ -455,6 +486,15 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -553,17 +593,53 @@ dependencies = [ "syn 2.0.66", ] +[[package]] +name = "toml" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f49eb2ab21d2f26bd6db7bf383edc527a7ebaee412d17af4d40fdccd442f335" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f21c7aaf97f1bd9ca9d4f9e73b0a6c74bd5afef56f2bc931943a6e1c37e04e38" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tunl-relay" version = "0.1.0" dependencies = [ "anyhow", "bincode", + "cidr", "clap", "log", "pretty_env_logger", "serde", "tokio", + "toml", ] [[package]] @@ -759,3 +835,12 @@ name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winnow" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b5e5f6c299a3c7890b876a2a587f3115162487e704907d9b6cd29473052ba1" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index 9e2a178..f10b89e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,3 +11,5 @@ bincode = "2.0.0-rc.3" anyhow = "1.0" pretty_env_logger = "0.5" log = "0.4" +cidr = { version = "0.2", features = ["serde"] } +toml = "0.8" diff --git a/README.md b/README.md index 31ba9fe..518619c 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,22 @@ ## Quick Start ```sh -$ ./tunl-relay --bind 0.0.0.0 --port 6666 --version v2 +$ ./tunl-relay --config config.toml ``` -## Usage -```sh -OPTIONS: - -b, --bind [default: 0.0.0.0] - -h, --help Print help information - -p, --port [default: 6666] - -v, --version [default: v1] [possible values: v1, v2] +## Config +```toml +version = "v1" +bind = "0.0.0.0" +port = 6666 + +whitelist = [ + "173.245.48.0/20", + "103.21.244.0/22", + "103.22.200.0/22", + "103.31.4.0/22", + ... +] ``` **protocol version**: v1 refers to [bepass-relay protocol](https://github.com/bepass-org/bepass-relay/) diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..40628a4 --- /dev/null +++ b/config.toml @@ -0,0 +1,28 @@ +version = "v1" +bind = "0.0.0.0" +port = 6666 + +whitelist = [ + "173.245.48.0/20", + "103.21.244.0/22", + "103.22.200.0/22", + "103.31.4.0/22", + "141.101.64.0/18", + "108.162.192.0/18", + "190.93.240.0/20", + "188.114.96.0/20", + "197.234.240.0/22", + "198.41.128.0/17", + "162.158.0.0/15", + "104.16.0.0/13", + "104.24.0.0/14", + "172.64.0.0/13", + "131.0.72.0/22", + "2400:cb00::/32", + "2606:4700::/32", + "2803:f800::/32", + "2405:b500::/32", + "2405:8100::/32", + "2a06:98c0::/29", + "2c0f:f248::/32" +] diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..b795df2 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,24 @@ +use crate::proto::Version; +use cidr::IpCidr; + +use std::net::IpAddr; + +use anyhow::{anyhow, Result}; +use serde::Deserialize; + +#[derive(Deserialize)] +pub struct Config { + pub bind: IpAddr, + pub port: u16, + pub version: Version, + pub whitelist: Vec, +} + +impl Config { + pub fn new(config: &str) -> Result { + match toml::from_str(config) { + Ok(c) => Ok(c), + Err(e) => Err(anyhow!("could not parse config file {}", e)), + } + } +} diff --git a/src/main.rs b/src/main.rs index 1d2178f..6530b6c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,23 +1,18 @@ +mod config; mod proto; mod proxy; -use proto::Version; +use config::Config; use proxy::Proxy; use anyhow::{anyhow, Result}; use clap::Parser; -use std::net::IpAddr; - #[derive(Debug, Parser)] #[clap(author, version)] pub struct Args { - #[clap(short, long, default_value = "0.0.0.0")] - pub bind: IpAddr, - #[clap(short, long, default_value = "6666")] - pub port: u16, - #[clap(short, long, value_enum, default_value_t)] - pub version: Version, + #[clap(short, long)] + pub config: String, } #[tokio::main] @@ -28,7 +23,12 @@ async fn main() -> Result<()> { let args = Args::parse(); - let proxy = Proxy::new(args.version, args.bind, args.port); + let config = match std::fs::read_to_string(args.config) { + Ok(c) => Config::new(&c), + _ => panic!("could not find the config file"), + }?; + + let proxy = Proxy::new(config); match proxy.run().await { Err(e) => Err(anyhow!("{e}")), _ => Ok(()), diff --git a/src/proto.rs b/src/proto.rs index c385d20..e82bc9b 100644 --- a/src/proto.rs +++ b/src/proto.rs @@ -2,10 +2,10 @@ use std::net::IpAddr; use std::str::FromStr; use bincode::{Decode, Encode}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use tokio::io::{Error, ErrorKind, Result}; -#[derive(clap::ValueEnum, Clone, Default, Debug, Decode, Encode, Serialize)] +#[derive(clap::ValueEnum, Clone, Default, Debug, Decode, Encode, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Version { #[default] diff --git a/src/proxy.rs b/src/proxy.rs index 6d2f53e..0f69e91 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -1,5 +1,7 @@ use std::net::IpAddr; +use std::sync::Arc; +use crate::config::Config; use crate::proto::*; use tokio::{ @@ -8,22 +10,18 @@ use tokio::{ }; pub struct Proxy { - bind: IpAddr, - port: u16, - version: Version, + config: Arc, } impl Proxy { - pub fn new(version: Version, bind: IpAddr, port: u16) -> Self { + pub fn new(config: Config) -> Self { Self { - bind, - port, - version, + config: Arc::new(config), } } pub async fn run(&self) -> io::Result<()> { - let addr = format!("{}:{}", self.bind, self.port); + let addr = format!("{}:{}", self.config.bind, self.config.port); let l = TcpListener::bind(&addr).await?; log::info!("Listening {}", &addr); @@ -37,9 +35,21 @@ impl Proxy { } async fn listener(&self, l: &TcpListener) -> io::Result<()> { - let (mut stream, _) = l.accept().await?; + let (mut stream, client_addr) = l.accept().await?; + + if !self + .config + .whitelist + .iter() + .any(|cidr| cidr.contains(&client_addr.ip())) + { + return Err(Error::new( + ErrorKind::Other, + "[blocked] source ip is not in the whitelist", + )); + } - let header = match &self.version { + let header = match &self.config.version { Version::V1 => { let mut buf = vec![]; @@ -84,15 +94,25 @@ impl Proxy { header.addr, header.port ); - tokio::spawn(handler(header, stream)); + tokio::spawn(handler(self.config.clone(), header, stream)); Ok(()) } } -async fn handler(header: Header, stream: TcpStream) { +async fn handler(config: Arc, header: Header, stream: TcpStream) { if let Err(e) = match header.net { - Network::Tcp => tcp_handler(stream, header.addr, header.port).await, + Network::Tcp => { + if !config + .whitelist + .iter() + .any(|cidr| cidr.contains(&header.addr)) + { + log::info!("[blocked] destination ip is not in the whitelist"); + return; + } + tcp_handler(stream, header.addr, header.port).await + } Network::Udp => udp_handler(stream, header.addr, header.port).await, } { log::error!("error {e}");