diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs index acc9bb65f42f..aae4f58f7c5c 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs @@ -7,7 +7,6 @@ use std::{ task::{Context, Poll}, }; -use cfg_if::cfg_if; use futures::{future::poll_fn, ready}; use log::{error, trace, warn}; use shadowsocks::net::is_dual_stack_addr; @@ -148,31 +147,11 @@ impl UdpSocketRedir for UdpRedirSocket { loop { let mut read_guard = ready!(self.io.poll_read_ready(cx))?; - cfg_if! { - if #[cfg(any(target_os = "macos", target_os = "ios"))] { - use crate::local::redir::sys::bsd_pf::PF; - - let (peer_addr, n) = match self.io.get_ref().recv_from(buf) { - Err(ref e) if e.kind() == ErrorKind::WouldBlock => { - read_guard.clear_ready(); - continue; - } - Err(e) => return Err(e), - Ok(x) => x, - }; - - let bind_addr = self.local_addr()?; - let actual_addr = PF.natlook(&bind_addr, &peer_addr, Protocol::UDP)?; - - return Ok((n, peer_addr, actual_addr)); - } else if #[cfg(target_os = "freebsd")] { - match recv_dest_from(self.io.get_ref(), buf) { - Err(ref e) if e.kind() == ErrorKind::WouldBlock => { - read_guard.clear_ready(); - } - x => return Poll::Ready(x), - } + match recv_dest_from(self.io.get_ref(), buf) { + Err(ref e) if e.kind() == ErrorKind::WouldBlock => { + read_guard.clear_ready(); } + x => return Poll::Ready(x), } } } diff --git a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs index c11782b1aa7e..fd34ff6cad2b 100644 --- a/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs +++ b/crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs @@ -1,52 +1,208 @@ use std::{ - io, - net::SocketAddr, + io::{self, Error, ErrorKind}, + mem, + net::{SocketAddr, UdpSocket}, + os::unix::io::AsRawFd, task::{Context, Poll}, }; +use futures::{future::poll_fn, ready}; +use log::{error, trace, warn}; +use shadowsocks::net::is_dual_stack_addr; +use socket2::{Domain, Protocol, SockAddr, Socket, Type}; +use tokio::io::unix::AsyncFd; + use crate::{ config::RedirType, - local::redir::redir_ext::{RedirSocketOpts, UdpSocketRedir}, + local::redir::{ + redir_ext::{RedirSocketOpts, UdpSocketRedir}, + sys::{bsd_pf::PF, set_ipv6_only}, + }, }; -pub struct UdpRedirSocket; +pub struct UdpRedirSocket { + io: AsyncFd, +} impl UdpRedirSocket { /// Create a new UDP socket binded to `addr` /// /// This will allow listening to `addr` that is not in local host - pub fn listen(_ty: RedirType, _addr: SocketAddr) -> io::Result { - unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...") + pub fn listen(ty: RedirType, addr: SocketAddr) -> io::Result { + UdpRedirSocket::bind(ty, addr, false) } /// Create a new UDP socket binded to `addr` /// /// This will allow binding to `addr` that is not in local host - pub fn bind_nonlocal( - _ty: RedirType, - _addr: SocketAddr, - _redir_opts: &RedirSocketOpts, - ) -> io::Result { - unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...") + pub fn bind_nonlocal(ty: RedirType, addr: SocketAddr, _: &RedirSocketOpts) -> io::Result { + UdpRedirSocket::bind(ty, addr, true) + } + + fn bind(ty: RedirType, addr: SocketAddr, reuse_port: bool) -> io::Result { + if ty == RedirType::NotSupported { + return Err(Error::new( + ErrorKind::InvalidInput, + "not supported udp transparent proxy type", + )); + } + + let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))?; + set_socket_before_bind(&addr, &socket)?; + + socket.set_nonblocking(true)?; + socket.set_reuse_address(true)?; + if reuse_port { + if let Err(err) = socket.set_reuse_port(true) { + if let Some(errno) = err.raw_os_error() { + match errno { + libc::ENOPROTOOPT => { + trace!("failed to set SO_REUSEPORT, error: {}", err); + } + _ => { + error!("failed to set SO_REUSEPORT, error: {}", err); + return Err(err); + } + } + } else { + error!("failed to set SO_REUSEPORT, error: {}", err); + return Err(err); + } + } + } + + let sock_addr = SockAddr::from(addr); + + if is_dual_stack_addr(&addr) { + // set IP_ORIGDSTADDR before bind() + + match set_ipv6_only(&socket, false) { + Ok(..) => { + if let Err(err) = socket.bind(&sock_addr) { + warn!( + "bind() dual-stack address {} failed, error: {}, fallback to IPV6_V6ONLY=true", + addr, err + ); + + if let Err(err) = set_ipv6_only(&socket, true) { + warn!( + "set IPV6_V6ONLY=true failed, error: {}, bind() to {} directly", + err, addr + ); + } + + socket.bind(&sock_addr)?; + } + } + Err(err) => { + warn!( + "set IPV6_V6ONLY=false failed, error: {}, bind() to {} directly", + err, addr + ); + socket.bind(&sock_addr)?; + } + } + } else { + socket.bind(&sock_addr)?; + } + + let io = AsyncFd::new(socket.into())?; + Ok(UdpRedirSocket { io }) } /// Send data to the socket to the given target address - pub async fn send_to(&self, _buf: &[u8], _target: SocketAddr) -> io::Result { - unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...") + pub async fn send_to(&self, buf: &[u8], target: SocketAddr) -> io::Result { + poll_fn(|cx| self.poll_send_to(cx, buf, target)).await + } + + fn poll_send_to(&self, cx: &mut Context<'_>, buf: &[u8], target: SocketAddr) -> Poll> { + loop { + let mut write_guard = ready!(self.io.poll_write_ready(cx))?; + + match self.io.get_ref().send_to(buf, target) { + Err(ref e) if e.kind() == ErrorKind::WouldBlock => { + write_guard.clear_ready(); + } + x => return Poll::Ready(x), + } + } } /// Returns the local address that this socket is bound to. pub fn local_addr(&self) -> io::Result { - unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...") + self.io.get_ref().local_addr() } } impl UdpSocketRedir for UdpRedirSocket { fn poll_recv_dest_from( &self, - _cx: &mut Context<'_>, - _buf: &mut [u8], + cx: &mut Context<'_>, + buf: &mut [u8], ) -> Poll> { - unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...") + loop { + let mut read_guard = ready!(self.io.poll_read_ready(cx))?; + + let (n, peer_addr) = match self.io.get_ref().recv_from(buf) { + Err(ref e) if e.kind() == ErrorKind::WouldBlock => { + read_guard.clear_ready(); + continue; + } + Err(e) => return Err(e).into(), + Ok(x) => x, + }; + + let bind_addr = self.local_addr()?; + let actual_addr = PF.natlook(&bind_addr, &peer_addr, Protocol::UDP)?; + + return Ok((n, peer_addr, actual_addr)).into(); + } + } +} + +fn set_disable_ip_fragmentation(level: libc::c_int, socket: &Socket) -> io::Result<()> { + // https://www.freebsd.org/cgi/man.cgi?query=ip&sektion=4&manpath=FreeBSD+9.0-RELEASE + + // sys/netinet/in.h + const IP_DONTFRAG: libc::c_int = 67; // don't fragment packet + + // sys/netinet6/in6.h + const IPV6_DONTFRAG: libc::c_int = 62; // bool; disable IPv6 fragmentation + + let enable: libc::c_int = 1; + + let opt = match level { + libc::IPPROTO_IP => IP_DONTFRAG, + libc::IPPROTO_IPV6 => IPV6_DONTFRAG, + _ => unreachable!("level can only be IPPROTO_IP or IPPROTO_IPV6"), + }; + + unsafe { + let ret = libc::setsockopt( + socket.as_raw_fd(), + level, + opt, + &enable as *const _ as *const _, + mem::size_of_val(&enable) as libc::socklen_t, + ); + + if ret < 0 { + return Err(io::Error::last_os_error()); + } } + + Ok(()) +} + +fn set_socket_before_bind(addr: &SocketAddr, socket: &Socket) -> io::Result<()> { + // https://www.freebsd.org/cgi/man.cgi?query=ip&sektion=4&manpath=FreeBSD+9.0-RELEASE + let level = match *addr { + SocketAddr::V4(..) => libc::IPPROTO_IP, + SocketAddr::V6(..) => libc::IPPROTO_IPV6, + }; + + // 1. disable IP fragmentation + set_disable_ip_fragmentation(level, socket)?; + + Ok(()) }