Using native_tls on TcpStream

Hi

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();

You can't split it on the TcpStream level. You have to use some sort of arc/mutex based thing to share the TlsStream object.

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.

It would seem that I will need to look into Arc/Mutex. Thanks for sharing this with me, I will take a look at that.

Hi Alice

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.

How can I rectify this?

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.

Hi cliff

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.

I don't think it is possible to both send and receive at the same time on a tls stream without using async/await.

Hi alice

Thanks, I will go read on that topic.

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: https://doc.rust-lang.org/std/net/struct.TcpStream.html#method.set_nonblocking

Hi cliff

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

Thread receive

loop
{
	//....

	get_reference.set_read_timeout(Some(wait_time)).expect("error setting read timeout");
	get_reference.set_nonblocking(true);

        //....

	print!("{}",std::str::from_utf8(&buffer2).unwrap());
}

Thread send

loop
{
	get_reference.set_write_timeout(Some(wait_time)).expect("error setting write timeout");
	get_reference.set_nonblocking(true);
        //....
}

I noticed that if I set non blocking to true in "receive" thread, I can send messages to ncat but I cannot receive messages.

If I do not set non blocking in "receive" thread, I cannot send messages to ncat but I can receive messages.

Setting the timeout to 2000 milliseconds or higher/lower does not resolve the original issue where I can receive but not send.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.