feat: get to a normal reverse proxy

This commit is contained in:
2025-07-02 02:31:41 +02:00
parent 4fc49dd3b8
commit 3c27eb55d7
9 changed files with 829 additions and 1 deletions
+145 -1
View File
@@ -1,5 +1,149 @@
//! # Tuxcord Reverse Proxy Header Authenthication
#![feature(rwlock_downgrade, try_blocks)]
use std::{
fs::File,
io::{self, Read, Write},
net::{Shutdown, TcpListener, TcpStream},
sync::Arc,
thread,
};
use clap::Parser;
pub mod args;
pub mod config;
#[cfg(feature = "ipc")]
pub mod ipc;
use args::Args;
const DEFAULT_CONFIG: &[u8] = include_bytes!("../config.default.toml");
fn main() {
println!("Hello, world!");
let args = Args::parse();
if !args.config.exists() {
println!(
"{:?} doesn't exist, creating a default config",
&args.config
);
File::create(args.config)
.expect("failure creating the config file")
.write_all(DEFAULT_CONFIG)
.expect("failure writting the contents to the config file");
return;
}
let mut config_file = File::open(&args.config).expect("failure opening the config file");
let mut config = String::new();
config_file
.read_to_string(&mut config)
.expect("failure reading the config file");
let config: config::Schema = toml::from_str(&config).expect("invalid config file");
#[cfg(feature = "ipc")]
if let Some(args::Commands::Ipc) = args.command {
ipc::start_client(config);
return;
}
println!("config: {config:#?}");
let listener = TcpListener::bind(config.listen_at).expect("failure tcp listening");
let config_arc = Arc::new(config); // will also serve as a counter
#[cfg(feature = "ipc")]
ipc::handle_daemon(config_arc.clone());
for stream in listener.incoming() {
match stream {
Ok(mut client) => {
let config_arc = config_arc.clone();
thread::spawn(|| {
if let Err(err) = handle_client(&mut client, config_arc.as_ref()) {
eprintln!("err: invalid req head ({err:?}), closing...");
_ = client.shutdown(Shutdown::Both);
}
drop(client);
drop(config_arc);
});
}
Err(err) => eprintln!("error with an incoming listener {err:#?}"),
}
}
unreachable!("listener had to be killed unexpectedly");
}
fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), &'static str> {
let mut header_buf = [0u8; 1024 * 8];
let mut read_pos = 0usize;
let pos = loop {
let Ok(n) = client.read(&mut header_buf[read_pos..]) else {
return Err("error reading stream");
};
read_pos += n;
let pos = header_buf
.windows(b"\r\n\r\n".len())
.position(|w| w == b"\r\n\r\n");
if let Some(pos) = pos {
break pos;
};
};
let mut headers = [httparse::EMPTY_HEADER; 16];
let mut req = httparse::Request::new(&mut headers);
let Ok(httparse::Status::Complete(_)) =
httparse::Request::parse(&mut req, &header_buf[0..(pos + b"\r\n\r\n".len())])
else {
return Err("parsing request was not complete");
};
let theres_body = req
.headers
.iter()
.any(|header| header.name.to_lowercase() == "content-length")
|| req
.headers
.iter()
.any(|header| header.name.to_lowercase() == "transfer-encoding");
let Some(header) = req
.headers
.iter()
.find(|header| header.name.to_lowercase() == "host")
else {
return Err("failed to find \"host\" header");
};
let Ok(host_header) = String::from_utf8(header.value.to_vec()) else {
return Err("\"host\" header is not valid UTF-8");
};
// Now find that header and pas everything
let Ok(read_hosts) = config.hosts.read() else {
return Err("poisoned RwLock");
};
let Some(addr) = read_hosts.get(&host_header) else {
return Err("host not in hashmap");
};
let Ok(mut stream) = TcpStream::connect(addr) else {
return Err("failed to connect to the hashmap address");
};
drop(read_hosts);
let Ok(_): io::Result<()> = (try {
stream.write_all(&header_buf[0..pos])?;
// here we sent all original headers, append our own (TODO)
stream.write_all(&header_buf[pos..read_pos])?; // send our overhead
if theres_body {
io::copy(client, &mut stream)?;
}
io::copy(&mut stream, client)?;
}) else {
return Err("io error exchanging head and/or body");
};
Ok(())
}