/* This file is part of DarkFi (https://dark.fi) * * Copyright (C) 2020-2026 Dyne.org foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ use std::{io, sync::Arc, time::Duration}; use async_trait::async_trait; use futures::{ future::{select, Either}, pin_mut, }; use futures_rustls::{TlsAcceptor, TlsStream}; use smol::{ lock::OnceCell, net::{SocketAddr, TcpListener as SmolTcpListener, TcpStream}, Async, Timer, }; use socket2::{Domain, Socket, TcpKeepalive, Type}; use tracing::debug; use url::Url; use super::{PtListener, PtNegotiation, PtStream}; trait SocketExt { fn enable_reuse_port(&self) -> io::Result<()>; } impl SocketExt for Socket { fn enable_reuse_port(&self) -> io::Result<()> { #[cfg(target_family = "unix")] self.set_reuse_port(true)?; // On Windows SO_REUSEPORT means the same thing as SO_REUSEADDR #[cfg(target_family = "windows")] self.set_reuse_address(true)?; Ok(()) } } /// TCP Dialer implementation #[derive(Debug, Clone)] pub struct TcpDialer { /// TTL to set for opened sockets, or `None` for default. ttl: Option, } impl TcpDialer { /// Instantiate a new [`TcpDialer`] with optional TTL. pub(crate) async fn new(ttl: Option) -> io::Result { Ok(Self { ttl }) } /// Internal helper function to create a TCP socket. async fn create_socket(&self, socket_addr: SocketAddr) -> io::Result { let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 }; let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?; if socket_addr.is_ipv6() { socket.set_only_v6(true)?; } if let Some(ttl) = self.ttl { socket.set_ttl_v4(ttl)?; } socket.set_tcp_nodelay(true)?; let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(20)); socket.set_tcp_keepalive(&keepalive)?; socket.enable_reuse_port()?; Ok(socket) } /// Internal dial function pub(crate) async fn do_dial( &self, socket_addr: SocketAddr, timeout: Option, ) -> io::Result { debug!(target: "net::tcp::do_dial", "Dialing {socket_addr} with TCP..."); let socket = self.create_socket(socket_addr).await?; socket.set_nonblocking(true)?; // Sync start socket connect. A WouldBlock error means this // connection is in progress. match socket.connect(&socket_addr.into()) { Ok(()) => {} Err(err) if err.raw_os_error() == Some(libc::EINPROGRESS) => {} Err(err) if err.kind() == io::ErrorKind::WouldBlock => {} Err(err) => return Err(err), }; let stream = Async::new_nonblocking(std::net::TcpStream::from(socket))?; // Wait until the async object becomes writable. let connect = async move { stream.writable().await?; match stream.get_ref().take_error()? { Some(err) => Err(err), None => Ok(stream), } }; // If a timeout is configured, run both the connect and timeout // futures and return whatever finishes first. Otherwise wait on // the connect future. match timeout { Some(t) => { let timeout = Timer::after(t); pin_mut!(timeout); pin_mut!(connect); match select(connect, timeout).await { Either::Left((Ok(stream), _)) => Ok(TcpStream::from(stream)), Either::Left((Err(e), _)) => Err(e), Either::Right((_, _)) => Err(io::ErrorKind::TimedOut.into()), } } None => { let stream = connect.await?; Ok(TcpStream::from(stream)) } } } } /// TCP Listener implementation #[derive(Debug, Clone)] pub struct TcpListener { /// Size of the listen backlog for listen sockets backlog: i32, /// When the user puts a port of 0, the OS will assign a random port. /// We get it from the listener so we know what the true endpoint is. pub port: Arc>, } impl TcpListener { /// Instantiate a new [`TcpListener`] with given backlog size. pub async fn new(backlog: i32) -> io::Result { Ok(Self { backlog, port: Arc::new(OnceCell::new()) }) } /// Internal helper function to create a TCP socket. async fn create_socket(&self, socket_addr: SocketAddr) -> io::Result { let domain = if socket_addr.is_ipv4() { Domain::IPV4 } else { Domain::IPV6 }; let socket = Socket::new(domain, Type::STREAM, Some(socket2::Protocol::TCP))?; if socket_addr.is_ipv6() { socket.set_only_v6(true)?; } socket.set_tcp_nodelay(true)?; let keepalive = TcpKeepalive::new().with_time(Duration::from_secs(20)); socket.set_tcp_keepalive(&keepalive)?; socket.enable_reuse_port()?; Ok(socket) } /// Internal listen function pub(crate) async fn do_listen(&self, socket_addr: SocketAddr) -> io::Result { let socket = self.create_socket(socket_addr).await?; socket.bind(&socket_addr.into())?; socket.listen(self.backlog)?; socket.set_nonblocking(true)?; let listener = std::net::TcpListener::from(socket); let local_port = listener.local_addr()?.port(); let listener = smol::Async::::try_from(listener)?; self.port.set(local_port).await.expect("fatal port already set for TcpListener"); Ok(SmolTcpListener::from(listener)) } } #[async_trait] impl PtListener for SmolTcpListener { async fn next(&self) -> io::Result { let (stream, peer_addr) = match self.accept().await { Ok((s, a)) => (s, a), Err(e) => return Err(e), }; let url = match Url::parse(&format!("tcp://{peer_addr}")) { Ok(v) => v, Err(e) => { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("Invalid peer address '{peer_addr}': {e}"), )) } }; Ok(Box::pin(async move { Ok((Box::new(stream) as Box, url)) })) } } #[async_trait] impl PtListener for (TlsAcceptor, SmolTcpListener) { async fn next(&self) -> io::Result { let (stream, peer_addr) = match self.1.accept().await { Ok((s, a)) => (s, a), Err(e) => return Err(e), }; let url = match Url::parse(&format!("tcp+tls://{peer_addr}")) { Ok(v) => v, Err(e) => { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("Invalid peer address '{peer_addr}': {e}"), )) } }; let acceptor = self.0.clone(); Ok(Box::pin(async move { let stream = acceptor.accept(stream).await?; Ok((Box::new(TlsStream::Server(stream)) as Box, url)) })) } }