Skip to content

Commit

Permalink
fix(local-redir): FreeBSD code cleanup, macOS calls pf natlook
Browse files Browse the repository at this point in the history
  • Loading branch information
zonyitoo committed Jun 1, 2024
1 parent 2acbaed commit 57a41a7
Show file tree
Hide file tree
Showing 2 changed files with 178 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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),
}
}
}
Expand Down
192 changes: 174 additions & 18 deletions crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/macos.rs
Original file line number Diff line number Diff line change
@@ -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<UdpSocket>,
}

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<UdpRedirSocket> {
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
pub fn listen(ty: RedirType, addr: SocketAddr) -> io::Result<UdpRedirSocket> {
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<UdpRedirSocket> {
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
pub fn bind_nonlocal(ty: RedirType, addr: SocketAddr, _: &RedirSocketOpts) -> io::Result<UdpRedirSocket> {
UdpRedirSocket::bind(ty, addr, true)
}

fn bind(ty: RedirType, addr: SocketAddr, reuse_port: bool) -> io::Result<UdpRedirSocket> {
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<usize> {
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
pub async fn send_to(&self, buf: &[u8], target: SocketAddr) -> io::Result<usize> {
poll_fn(|cx| self.poll_send_to(cx, buf, target)).await
}

fn poll_send_to(&self, cx: &mut Context<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>> {
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<SocketAddr> {
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<io::Result<(usize, SocketAddr, SocketAddr)>> {
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(())
}

0 comments on commit 57a41a7

Please sign in to comment.