From 95a52a4b5ce4265748794af3185292727e4313d9 Mon Sep 17 00:00:00 2001 From: "Sijie.Sun" Date: Wed, 31 Jan 2024 21:43:23 +0800 Subject: [PATCH] bind socket to device on macos (#9) bind socket to device on macos --- easytier-core/src/tunnels/common.rs | 35 ++++++++++++++++++++++++- easytier-core/src/tunnels/tcp_tunnel.rs | 35 +++++++++---------------- easytier-core/src/tunnels/udp_tunnel.rs | 11 ++++++-- 3 files changed, 55 insertions(+), 26 deletions(-) diff --git a/easytier-core/src/tunnels/common.rs b/easytier-core/src/tunnels/common.rs index cabf14d..fc3ab5b 100644 --- a/easytier-core/src/tunnels/common.rs +++ b/easytier-core/src/tunnels/common.rs @@ -1,6 +1,6 @@ use std::{ collections::VecDeque, - net::IpAddr, + net::{IpAddr, SocketAddr}, sync::Arc, task::{ready, Context, Poll}, }; @@ -269,6 +269,39 @@ pub(crate) fn get_interface_name_by_ip(local_ip: &IpAddr) -> Option { None } +pub(crate) fn setup_sokcet2( + socket2_socket: &socket2::Socket, + bind_addr: &SocketAddr, +) -> Result<(), TunnelError> { + socket2_socket.set_nonblocking(true)?; + socket2_socket.set_reuse_address(true)?; + socket2_socket.bind(&socket2::SockAddr::from(*bind_addr))?; + + #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))] + socket2_socket.set_reuse_port(true)?; + + // linux/mac does not use interface of bind_addr to send packet, so we need to bind device + // win can handle this with bind correctly + #[cfg(any(target_os = "ios", target_os = "macos"))] + if let Some(dev_name) = super::common::get_interface_name_by_ip(&bind_addr.ip()) { + // use IP_BOUND_IF to bind device + unsafe { + let dev_idx = nix::libc::if_nametoindex(dev_name.as_str().as_ptr() as *const i8); + tracing::warn!(?dev_idx, ?dev_name, "bind device"); + socket2_socket.bind_device_by_index_v4(std::num::NonZeroU32::new(dev_idx))?; + tracing::warn!(?dev_idx, ?dev_name, "bind device doen"); + } + } + + #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] + if let Some(dev_name) = super::common::get_interface_name_by_ip(&bind_addr.ip()) { + tracing::trace!(dev_name = ?dev_name, "bind device"); + socket2_socket.bind_device(Some(dev_name.as_bytes()))?; + } + + Ok(()) +} + pub mod tests { use std::time::Instant; diff --git a/easytier-core/src/tunnels/tcp_tunnel.rs b/easytier-core/src/tunnels/tcp_tunnel.rs index 49ff70d..19aa703 100644 --- a/easytier-core/src/tunnels/tcp_tunnel.rs +++ b/easytier-core/src/tunnels/tcp_tunnel.rs @@ -5,6 +5,8 @@ use futures::{stream::FuturesUnordered, StreamExt}; use tokio::net::{TcpListener, TcpSocket, TcpStream}; use tokio_util::codec::{FramedRead, FramedWrite, LengthDelimitedCodec}; +use crate::tunnels::common::setup_sokcet2; + use super::{ check_scheme_and_get_socket_addr, common::FramedTunnel, Tunnel, TunnelInfo, TunnelListener, }; @@ -112,32 +114,21 @@ impl TcpTunnelConnector { return get_tunnel_with_tcp_stream(stream, self.addr.clone().into()); } - async fn connect_with_custom_bind( - &mut self, - is_ipv4: bool, - ) -> Result, super::TunnelError> { + async fn connect_with_custom_bind(&mut self) -> Result, super::TunnelError> { let mut futures = FuturesUnordered::new(); let dst_addr = check_scheme_and_get_socket_addr::(&self.addr, "tcp")?; for bind_addr in self.bind_addrs.iter() { - let socket = if is_ipv4 { - TcpSocket::new_v4()? - } else { - TcpSocket::new_v6()? - }; - socket.set_reuseaddr(true)?; + tracing::info!(bind_addr = ?bind_addr, ?dst_addr, "bind addr"); - #[cfg(all(unix, not(target_os = "solaris"), not(target_os = "illumos")))] - socket.set_reuseport(true)?; + let socket2_socket = socket2::Socket::new( + socket2::Domain::for_address(dst_addr), + socket2::Type::STREAM, + Some(socket2::Protocol::TCP), + )?; + setup_sokcet2(&socket2_socket, bind_addr)?; - socket.bind(*bind_addr)?; - // linux does not use interface of bind_addr to send packet, so we need to bind device - // mac can handle this with bind correctly - #[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))] - if let Some(dev_name) = super::common::get_interface_name_by_ip(&bind_addr.ip()) { - tracing::trace!(dev_name = ?dev_name, "bind device"); - socket.bind_device(Some(dev_name.as_bytes()))?; - } + let socket = TcpSocket::from_std_stream(socket2_socket.into()); futures.push(socket.connect(dst_addr.clone())); } @@ -156,10 +147,8 @@ impl super::TunnelConnector for TcpTunnelConnector { async fn connect(&mut self) -> Result, super::TunnelError> { if self.bind_addrs.is_empty() { self.connect_with_default_bind().await - } else if self.bind_addrs[0].is_ipv4() { - self.connect_with_custom_bind(true).await } else { - self.connect_with_custom_bind(false).await + self.connect_with_custom_bind().await } } diff --git a/easytier-core/src/tunnels/udp_tunnel.rs b/easytier-core/src/tunnels/udp_tunnel.rs index 1e96b01..a4934c9 100644 --- a/easytier-core/src/tunnels/udp_tunnel.rs +++ b/easytier-core/src/tunnels/udp_tunnel.rs @@ -20,7 +20,7 @@ use crate::{ use super::{ codec::BytesCodec, - common::{FramedTunnel, TunnelWithCustomInfo}, + common::{setup_sokcet2, FramedTunnel, TunnelWithCustomInfo}, ring_tunnel::create_ring_tunnel_pair, DatagramSink, DatagramStream, Tunnel, TunnelListener, }; @@ -269,7 +269,14 @@ impl UdpTunnelListener { impl TunnelListener for UdpTunnelListener { async fn listen(&mut self) -> Result<(), super::TunnelError> { let addr = super::check_scheme_and_get_socket_addr::(&self.addr, "udp")?; - self.socket = Some(Arc::new(UdpSocket::bind(addr).await?)); + + let socket2_socket = socket2::Socket::new( + socket2::Domain::for_address(addr), + socket2::Type::DGRAM, + Some(socket2::Protocol::UDP), + )?; + setup_sokcet2(&socket2_socket, &addr)?; + self.socket = Some(Arc::new(UdpSocket::from_std(socket2_socket.into())?)); let socket = self.socket.as_ref().unwrap().clone(); let forward_tasks = self.forward_tasks.clone();