I currently wrote an example on using TcpStream. In my code, I cloned TcpStream and used the original TcpStream in one thread for listening and the cloned TcpStream in the other thread for writing.
How do I implement native_tls over this implementation? As my threads use move inside the closure, I cannot use the same TlsStream inside two threads since it will be moved inside the first thread.
I also cannot use the TcpStream clone as attempting to use TlsConnector to connect more than once on the same TcpStream will create an error.
I had to use move inside the closure or else there is an error that the closure may outlive the function and suggests to use "move".
I cannot clone the TlsConnector to establish a second TlsStream as that will cause error during TLS Handshake (Observed using Wireshark that there is an extraneous "Encrypted Handshake Message" packet sent).
The two threads are supposed to run infinite loops (listening and writing) and I will terminate the connection by pressing Ctrl + C.
I wrote this based on what I understand from online sources, if I am wrong, please do point me in the right direction.
let mut tls_stream = working_connector.connect(&string_ip,stream).unwrap();
let receiving = thread::spawn(move || -> std::io::Result<()>{
loop
{
let mut buffer2 = [0; 256];
tls_stream.read(&mut buffer2);
print!("{}",std::str::from_utf8(&buffer2).unwrap());
}
Ok(())
});
let sending = thread::spawn(move || -> std::io::Result<()>{
//not implemented. TODO
Ok(())
});
sending.join().unwrap();
receiving.join().unwrap();
TcpStream::try_clone() is possible since the TCP state machine and concurrency is handled by the kernel. Since you're not using in-kernel TLS(which is not recommended in general), it's you who have responsibility to control concurrent access into the TLS state machine. And the Arc<Mutex<T>> would be the easiest way, if you do need concurrent access into it.
I tried your suggestion and just wanna be sure I got it right. Since I am spawning two threads (receive and send), I wrote the code as such
let mut tls_connector = Arc::new(Mutex::new(working_connector.connect(&string_ip,stream).unwrap()));
let tls_clone = Arc::clone(&tls_connector);
let tls_clone2 = Arc::clone(&tls_connector);
I am using one infinite loop in receiving thread for incoming data and one infinite loop in sending thread for writing data. Connection is terminated by pressing Ctrl + C.
So when I used the following code inside the threads
Receiving thread
let mut thread_send = tls_clone.lock().unwrap();
Sending thread
let mut thread_send = tls_clone2.lock().unwrap();
The receiving thread will be able to receive incoming data but I am not able to send data to the remote ncat client. The reason being, I suppose, the lock was acquired in the receiving thread and there is the infinite loop, so code execution never reaches the sending thread, which comes after the receiving thread, in terms of code execution.
Put the lock inside the loop, so it gets released after each iteration. You'll also need to ensure that each iteration completes whether it has work to do or not.
I did try putting the locks inside the loops. Since the receive thread comes before the send thread in terms of code execution, the program will wait indefinitely and I am not able to send data to ncat.
How do I ensure that each iteration completes regardless it has work? For example, the receive thread needs to listen continuously for the remote ncat server to send data.
Since the receive thread comes before the send thread in terms of code execution, the program will wait indefinitely
Right. This is why I said "You'll also need to ensure that each iteration completes whether it has work to do or not." Specifically, with TcpStream, the receiver can do this with .set_nonblocking() or .set_read_timeout(), and handling the WouldBlock and TimedOut errors appropriately. Docs here: TcpStream in std::net - Rust
I tried your suggestion but I am still encountering some problems. In my two threads (receive comes before send), I have something similar to the following