From e72968280478fdaf889ed44f0333daa895b5a52f Mon Sep 17 00:00:00 2001 From: John Smith Date: Sun, 13 Mar 2022 11:09:48 -0400 Subject: [PATCH] fix windows --- veilid-core/src/intf/native/network/mod.rs | 4 +- .../src/intf/native/network/network_tcp.rs | 1 + .../src/intf/native/network/network_udp.rs | 1 + .../src/intf/native/network/protocol/mod.rs | 140 +------------- .../intf/native/network/protocol/sockets.rs | 181 ++++++++++++++++++ .../src/intf/native/network/protocol/tcp.rs | 5 +- .../src/intf/native/network/protocol/ws.rs | 5 +- .../intf/native/network/start_protocols.rs | 27 ++- 8 files changed, 219 insertions(+), 145 deletions(-) create mode 100644 veilid-core/src/intf/native/network/protocol/sockets.rs diff --git a/veilid-core/src/intf/native/network/mod.rs b/veilid-core/src/intf/native/network/mod.rs index 883910ee..0263f216 100644 --- a/veilid-core/src/intf/native/network/mod.rs +++ b/veilid-core/src/intf/native/network/mod.rs @@ -52,12 +52,12 @@ struct NetworkInner { wss_port: u16, interfaces: NetworkInterfaces, // udp - bound_first_udp: BTreeMap, + bound_first_udp: BTreeMap>, inbound_udp_protocol_handlers: BTreeMap, outbound_udpv4_protocol_handler: Option, outbound_udpv6_protocol_handler: Option, //tcp - bound_first_tcp: BTreeMap, + bound_first_tcp: BTreeMap>, tls_acceptor: Option, listener_states: BTreeMap>>, } diff --git a/veilid-core/src/intf/native/network/network_tcp.rs b/veilid-core/src/intf/native/network/network_tcp.rs index ff359310..82cbadd8 100644 --- a/veilid-core/src/intf/native/network/network_tcp.rs +++ b/veilid-core/src/intf/native/network/network_tcp.rs @@ -1,3 +1,4 @@ +use super::sockets::*; use super::*; use crate::intf::*; use crate::network_connection::*; diff --git a/veilid-core/src/intf/native/network/network_udp.rs b/veilid-core/src/intf/native/network/network_udp.rs index 8ae2eb48..7894138d 100644 --- a/veilid-core/src/intf/native/network/network_udp.rs +++ b/veilid-core/src/intf/native/network/network_udp.rs @@ -1,3 +1,4 @@ +use super::sockets::*; use super::*; use futures_util::stream; diff --git a/veilid-core/src/intf/native/network/protocol/mod.rs b/veilid-core/src/intf/native/network/protocol/mod.rs index 7099eb4e..861139eb 100644 --- a/veilid-core/src/intf/native/network/protocol/mod.rs +++ b/veilid-core/src/intf/native/network/protocol/mod.rs @@ -1,3 +1,4 @@ +pub mod sockets; pub mod tcp; pub mod udp; pub mod wrtc; @@ -6,7 +7,6 @@ pub mod ws; use crate::network_connection::*; use crate::xx::*; use crate::*; -use socket2::{Domain, Protocol, Socket, Type}; #[derive(Debug)] pub enum ProtocolNetworkConnection { @@ -85,141 +85,3 @@ impl ProtocolNetworkConnection { } } } - -pub fn new_unbound_shared_udp_socket(domain: Domain) -> Result { - let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP)) - .map_err(|e| format!("Couldn't create UDP socket: {}", e))?; - if domain == Domain::IPV6 { - socket - .set_only_v6(true) - .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; - } - socket - .set_reuse_address(true) - .map_err(|e| format!("Couldn't set reuse address: {}", e))?; - cfg_if! { - if #[cfg(unix)] { - socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; - } - } - Ok(socket) -} - -pub fn new_bound_shared_udp_socket(local_address: SocketAddr) -> Result { - let domain = Domain::for_address(local_address); - let socket = new_unbound_shared_udp_socket(domain)?; - let socket2_addr = socket2::SockAddr::from(local_address); - socket - .bind(&socket2_addr) - .map_err(|e| format!("failed to bind UDP socket: {}", e))?; - - log_net!("created shared udp socket on {:?}", &local_address); - - Ok(socket) -} - -pub fn new_bound_first_udp_socket(local_address: SocketAddr) -> Result { - let domain = Domain::for_address(local_address); - let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP)) - .map_err(|e| format!("Couldn't create UDP socket: {}", e))?; - if domain == Domain::IPV6 { - socket - .set_only_v6(true) - .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; - } - // Bind the socket -first- before turning on 'reuse address' this way it will - // fail if the port is already taken - let socket2_addr = socket2::SockAddr::from(local_address); - socket - .bind(&socket2_addr) - .map_err(|e| format!("failed to bind UDP socket: {}", e))?; - - // Set 'reuse address' so future binds to this port will succeed - socket - .set_reuse_address(true) - .map_err(|e| format!("Couldn't set reuse address: {}", e))?; - cfg_if! { - if #[cfg(unix)] { - socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; - } - } - log_net!("created shared udp socket on {:?}", &local_address); - - Ok(socket) -} - -pub fn new_unbound_shared_tcp_socket(domain: Domain) -> Result { - let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) - .map_err(map_to_string) - .map_err(logthru_net!("failed to create TCP socket"))?; - if let Err(e) = socket.set_linger(None) { - log_net!(error "Couldn't set TCP linger: {}", e); - } - if let Err(e) = socket.set_nodelay(true) { - log_net!(error "Couldn't set TCP nodelay: {}", e); - } - if domain == Domain::IPV6 { - socket - .set_only_v6(true) - .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; - } - socket - .set_reuse_address(true) - .map_err(|e| format!("Couldn't set reuse address: {}", e))?; - cfg_if! { - if #[cfg(unix)] { - socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; - } - } - Ok(socket) -} - -pub fn new_bound_shared_tcp_socket(local_address: SocketAddr) -> Result { - let domain = Domain::for_address(local_address); - - let socket = new_unbound_shared_tcp_socket(domain)?; - - let socket2_addr = socket2::SockAddr::from(local_address); - socket - .bind(&socket2_addr) - .map_err(|e| format!("failed to bind TCP socket: {}", e))?; - - Ok(socket) -} - -pub fn new_bound_first_tcp_socket(local_address: SocketAddr) -> Result { - let domain = Domain::for_address(local_address); - - let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) - .map_err(map_to_string) - .map_err(logthru_net!("failed to create TCP socket"))?; - if let Err(e) = socket.set_linger(None) { - log_net!(error "Couldn't set TCP linger: {}", e); - } - if let Err(e) = socket.set_nodelay(true) { - log_net!(error "Couldn't set TCP nodelay: {}", e); - } - if domain == Domain::IPV6 { - socket - .set_only_v6(true) - .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; - } - // Bind the socket -first- before turning on 'reuse address' this way it will - // fail if the port is already taken - - let socket2_addr = socket2::SockAddr::from(local_address); - socket - .bind(&socket2_addr) - .map_err(|e| format!("failed to bind TCP socket: {}", e))?; - - // Set 'reuse address' so future binds to this port will succeed - socket - .set_reuse_address(true) - .map_err(|e| format!("Couldn't set reuse address: {}", e))?; - cfg_if! { - if #[cfg(unix)] { - socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; - } - } - Ok(socket) -} diff --git a/veilid-core/src/intf/native/network/protocol/sockets.rs b/veilid-core/src/intf/native/network/protocol/sockets.rs new file mode 100644 index 00000000..0c3cd281 --- /dev/null +++ b/veilid-core/src/intf/native/network/protocol/sockets.rs @@ -0,0 +1,181 @@ +use crate::xx::*; +use crate::*; +use socket2::{Domain, Protocol, Socket, Type}; + +cfg_if! { + if #[cfg(windows)] { + use winapi::shared::ws2def::{ SOL_SOCKET, SO_EXCLUSIVEADDRUSE}; + use winapi::um::winsock2::{SOCKET_ERROR, setsockopt}; + use winapi::ctypes::c_int; + use std::os::windows::io::AsRawSocket; + + fn set_exclusiveaddruse(socket: &Socket) -> Result<(), String> { + unsafe { + let optval:c_int = 1; + if setsockopt(socket.as_raw_socket().try_into().unwrap(), SOL_SOCKET, SO_EXCLUSIVEADDRUSE, (&optval as *const c_int).cast(), + std::mem::size_of::() as c_int) == SOCKET_ERROR { + return Err("Unable to SO_EXCLUSIVEADDRUSE".to_owned()); + } + Ok(()) + } + } + } +} + +pub fn new_unbound_shared_udp_socket(domain: Domain) -> Result { + let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP)) + .map_err(|e| format!("Couldn't create UDP socket: {}", e))?; + if domain == Domain::IPV6 { + socket + .set_only_v6(true) + .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; + } + socket + .set_reuse_address(true) + .map_err(|e| format!("Couldn't set reuse address: {}", e))?; + cfg_if! { + if #[cfg(unix)] { + socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; + } + } + Ok(socket) +} + +pub fn new_bound_shared_udp_socket(local_address: SocketAddr) -> Result { + let domain = Domain::for_address(local_address); + let socket = new_unbound_shared_udp_socket(domain)?; + let socket2_addr = socket2::SockAddr::from(local_address); + socket.bind(&socket2_addr).map_err(|e| { + format!( + "failed to bind UDP socket to '{}' in domain '{:?}': {} ", + local_address, domain, e + ) + })?; + + log_net!("created shared udp socket on {:?}", &local_address); + + Ok(socket) +} + +pub fn new_bound_first_udp_socket(local_address: SocketAddr) -> Result { + let domain = Domain::for_address(local_address); + let socket = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP)) + .map_err(|e| format!("Couldn't create UDP socket: {}", e))?; + if domain == Domain::IPV6 { + socket + .set_only_v6(true) + .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; + } + // Bind the socket -first- before turning on 'reuse address' this way it will + // fail if the port is already taken + let socket2_addr = socket2::SockAddr::from(local_address); + + // On windows, do SO_EXCLUSIVEADDRUSE before the bind to ensure the port is fully available + cfg_if! { + if #[cfg(windows)] { + set_exclusiveaddruse(&socket)?; + } + } + + socket + .bind(&socket2_addr) + .map_err(|e| format!("failed to bind UDP socket: {}", e))?; + + // Set 'reuse address' so future binds to this port will succeed + // This does not work on Windows, where reuse options can not be set after the bind + cfg_if! { + if #[cfg(unix)] { + socket + .set_reuse_address(true) + .map_err(|e| format!("Couldn't set reuse address: {}", e))?; + socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; + } + } + log_net!("created shared udp socket on {:?}", &local_address); + + Ok(socket) +} + +pub fn new_unbound_shared_tcp_socket(domain: Domain) -> Result { + let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) + .map_err(map_to_string) + .map_err(logthru_net!("failed to create TCP socket"))?; + if let Err(e) = socket.set_linger(None) { + log_net!(error "Couldn't set TCP linger: {}", e); + } + if let Err(e) = socket.set_nodelay(true) { + log_net!(error "Couldn't set TCP nodelay: {}", e); + } + if domain == Domain::IPV6 { + socket + .set_only_v6(true) + .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; + } + socket + .set_reuse_address(true) + .map_err(|e| format!("Couldn't set reuse address: {}", e))?; + cfg_if! { + if #[cfg(unix)] { + socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; + } + } + Ok(socket) +} + +pub fn new_bound_shared_tcp_socket(local_address: SocketAddr) -> Result { + let domain = Domain::for_address(local_address); + + let socket = new_unbound_shared_tcp_socket(domain)?; + + let socket2_addr = socket2::SockAddr::from(local_address); + socket + .bind(&socket2_addr) + .map_err(|e| format!("failed to bind TCP socket: {}", e))?; + + Ok(socket) +} + +pub fn new_bound_first_tcp_socket(local_address: SocketAddr) -> Result { + let domain = Domain::for_address(local_address); + + let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP)) + .map_err(map_to_string) + .map_err(logthru_net!("failed to create TCP socket"))?; + if let Err(e) = socket.set_linger(None) { + log_net!(error "Couldn't set TCP linger: {}", e); + } + if let Err(e) = socket.set_nodelay(true) { + log_net!(error "Couldn't set TCP nodelay: {}", e); + } + if domain == Domain::IPV6 { + socket + .set_only_v6(true) + .map_err(|e| format!("Couldn't set IPV6_V6ONLY: {}", e))?; + } + + // On windows, do SO_EXCLUSIVEADDRUSE before the bind to ensure the port is fully available + cfg_if! { + if #[cfg(windows)] { + set_exclusiveaddruse(&socket)?; + } + } + + // Bind the socket -first- before turning on 'reuse address' this way it will + // fail if the port is already taken + let socket2_addr = socket2::SockAddr::from(local_address); + socket + .bind(&socket2_addr) + .map_err(|e| format!("failed to bind TCP socket: {}", e))?; + + // Set 'reuse address' so future binds to this port will succeed + // This does not work on Windows, where reuse options can not be set after the bind + cfg_if! { + if #[cfg(unix)] { + socket + .set_reuse_address(true) + .map_err(|e| format!("Couldn't set reuse address: {}", e))?; + socket.set_reuse_port(true).map_err(|e| format!("Couldn't set reuse port: {}", e))?; + } + } + Ok(socket) +} diff --git a/veilid-core/src/intf/native/network/protocol/tcp.rs b/veilid-core/src/intf/native/network/protocol/tcp.rs index c90e5db1..e7e3c656 100644 --- a/veilid-core/src/intf/native/network/protocol/tcp.rs +++ b/veilid-core/src/intf/native/network/protocol/tcp.rs @@ -1,3 +1,4 @@ +use super::sockets::*; use super::*; use crate::intf::*; use crate::network_manager::MAX_MESSAGE_SIZE; @@ -139,7 +140,9 @@ impl RawTcpProtocolHandler { // Make a shared socket let socket = match local_address { Some(a) => new_bound_shared_tcp_socket(a)?, - None => new_unbound_shared_tcp_socket(Domain::for_address(remote_socket_addr))?, + None => { + new_unbound_shared_tcp_socket(socket2::Domain::for_address(remote_socket_addr))? + } }; // Connect to the remote address diff --git a/veilid-core/src/intf/native/network/protocol/ws.rs b/veilid-core/src/intf/native/network/protocol/ws.rs index 14dccaf6..020b9adb 100644 --- a/veilid-core/src/intf/native/network/protocol/ws.rs +++ b/veilid-core/src/intf/native/network/protocol/ws.rs @@ -1,3 +1,4 @@ +use super::sockets::*; use super::*; use crate::intf::*; use crate::network_manager::MAX_MESSAGE_SIZE; @@ -207,7 +208,9 @@ impl WebsocketProtocolHandler { // Make a shared socket let socket = match local_address { Some(a) => new_bound_shared_tcp_socket(a)?, - None => new_unbound_shared_tcp_socket(Domain::for_address(remote_socket_addr))?, + None => { + new_unbound_shared_tcp_socket(socket2::Domain::for_address(remote_socket_addr))? + } }; // Connect to the remote address diff --git a/veilid-core/src/intf/native/network/start_protocols.rs b/veilid-core/src/intf/native/network/start_protocols.rs index e0ae60b8..71bc0232 100644 --- a/veilid-core/src/intf/native/network/start_protocols.rs +++ b/veilid-core/src/intf/native/network/start_protocols.rs @@ -1,3 +1,4 @@ +use super::sockets::*; use super::*; impl Network { @@ -26,7 +27,18 @@ impl Network { } } if let (Some(bfs4), Some(bfs6)) = (bound_first_socket_v4, bound_first_socket_v6) { - inner.bound_first_udp.insert(udp_port, (bfs4, bfs6)); + cfg_if! { + if #[cfg(windows)] { + // On windows, drop the socket. This is a race condition, but there's + // no way around it. This isn't for security anyway, it's to prevent multiple copies of the + // app from binding on the same port. + drop(bfs4); + drop(bfs6); + inner.bound_first_udp.insert(udp_port, None); + } else { + inner.bound_first_udp.insert(udp_port, Some(bfs4, bfs6)); + } + } true } else { false @@ -53,7 +65,18 @@ impl Network { } } if let (Some(bfs4), Some(bfs6)) = (bound_first_socket_v4, bound_first_socket_v6) { - inner.bound_first_tcp.insert(tcp_port, (bfs4, bfs6)); + cfg_if! { + if #[cfg(windows)] { + // On windows, drop the socket. This is a race condition, but there's + // no way around it. This isn't for security anyway, it's to prevent multiple copies of the + // app from binding on the same port. + drop(bfs4); + drop(bfs6); + inner.bound_first_tcp.insert(tcp_port, None); + } else { + inner.bound_first_tcp.insert(tcp_port, Some(bfs4, bfs6)); + } + } true } else { false