From e8d523e579a262e5901a00e6248bd315f0352afe Mon Sep 17 00:00:00 2001 From: deputinizer Date: Thu, 28 Apr 2022 12:39:10 +0200 Subject: [PATCH 1/2] feat(connect): network interface binding with bind_local_device Sometimes binding to local IP just doesn't work (for example raspberry pi wlan0). Also, when DHCP IP changes, the program would have to update local IPv4 and IPv6. Solution to that is binding to an interface name, which does not change. --- src/client/connect/http.rs | 39 +++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/src/client/connect/http.rs b/src/client/connect/http.rs index afe7b155eb..e944984a32 100644 --- a/src/client/connect/http.rs +++ b/src/client/connect/http.rs @@ -77,6 +77,7 @@ struct Config { keep_alive_timeout: Option, local_address_ipv4: Option, local_address_ipv6: Option, + local_device: Option>, nodelay: bool, reuse_address: bool, send_buffer_size: Option, @@ -117,6 +118,7 @@ impl HttpConnector { keep_alive_timeout: None, local_address_ipv4: None, local_address_ipv6: None, + local_device: None, nodelay: false, reuse_address: false, send_buffer_size: None, @@ -193,6 +195,21 @@ impl HttpConnector { cfg.local_address_ipv6 = Some(addr_ipv6); } + /// Set that all sockets are bound to the configured interface + /// + /// # Example + /// + /// ``` + /// let mut http = HttpConnector::new(); + /// http.set_local_device(Some("wlan0".into())); + /// ``` + #[inline] + pub fn set_local_device(&mut self, device: Option>) { + let cfg = self.config_mut(); + + cfg.local_device = device; + } + /// Set the connect timeout. /// /// If a domain resolves to multiple IP addresses, the timeout will be @@ -585,6 +602,17 @@ fn bind_local_address( Ok(()) } +fn bind_local_device( + socket: &socket2::Socket, + device_interface: &Option>, +) -> io::Result<()> { + // None value would unbind the device + if let Some(device) = device_interface { + socket.bind_device(Some(device.as_slice()))?; + } + Ok(()) +} + fn connect( addr: &SocketAddr, config: &Config, @@ -619,7 +647,15 @@ fn connect( &config.local_address_ipv4, &config.local_address_ipv6, ) - .map_err(ConnectError::m("tcp bind local error"))?; + .map_err(ConnectError::m("tcp bind local IP error"))?; + + + bind_local_device( + &socket, + &config.local_device, + ) + .map_err(ConnectError::m("tcp bind local device error"))?; + #[cfg(unix)] let socket = unsafe { @@ -934,6 +970,7 @@ mod tests { let cfg = Config { local_address_ipv4: None, local_address_ipv6: None, + local_device: None, connect_timeout: None, keep_alive_timeout: None, happy_eyeballs_timeout: Some(fallback_timeout), From 536b3a87aa84d0577d2223b2ad64bc22571ec2a9 Mon Sep 17 00:00:00 2001 From: deputinizer Date: Thu, 28 Apr 2022 12:46:03 +0200 Subject: [PATCH 2/2] fix(connect): connection from local IPv6 to remote IPv4 --- src/client/connect/http.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/client/connect/http.rs b/src/client/connect/http.rs index e944984a32..569cfc1b56 100644 --- a/src/client/connect/http.rs +++ b/src/client/connect/http.rs @@ -581,7 +581,11 @@ fn bind_local_address( local_addr_ipv6: &Option, ) -> io::Result<()> { match (*dst_addr, local_addr_ipv4, local_addr_ipv6) { - (SocketAddr::V4(_), Some(addr), _) => { + (SocketAddr::V4(_), Some(addr), None) => { + socket.bind(&SocketAddr::new(addr.clone().into(), 0).into())?; + } + (SocketAddr::V4(_), None, Some(addr)) => { + // Connection from IPv6 local to IPv4 remote socket.bind(&SocketAddr::new(addr.clone().into(), 0).into())?; } (SocketAddr::V6(_), _, Some(addr)) => {