I am new to Rust but well versed in Go. I have written the following program which initiates connection to the remote-addr
and starting a thread to listen to the incoming connections at local-addr
. It will forward packets received from the remote connection to all incoming connections. The program works very well but wanted a code review here.
use std::collections::HashMap;
use std::env;
use std::io::{Error, Write};
use std::sync::mpsc::{self, Sender};
use std::sync::Arc;
use std::sync::Mutex;
use std::thread;
use std::{io::Read, net::TcpListener, net::TcpStream};
fn handle_stream(stream: &mut TcpStream, tx: &Sender<Vec<u8>>) -> Result<usize, Error> {
let mut buf = [0; 1024];
let n = stream.read(&mut buf[..])?;
tx.send(buf.to_vec()).unwrap_or_else(|error| {
panic!("Sending error: {:?}", error.to_string());
});
Ok(n)
}
fn process_args(cargs: Vec<String>) -> HashMap<String, String> {
let mut args = HashMap::new();
for e in cargs {
let arg: Vec<&str> = e.as_str().split("=").collect();
match arg[0] {
"--remote-addr" => {
args.insert(String::from("remote-addr"), String::from(arg[1]));
}
"--local-addr" => {
args.insert(String::from("local-addr"), String::from(arg[1]));
}
_ => (),
}
}
args
}
fn main() -> std::io::Result<()> {
let cargs: Vec<String> = env::args().collect();
let args = process_args(cargs);
if *&args.is_empty()
|| *&args.get("remote-addr").is_none()
|| *&args.get("local-addr").is_none()
{
panic!("expected args: remote and local addr")
}
let (tx, rx) = mpsc::channel();
let i_stream: Arc<Mutex<Vec<Result<TcpStream, Error>>>> = Arc::new(Mutex::new(Vec::new()));
let mut handles = vec![];
let i_stream_c1 = Arc::clone(&i_stream);
let handle = thread::spawn(move || loop {
let received: Vec<u8> = rx.recv().unwrap();
let mut streams_to_delete: Vec<usize> = Vec::new();
let mut incoming_stream = i_stream_c1.lock().unwrap();
for (idx, s) in incoming_stream.iter().enumerate() {
let mut x = s.as_ref().unwrap();
_ = x.write(received.as_slice()).unwrap_or_else(|_| {
streams_to_delete.push(idx);
0
});
}
for idx in streams_to_delete.iter() {
incoming_stream.remove(*idx).unwrap();
}
});
handles.push(handle);
let i_stream_c2 = Arc::clone(&i_stream);
let args1 = args.clone();
let handle = thread::spawn(move || {
let listener = TcpListener::bind(args1.get("local-addr").unwrap()).unwrap();
println!(
"ready to accept connections at {:?}",
args1.get("local-addr").unwrap()
);
for stream in listener.incoming() {
println!(
"new connection from {:?}",
stream.as_ref().unwrap().peer_addr().unwrap()
);
let mut incoming_stream = i_stream_c2.lock().unwrap();
incoming_stream.push(stream);
}
});
handles.push(handle);
let mut stream = TcpStream::connect(args.get("remote-addr").unwrap())?;
println!("connecting to {:?}", args.get("remote-addr").unwrap());
loop {
let mut break_loop = false;
handle_stream(&mut stream, &tx).unwrap_or_else(|_| {
break_loop = true;
0
});
if break_loop {
break;
}
}
for handle in handles {
handle.join().unwrap();
}
Ok(())
}
Here the error control is not so considered, hence you will see lot of unwrap
s. But that was intentional as it was not PROD ready code.
To run: ./tcp_forwarder --remote-addr=0.0.0.0:2100 --local-addr=0.0.0.0:2100
clap
. \$\endgroup\$