├── .github └── workflows │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md └── src └── main.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Build 13 | run: cargo build --verbose 14 | - name: Run tests 15 | run: cargo test --verbose 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "rsproxy" 3 | version = "0.1.1" 4 | dependencies = [ 5 | "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", 6 | ] 7 | 8 | [[package]] 9 | name = "getopts" 10 | version = "0.2.14" 11 | source = "registry+https://github.com/rust-lang/crates.io-index" 12 | 13 | [metadata] 14 | "checksum getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9047cfbd08a437050b363d35ef160452c5fe8ea5187ae0a624708c91581d685" 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rsproxy" 3 | version = "0.1.1" 4 | authors = ["Mahmoud Al-Qudsi "] 5 | description = "Cross-platform, multi-client TCP/UDP proxy" 6 | homepage = "https://github.com/neosmart/rsproxy" 7 | repository = "https://github.com/neosmart/rsproxy" 8 | readme = "README.md" 9 | keywords = ["proxy", "udp", "tcp", "networking"] 10 | categories = ["command-line-utilities", "network-programming"] 11 | license = "MIT" 12 | 13 | [dependencies] 14 | getopts = "0.2" 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 NeoSmart Technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rsproxy 2 | _a simple, cross-platform, multi-client TCP/UDP proxy_ 3 | 4 | `rsproxy` is a cross-platform, multi-client TCP/UDP proxy written in rust, that is designed for those "one-time" tasks where you usually end up spending more time installing a proxy server and setting up the myriad configuration files and options than you do actually using it. 5 | 6 | ## Usage 7 | 8 | `rsproxy` is a command-line application. One instance of `rsproxy` should be started for each remote endpoint you wish to proxy data to/from. All configuration is done via command-line arguments, in keeping with the spirit of this project. 9 | 10 | `rsproxy` is written as a wrapper around the standalone [`tcpproxy`](https://github.com/neosmart/tcpproxy) and [`udpproxy`](https://github.com/neosmart/udpproxy) proxy servers, and shells out to one or the other depending on the chosen protocol. If `tcpproxy` or `udpproxy` is not installed, `rsproxy` will attempt to install them automatically (via the `cargo` package manager). 11 | 12 | ``` 13 | rsproxy [-b BIND_ADDR] -l LOCAL_PORT -h REMOTE_ADDR -r REMOTE_PORT [[-t]|-u] 14 | 15 | Options: 16 | -l, --local-port LOCAL_PORT 17 | The local port to which proxy should bind to 18 | -r, --remote-port REMOTE_PORT 19 | The remote port to which UDP packets should be 20 | forwarded 21 | -h, --host REMOTE_ADDR 22 | The remote address to which packets will be forwarded 23 | -b, --bind BIND_ADDR 24 | The address on which to listen for incoming requests 25 | -d, --debug Enable debug mode 26 | -t, --tcp Run in TCP mode. Cannot be used together with --udp 27 | -u, --udp Run in UDP mode. Cannot be used together with --tcp 28 | ``` 29 | 30 | Where possible, sane defaults for arguments are provided automatically. `rsproxy` defaults to TCP mode if neither `--tcp` nor `--udp` is specified. 31 | 32 | ## Installation 33 | 34 | `rsproxy` is available via `crate`, the rust package manager. Installation is as follows: 35 | 36 | cargo install rsproxy 37 | 38 | Pre-complied binaries for select platforms may be available from the `rsproxy` homepage at https://neosmart.net/rsproxy/ 39 | 40 | ## License 41 | 42 | `rsproxy` is open source and licensed under the terms of the MIT public license. 43 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate getopts; 2 | 3 | use getopts::Options; 4 | use std::env; 5 | use std::path::PathBuf; 6 | 7 | fn print_usage(program: &str, opts: Options) { 8 | let program_path = std::path::PathBuf::from(program); 9 | let program_name = program_path.file_stem().unwrap().to_str().unwrap(); 10 | let brief = format!("Usage: {} [-b BIND_ADDR] -l LOCAL_PORT -h REMOTE_ADDR -r REMOTE_PORT", 11 | program_name); 12 | print!("{}", opts.usage(&brief)); 13 | } 14 | 15 | fn main() { 16 | let args: Vec = env::args().collect(); 17 | let program = args[0].clone(); 18 | 19 | let mut opts = Options::new(); 20 | opts.reqopt("l", 21 | "local-port", 22 | "The local port to which udpproxy should bind to", 23 | "LOCAL_PORT"); 24 | opts.reqopt("r", 25 | "remote-port", 26 | "The remote port to which UDP packets should be forwarded", 27 | "REMOTE_PORT"); 28 | opts.reqopt("h", 29 | "host", 30 | "The remote address to which packets will be forwarded", 31 | "REMOTE_ADDR"); 32 | opts.optopt("b", 33 | "bind", 34 | "The address on which to listen for incoming requests", 35 | "BIND_ADDR"); 36 | opts.optflag("d", "debug", "Enable debug mode"); 37 | opts.optflag("t", "tcp", "Run in TCP proxy mode"); 38 | opts.optflag("u", "udp", "Run in UDP proxy mode"); 39 | 40 | let matches = opts.parse(&args[1..]) 41 | .unwrap_or_else(|_| { 42 | print_usage(&program, opts); 43 | std::process::exit(-1); 44 | }); 45 | if matches.opt_present("t") && matches.opt_present("u") { 46 | println!("Cannot use both --t/--tcp and -u/--udp at the same time!"); 47 | std::process::exit(-1); 48 | } 49 | 50 | let protocol = match matches.opt_present("u") { 51 | true => Protocol::Udp, 52 | false => Protocol::Tcp, //because it's the default 53 | }; 54 | 55 | let server = match protocol { 56 | Protocol::Tcp => "tcpproxy", 57 | Protocol::Udp => "udpproxy", 58 | }; 59 | 60 | if is_executable(server).is_none() { 61 | cargo_install(server).expect(&format!("Failed to install {} package via cargo!", server)); 62 | } 63 | 64 | let mut child = std::process::Command::new(server) 65 | .arg("-l") 66 | .arg(matches.opt_str("l").unwrap()) 67 | .arg("-r") 68 | .arg(matches.opt_str("r").unwrap()) 69 | .arg("-h") 70 | .arg(matches.opt_str("h").unwrap()) 71 | .arg(match matches.opt_present("b") { 72 | true => "-b", 73 | false => "", 74 | }) 75 | .arg(match matches.opt_present("b") { 76 | true => matches.opt_str("b").unwrap(), 77 | false => "".to_owned(), 78 | }) 79 | .arg(match matches.opt_present("d") { 80 | true => "-d", 81 | false => "", 82 | }) 83 | .spawn() 84 | .expect(&format!("{} failed to run!", server)); 85 | let exit_status = child.wait().unwrap(); 86 | 87 | std::process::exit(exit_status.code().unwrap_or(-1)); 88 | } 89 | 90 | enum Protocol { 91 | Tcp, 92 | Udp, 93 | } 94 | 95 | fn cargo_install(package: &str) -> std::io::Result<()> { 96 | if is_executable("cargo").is_none() { 97 | panic!("Could not find cargo package manager in $PATH!"); 98 | } 99 | 100 | let mut child = std::process::Command::new("cargo") 101 | .arg("install") 102 | .arg(package) 103 | .spawn() 104 | .expect(&format!("Failed to install {} via cargo package manager!", package)); 105 | child.wait().unwrap(); 106 | 107 | return Ok(()); 108 | } 109 | 110 | //checks if an exe is executable, and if so, returns the full path 111 | fn is_executable(name: &str) -> Option { 112 | let env_path = env::var("PATH").expect("Could not query $PATHS"); 113 | let mut paths: Vec = 114 | env_path.split(|c| [';', ':'].iter().find(|x| **x == c).is_some()) 115 | .map(|p| PathBuf::from(p)) 116 | .collect(); 117 | paths.push(env::current_dir().unwrap()); 118 | 119 | let mut search_name = name.to_owned(); 120 | if cfg!(windows) { 121 | search_name.push_str(".exe"); 122 | }; 123 | 124 | for path in paths { 125 | let full_path = path.join(&search_name); 126 | if full_path.exists() { 127 | return Some(full_path); 128 | } 129 | } 130 | 131 | return None; 132 | } 133 | --------------------------------------------------------------------------------