Initial commit

Vibe Coded with Copilot to build this.
This commit is contained in:
2025-07-21 08:08:29 -04:00
commit 6e44faedd5
7 changed files with 675 additions and 0 deletions
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "tps"
version = "0.1.0"
+11
View File
@@ -0,0 +1,11 @@
[package]
name = "tps"
version = "0.1.0"
edition = "2024"
[dependencies]
pretty_env_logger.workspace = true
log.workspace = true
clap.workspace = true
trevors_utilities.workspace = true
colored.workspace = true
+114
View File
@@ -0,0 +1,114 @@
use clap::Parser;
use std::fs::{create_dir_all, File};
use std::io::{Read, Write};
use std::net::{TcpListener, TcpStream};
use std::path::PathBuf;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::thread;
use std::time::SystemTime;
/// Simple TCP proxy that logs traffic per client
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Args {
/// Host to listen on
#[arg(long)]
listen_host: String,
/// Port to listen on
#[arg(long)]
listen_port: u16,
/// Host to connect to
#[arg(long)]
connect_host: String,
/// Port to connect to
#[arg(long)]
connect_port: u16,
}
static CLIENT_ID: AtomicUsize = AtomicUsize::new(0);
fn main() -> std::io::Result<()> {
let args = Args::parse();
let listen_addr = format!("{}:{}", args.listen_host, args.listen_port);
let target_addr = format!("{}:{}", args.connect_host, args.connect_port);
let listener = TcpListener::bind(&listen_addr)?;
println!("Proxy listening on {}", listen_addr);
for stream in listener.incoming() {
let stream = stream?;
let target = target_addr.clone();
let client_id = CLIENT_ID.fetch_add(1, Ordering::SeqCst);
thread::spawn(move || {
if let Err(e) = handle_client(stream, &target, client_id) {
eprintln!("Error handling client {}: {}", client_id, e);
}
});
}
Ok(())
}
fn handle_client(mut client: TcpStream, target: &str, client_id: usize) -> std::io::Result<()> {
let mut server = TcpStream::connect(target)?;
let timestamp = SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap_or_else(|_| std::time::Duration::from_secs(0))
.as_secs();
let log_dir = PathBuf::from("logs");
create_dir_all(&log_dir)?;
let mut log_file = File::create(log_dir.join(format!("client_{}_{}.log", client_id, timestamp)))?;
let mut client_clone = client.try_clone()?;
let mut server_clone = server.try_clone()?;
let log_file_clone = log_file.try_clone()?;
let client_to_server = thread::spawn(move || {
proxy_data(&mut client_clone, &mut server, log_file_clone, true)
});
let server_to_client = thread::spawn(move || {
proxy_data(&mut server_clone, &mut client, log_file, false)
});
client_to_server.join().unwrap()?;
server_to_client.join().unwrap()?;
Ok(())
}
fn proxy_data(
reader: &mut TcpStream,
writer: &mut TcpStream,
mut log_file: File,
from_client: bool,
) -> std::io::Result<()> {
let mut buffer = [0u8; 1024];
loop {
let n = reader.read(&mut buffer)?;
if n == 0 {
break;
}
writer.write_all(&buffer[..n])?;
let direction = if from_client { "Client -> Server" } else { "Server -> Client" };
writeln!(log_file, "\n[{}] {} bytes", direction, n)?;
for chunk in buffer[..n].chunks(16) {
let hex: String = chunk.iter().map(|b| format!("{:02X} ", b)).collect();
let ascii: String = chunk
.iter()
.map(|b| if b.is_ascii_graphic() { *b as char } else { '.' })
.collect();
writeln!(log_file, "{:<48} {}", hex, ascii)?;
}
}
Ok(())
}