From 014c6df8f5b832b64c38723c8dee5d53876c9bc4 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 13 Mar 2023 14:48:29 +0100 Subject: [PATCH] Create Rust project and add proxy functionality --- .gitignore | 5 ++ Cargo.toml | 16 ++++++ src/lib.rs | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 32 +++++++++++ 4 files changed, 202 insertions(+) create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 088ba6b..22d3516 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ Cargo.lock # These are backup files generated by rustfmt **/*.rs.bk + + +# Added by cargo + +/target diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..bf57738 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "proxima-centauri" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.69" +axum = { version = "0.6.11", features = ["json"] } +serde = { version = "1.0.155", features = ["derive"] } +serde_json = "1.0.94" +tokio = { version = "1.26.0", features = ["full"] } +tracing = "0.1.37" +tracing-subscriber = "0.3.16" +uuid = { version = "1.3.0", features = ["v4", "serde"] } diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..09febde --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,149 @@ +use axum::{http::StatusCode, Json}; +use serde::{Deserialize, Serialize}; +use std::net::{IpAddr, SocketAddr}; +use tokio::io::{self, AsyncWriteExt}; +use tokio::net::{TcpListener, TcpStream}; +use uuid::Uuid; + +#[derive(Deserialize, Serialize, Debug)] +pub struct ProxyCommand { + command: Command, + signature: [u8; 32], +} + +#[derive(Deserialize, Serialize, Debug)] +enum Command { + New { + incoming_port: u16, + destination_port: u16, + destination_ip: IpAddr, + id: Uuid, + }, + Modify { + destionation_ip: IpAddr, + id: Uuid, + }, + Delete { + id: Uuid, + }, +} + +#[derive(Serialize)] +pub struct ProxyResponse { + message: String, +} + +pub async fn root() -> &'static str { + "Hello, World!" +} + +pub async fn process_command( + Json(payload): Json, +) -> (StatusCode, Json) { + tracing::error!("Received payload: {:?}", payload); + // TODO: verify signature + match payload.command { + Command::New { + incoming_port, + destination_port, + destination_ip, + id, + } => { + // TODO: add id to global proxy map + add_proxy( + incoming_port, + SocketAddr::new(destination_ip, destination_port), + ) + .await + .unwrap(); // TODO: error propagation?? + } + Command::Modify { + destionation_ip, + id, + } => todo!(), + Command::Delete { id } => todo!(), + } + ( + StatusCode::CREATED, + Json(ProxyResponse { + message: "Success".to_string(), + }), + ) +} + +async fn add_proxy(in_port: u16, destination: SocketAddr) -> anyhow::Result<()> { + let listener = TcpListener::bind(("127.0.0.1", in_port)).await?; + + tracing::info!("proxying port {in_port} to {destination}"); + + tokio::spawn(proxy(listener, destination)); + Ok(()) +} + +async fn proxy(listener: TcpListener, destination: SocketAddr) { + while let Ok((inbound, _)) = listener.accept().await { + let transfer = transfer(inbound, destination); + + tokio::spawn(transfer); + } +} + +async fn transfer(mut inbound: TcpStream, destination: SocketAddr) -> anyhow::Result<()> { + let mut outbound = TcpStream::connect(destination).await?; + + 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?; + wo.shutdown().await + }; + + let server_to_client = async { + io::copy(&mut ro, &mut wi).await?; + wi.shutdown().await + }; + + tokio::try_join!(client_to_server, server_to_client)?; + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::net::Ipv4Addr; + + use crate::{Command, ProxyCommand}; + use serde::{Deserialize, Serialize}; + use uuid::uuid; + + #[test] + fn serialize_proxy_command_new() { + let proxy_command = ProxyCommand { + command: Command::New { + incoming_port: 5555, + destination_port: 6666, + destination_ip: std::net::IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), + id: uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"), + }, + signature: [0u8; 32], + }; + let expected = "{\"command\":{\"New\":{\"incoming_port\":5555,\"destination_port\":6666,\"\ + destination_ip\":\"127.0.0.1\",\"id\":\"67e55044-10b1-426f-9247-bb680e5fe0c8\"}},\ + \"signature\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}"; + assert_eq!(serde_json::to_string(&proxy_command).unwrap(), expected); + } + + #[test] + fn serialize_proxy_command_delete() { + let proxy_command = ProxyCommand { + command: Command::Delete { + id: uuid!("67e55044-10b1-426f-9247-bb680e5fe0c8"), + }, + signature: [0u8; 32], + }; + let expected = "{\"command\":{\"Delete\":{\"id\":\"67e55044-10b1-426f-9247-bb680e5fe0c8\"}},\ + \"signature\":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}"; + assert_eq!(serde_json::to_string(&proxy_command).unwrap(), expected); + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..132cf2f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,32 @@ +use axum::{ + routing::{get, post}, + Router, +}; +use proxima_centauri::{process_command, root}; +use std::net::SocketAddr; +use tracing::Level; + +#[tokio::main] +async fn main() { + // initialize tracing + let subscriber = tracing_subscriber::FmtSubscriber::builder() + .with_max_level(Level::TRACE) + .finish(); + + tracing::subscriber::set_global_default(subscriber).unwrap(); + + // build our application with a route + let app = Router::new() + // `GET /` goes to `root` + .route("/", get(root)) + // `POST /command` goes to `process_command` + .route("/command", post(process_command)); + + // run our app with hyper + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + tracing::debug!("listening on {}", addr); + axum::Server::bind(&addr) + .serve(app.into_make_service()) + .await + .unwrap(); +}