From 4199fbde49152e7ec54d24e9f5ed7a2deee7918f Mon Sep 17 00:00:00 2001 From: TudbuT Date: Tue, 31 Jan 2023 19:57:49 +0100 Subject: [PATCH] Initital commit --- .gitignore | 1 + Cargo.lock | 110 ++++++++++++++++++++++++++++++++++ Cargo.toml | 12 ++++ README.md | 62 +++++++++++++++++++ src/client.rs | 121 +++++++++++++++++++++++++++++++++++++ src/lib.rs | 19 ++++++ src/main.rs | 23 +++++++ src/packet.rs | 10 ++++ src/server.rs | 136 ++++++++++++++++++++++++++++++++++++++++++ src/socket_adapter.rs | 87 +++++++++++++++++++++++++++ 10 files changed, 581 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 README.md create mode 100644 src/client.rs create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/packet.rs create mode 100644 src/server.rs create mode 100644 src/socket_adapter.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..28c9a3e --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,110 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "enum-ordinalize" +version = "3.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bb1df8b45ecb7ffa78dca1c17a438fb193eb083db0b1b494d2a61bcb5096a" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + +[[package]] +name = "num-bigint" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "revpfw3" +version = "0.1.0" +dependencies = [ + "enum-ordinalize", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e135f1a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "revpfw3" +repository = "https://github.com/tudbut/revpfw3" +desciption = "A tool to bypass portforwarding restrictions using some cheap VServer" +license = "MIT" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +enum-ordinalize = "3.1" diff --git a/README.md b/README.md new file mode 100644 index 0000000..f22dacd --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ + Reverse-PortForward V3 +======================== + +This tool bypasses port restrictions of your router using some not-very-powerful +server (a cheap 1€ vserver will suffice.) + +--- + +### How to download it + +I will provide a windows and mac build shortly. Right now, I can only provide a linux +build. + +All builds I make can be found in the +[releases](https://github.com/tudbut/revpfw3/releases/latest). + +If my build doesn't work or your system doesn't have one, [install +Rust](https://rustup.rs) and run `cargo install revpfw3` or `cargo install --git +https://github.com/tudbut/revpfw3`. + +--- + +### How to set it up: + +1. Buy some cheap server online, it will only need + 1. Enough disk space to run a 5MB program (I recommend about .5GB free after + OS is installed) + 2. 500MB RAM or more + 3. Flexible port settings + 4. Not much CPU power +2. Download revpfw3 to it +3. Run it like this: `revpfw3 server ` (I reocmmend doing it in a + loop) +4. Download it to your destination as well (your PC, a raspi, etc) +5. Run it like this: `revpfw3 client localhost + ` +6. To restart, end BOTH processes (remote and on your local server) and restart + them. + +--- + +### Applications and special features: + +- Minecraft servers tested and functional. +- HTTP tested and functional. +- Some third-party protocols tested and functional. +- This is not an HTTP-Proxy. It will work with any TCP protocol that isn't + reliant on TCPNODELAY. +- No disconnects, even when the sockets stay open for hours. +- Fast +- Little ping increase in normal applications +- A 1ms waiting delay before sending is built in to reduce stress and increase + efficiency by waiting for further data. + +--- + +### As a rust library + +Reverse-PortForward V3 supports being used as a library. `revpfw3::client` and +`revpfw3::server` are public, so you can use those. Keep in mind they will panic +when the connection to the corresponding client/server drops. + diff --git a/src/client.rs b/src/client.rs new file mode 100644 index 0000000..a97c824 --- /dev/null +++ b/src/client.rs @@ -0,0 +1,121 @@ +use std::{ + io::Read, + io::Write, + net::{Shutdown, TcpStream}, + thread, + time::{Duration, SystemTime}, + vec, +}; + +use crate::{io_sync, PacketType, SocketAdapter}; + +pub fn client(ip: &str, port: u16, dest_ip: &str, dest_port: u16, key: &str, sleep_delay_ms: u64) { + let mut buf1 = [0u8; 1]; + let mut buf4 = [0u8; 4]; + let mut buf = [0; 1024]; + let mut tcp = TcpStream::connect((ip, port)).unwrap(); + println!("Syncing..."); + tcp.write_all(&['R' as u8, 'P' as u8, 'F' as u8, 30]) + .unwrap(); + println!("Authenticating..."); + tcp.write_all(&(key.len() as u32).to_be_bytes()).unwrap(); + tcp.write_all(key.as_bytes()).unwrap(); + + println!("Syncing..."); + tcp.read_exact(&mut buf4).unwrap(); + if buf4 != ['R' as u8, 'P' as u8, 'F' as u8, 30] { + panic!("RPF30 header expected, but not found. Make sure the server is actually running revpfw3!"); + } + tcp.write_all(&[PacketType::KeepAlive.ordinal() as u8]) + .unwrap(); + + println!("READY!"); + + tcp.set_nonblocking(true).unwrap(); + + let mut tcp = SocketAdapter::new(tcp); + let mut sockets: Vec = Vec::new(); + let mut last_keep_alive = SystemTime::now(); + loop { + let mut did_anything = false; + + if last_keep_alive.elapsed().unwrap().as_secs() >= 60 { + panic!("connection dropped. exiting."); + } + + let mut to_remove = vec![]; + for (i, socket) in sockets.iter_mut().enumerate() { + if let Ok(x) = socket.poll(&mut buf) { + if let Some(len) = x { + if len == 0 { + to_remove.push(i); + } else { + tcp.write(&[PacketType::ServerData.ordinal() as u8]) + .unwrap(); + tcp.write(&(i as u32).to_be_bytes()).unwrap(); + tcp.write(&(len as u32).to_be_bytes()).unwrap(); + tcp.write(&buf[..len]).unwrap(); + } + did_anything = true; + } + } else { + to_remove.push(i); + did_anything = true; + } + } + for i in to_remove.into_iter().rev() { + tcp.write(&[PacketType::CloseClient.ordinal() as u8]) + .unwrap(); + tcp.write(&(i as u32).to_be_bytes()).unwrap(); + let _ = sockets.remove(i).internal.shutdown(Shutdown::Both); + } + + tcp.update().unwrap(); + if io_sync(tcp.internal.read_exact(&mut buf1)) + .unwrap() + .is_none() + { + if !did_anything { + thread::sleep(Duration::from_millis(sleep_delay_ms)); + } + continue; + } + + let pt = PacketType::from_ordinal(buf1[0] as i8) + .expect("server/client version mismatch or broken TCP"); + tcp.internal.set_nonblocking(false).unwrap(); + match pt { + PacketType::NewClient => { + let tcp = TcpStream::connect((dest_ip, dest_port)).unwrap(); + tcp.set_nonblocking(true).unwrap(); + sockets.push(SocketAdapter::new(tcp)); + } + + PacketType::CloseClient => { + tcp.internal.read_exact(&mut buf4).unwrap(); + let _ = sockets + .remove(u32::from_be_bytes(buf4) as usize) + .internal + .shutdown(Shutdown::Both); + } + + PacketType::KeepAlive => { + last_keep_alive = SystemTime::now(); + tcp.write(&[PacketType::KeepAlive.ordinal() as u8]).unwrap(); + } + + PacketType::ClientData => { + tcp.internal.read_exact(&mut buf4).unwrap(); + let idx = u32::from_be_bytes(buf4) as usize; + tcp.internal.read_exact(&mut buf4).unwrap(); + let len = u32::from_be_bytes(buf4) as usize; + tcp.internal.read_exact(&mut buf[..len]).unwrap(); + + let _ = sockets[idx].write_later(&buf[..len]); + } + + PacketType::ServerData => unreachable!(), + } + tcp.internal.set_nonblocking(true).unwrap(); + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..18b3561 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,19 @@ +mod client; +mod packet; +mod server; +mod socket_adapter; + +use std::io::{Error, ErrorKind}; + +pub use client::*; +pub(crate) use packet::*; +pub use server::*; +pub(crate) use socket_adapter::*; + +pub(crate) fn io_sync(result: Result) -> Result, Error> { + match result { + Ok(x) => Ok(Some(x)), + Err(x) if x.kind() == ErrorKind::WouldBlock => Ok(None), + Err(x) => Err(x), + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..25391d4 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,23 @@ +use std::env; + +use revpfw3::{client, server}; + +fn main() { + let args: Vec<_> = env::args().skip(1).collect(); + if (6..=7).contains(&args.len()) && args[0] == "client" { + client( + &args[1], + args[2].parse().unwrap(), + &args[3], + args[4].parse().unwrap(), + &args[5], + if args.len() == 7 { args[6].parse().unwrap() } else { 1 } + ); + } + if (3..=4).contains(&args.len()) && args[0] == "server" { + server(args[1].parse().unwrap(), &args[2], if args.len() == 4 { args[3].parse().unwrap() } else { 1 }); + } + eprintln!("Usage: \n\ + \x20 revpfw3 server []\n\ + \x20 revpfw3 client []"); +} diff --git a/src/packet.rs b/src/packet.rs new file mode 100644 index 0000000..23e234f --- /dev/null +++ b/src/packet.rs @@ -0,0 +1,10 @@ +use enum_ordinalize::Ordinalize; + +#[derive(Debug, PartialEq, Eq, Ordinalize)] +pub(crate) enum PacketType { + NewClient, + CloseClient, + KeepAlive, + ClientData, + ServerData, +} diff --git a/src/server.rs b/src/server.rs new file mode 100644 index 0000000..0717e90 --- /dev/null +++ b/src/server.rs @@ -0,0 +1,136 @@ +use std::{ + io::Read, + io::Write, + net::{Shutdown, TcpListener}, + thread, + time::{Duration, SystemTime}, + vec, +}; + +use crate::{io_sync, PacketType, SocketAdapter}; + +pub fn server(port: u16, key: &str, sleep_delay_ms: u64) { + let mut buf1 = [0u8; 1]; + let mut buf4 = [0u8; 4]; + let mut buf = [0; 1024]; + let tcpl = TcpListener::bind(("0.0.0.0", port)).unwrap(); + let mut tcp = loop { + let Ok(mut tcp) = tcpl.accept() else { continue }; + let Ok(()) = tcp.0.read_exact(&mut buf4) else { + tcp.0.shutdown(Shutdown::Both).unwrap(); + continue; + }; + if buf4 == ['R' as u8, 'P' as u8, 'F' as u8, 30] { + println!("Compatible client connected."); + if tcp.0.read_exact(&mut buf4).is_ok() && u32::from_be_bytes(buf4) == key.len() as u32 { + println!("Key length matches."); + let mut keybuf = vec![0u8; key.len()]; + if tcp.0.read_exact(&mut keybuf).is_ok() && keybuf == key.as_bytes() { + println!("Accepted."); + break tcp.0; + } + println!("Key content does not match."); + } + println!("Key mismatch - forgetting client."); + } + }; + + tcp.write_all(&mut ['R' as u8, 'P' as u8, 'F' as u8, 30]) + .unwrap(); + + tcp.set_nonblocking(true).unwrap(); + tcpl.set_nonblocking(true).unwrap(); + + let mut tcp = SocketAdapter::new(tcp); + let mut sockets: Vec = Vec::new(); + let mut last_keep_alive_sent = SystemTime::now(); + let mut last_keep_alive = SystemTime::now(); + loop { + let mut did_anything = false; + + if last_keep_alive_sent.elapsed().unwrap().as_secs() >= 20 { + last_keep_alive_sent = SystemTime::now(); + tcp.write(&[PacketType::KeepAlive.ordinal() as u8]).unwrap(); + } + if last_keep_alive.elapsed().unwrap().as_secs() >= 60 { + panic!("connection dropped. exiting."); + } + + if let Ok(new) = tcpl.accept() { + new.0.set_nonblocking(true).unwrap(); + sockets.push(SocketAdapter::new(new.0)); + tcp.write(&[PacketType::NewClient.ordinal() as u8]).unwrap(); + did_anything = true; + } + + let mut to_remove = vec![]; + for (i, socket) in sockets.iter_mut().enumerate() { + if let Ok(x) = socket.poll(&mut buf) { + if let Some(len) = x { + if len == 0 { + to_remove.push(i); + } else { + tcp.write(&[PacketType::ClientData.ordinal() as u8]) + .unwrap(); + tcp.write(&(i as u32).to_be_bytes()).unwrap(); + tcp.write(&(len as u32).to_be_bytes()).unwrap(); + tcp.write(&buf[..len]).unwrap(); + } + did_anything = true; + } + } else { + to_remove.push(i); + did_anything = true; + } + } + for i in to_remove.into_iter().rev() { + tcp.write(&[PacketType::CloseClient.ordinal() as u8]) + .unwrap(); + tcp.write(&(i as u32).to_be_bytes()).unwrap(); + let _ = sockets.remove(i).internal.shutdown(Shutdown::Both); + } + + tcp.update().unwrap(); + if io_sync(tcp.internal.read_exact(&mut buf1)) + .unwrap() + .is_none() + { + if !did_anything { + thread::sleep(Duration::from_millis(sleep_delay_ms)); + } + continue; + } + + let pt = PacketType::from_ordinal(buf1[0] as i8) + .expect("server/client version mismatch or broken TCP"); + tcp.internal.set_nonblocking(false).unwrap(); + match pt { + PacketType::NewClient => unreachable!(), + + PacketType::CloseClient => { + tcp.internal.read_exact(&mut buf4).unwrap(); + let _ = sockets + .remove(u32::from_be_bytes(buf4) as usize) + .internal + .shutdown(Shutdown::Both); + } + + PacketType::KeepAlive => { + last_keep_alive = SystemTime::now(); + } + + PacketType::ClientData => unreachable!(), + + PacketType::ServerData => { + tcp.internal.read_exact(&mut buf4).unwrap(); + let idx = u32::from_be_bytes(buf4) as usize; + tcp.internal.read_exact(&mut buf4).unwrap(); + let len = u32::from_be_bytes(buf4) as usize; + tcp.internal.read_exact(&mut buf[..len]).unwrap(); + + let _ = sockets[idx].write_later(&buf[..len]); + } + } + tcp.internal.set_nonblocking(true).unwrap(); + } +} diff --git a/src/socket_adapter.rs b/src/socket_adapter.rs new file mode 100644 index 0000000..af3687a --- /dev/null +++ b/src/socket_adapter.rs @@ -0,0 +1,87 @@ +use std::{ + io::{Error, Read}, + io::{ErrorKind, Write}, + net::TcpStream, +}; + +use crate::io_sync; + +pub(crate) struct SocketAdapter { + pub(crate) internal: TcpStream, + written: usize, + to_write: usize, + write: [u8; 4096], + broken: Option, +} + +impl SocketAdapter { + pub fn new(tcp: TcpStream) -> SocketAdapter { + Self { + internal: tcp, + written: 0, + to_write: 0, + write: [0u8; 4096], + broken: None, + } + } + + pub fn write_later(&mut self, buf: &[u8]) -> Result<(), Error> { + if let Some(ref x) = self.broken { + return Err(Error::from_raw_os_error(*x)); + } + let lidx = self.to_write + self.written + buf.len(); + if lidx > self.write.len() && lidx - self.to_write < self.write.len() { + self.write + .copy_within(self.written..self.written + self.to_write, 0); + self.written = 0; + } + let Some(x) = self.write.get_mut(self.to_write + self.written..self.to_write + self.written + buf.len()) else { + self.broken = Some(Error::from(ErrorKind::TimedOut).raw_os_error().unwrap()); + return Err(ErrorKind::TimedOut.into()); + }; + x.copy_from_slice(buf); + self.to_write += buf.len(); + Ok(()) + } + + pub fn write(&mut self, buf: &[u8]) -> Result<(), Error> { + self.write_later(buf)?; + if let Err(x) = self.update() { + self.broken = Some(x.raw_os_error().unwrap()); + Err(x) + } else { + Ok(()) + } + } + + pub fn update(&mut self) -> Result<(), Error> { + if let Some(ref x) = self.broken { + return Err(Error::from_raw_os_error(*x)); + } + if self.to_write == 0 { + return Ok(()); + } + match self + .internal + .write(&self.write[self.written..self.written + self.to_write]) + { + Ok(x) => { + self.to_write -= x; + self.written += x; + if self.to_write == 0 { + self.written = 0; + } + Ok(()) + } + Err(x) => { + self.broken = Some(x.raw_os_error().unwrap()); + Err(x) + } + } + } + + pub fn poll(&mut self, buf: &mut [u8]) -> Result, Error> { + self.update()?; + io_sync(self.internal.read(buf)) + } +}