Skip to content

Commit 57a41a7

Browse files
committed
fix(local-redir): FreeBSD code cleanup, macOS calls pf natlook
1 parent 2acbaed commit 57a41a7

File tree

2 files changed

+178
-43
lines changed
  • crates/shadowsocks-service/src/local/redir/udprelay/sys/unix

2 files changed

+178
-43
lines changed

crates/shadowsocks-service/src/local/redir/udprelay/sys/unix/bsd.rs

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use std::{
77
task::{Context, Poll},
88
};
99

10-
use cfg_if::cfg_if;
1110
use futures::{future::poll_fn, ready};
1211
use log::{error, trace, warn};
1312
use shadowsocks::net::is_dual_stack_addr;
@@ -148,31 +147,11 @@ impl UdpSocketRedir for UdpRedirSocket {
148147
loop {
149148
let mut read_guard = ready!(self.io.poll_read_ready(cx))?;
150149

151-
cfg_if! {
152-
if #[cfg(any(target_os = "macos", target_os = "ios"))] {
153-
use crate::local::redir::sys::bsd_pf::PF;
154-
155-
let (peer_addr, n) = match self.io.get_ref().recv_from(buf) {
156-
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
157-
read_guard.clear_ready();
158-
continue;
159-
}
160-
Err(e) => return Err(e),
161-
Ok(x) => x,
162-
};
163-
164-
let bind_addr = self.local_addr()?;
165-
let actual_addr = PF.natlook(&bind_addr, &peer_addr, Protocol::UDP)?;
166-
167-
return Ok((n, peer_addr, actual_addr));
168-
} else if #[cfg(target_os = "freebsd")] {
169-
match recv_dest_from(self.io.get_ref(), buf) {
170-
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
171-
read_guard.clear_ready();
172-
}
173-
x => return Poll::Ready(x),
174-
}
150+
match recv_dest_from(self.io.get_ref(), buf) {
151+
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
152+
read_guard.clear_ready();
175153
}
154+
x => return Poll::Ready(x),
176155
}
177156
}
178157
}
Lines changed: 174 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,52 +1,208 @@
11
use std::{
2-
io,
3-
net::SocketAddr,
2+
io::{self, Error, ErrorKind},
3+
mem,
4+
net::{SocketAddr, UdpSocket},
5+
os::unix::io::AsRawFd,
46
task::{Context, Poll},
57
};
68

9+
use futures::{future::poll_fn, ready};
10+
use log::{error, trace, warn};
11+
use shadowsocks::net::is_dual_stack_addr;
12+
use socket2::{Domain, Protocol, SockAddr, Socket, Type};
13+
use tokio::io::unix::AsyncFd;
14+
715
use crate::{
816
config::RedirType,
9-
local::redir::redir_ext::{RedirSocketOpts, UdpSocketRedir},
17+
local::redir::{
18+
redir_ext::{RedirSocketOpts, UdpSocketRedir},
19+
sys::{bsd_pf::PF, set_ipv6_only},
20+
},
1021
};
1122

12-
pub struct UdpRedirSocket;
23+
pub struct UdpRedirSocket {
24+
io: AsyncFd<UdpSocket>,
25+
}
1326

1427
impl UdpRedirSocket {
1528
/// Create a new UDP socket binded to `addr`
1629
///
1730
/// This will allow listening to `addr` that is not in local host
18-
pub fn listen(_ty: RedirType, _addr: SocketAddr) -> io::Result<UdpRedirSocket> {
19-
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
31+
pub fn listen(ty: RedirType, addr: SocketAddr) -> io::Result<UdpRedirSocket> {
32+
UdpRedirSocket::bind(ty, addr, false)
2033
}
2134

2235
/// Create a new UDP socket binded to `addr`
2336
///
2437
/// This will allow binding to `addr` that is not in local host
25-
pub fn bind_nonlocal(
26-
_ty: RedirType,
27-
_addr: SocketAddr,
28-
_redir_opts: &RedirSocketOpts,
29-
) -> io::Result<UdpRedirSocket> {
30-
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
38+
pub fn bind_nonlocal(ty: RedirType, addr: SocketAddr, _: &RedirSocketOpts) -> io::Result<UdpRedirSocket> {
39+
UdpRedirSocket::bind(ty, addr, true)
40+
}
41+
42+
fn bind(ty: RedirType, addr: SocketAddr, reuse_port: bool) -> io::Result<UdpRedirSocket> {
43+
if ty == RedirType::NotSupported {
44+
return Err(Error::new(
45+
ErrorKind::InvalidInput,
46+
"not supported udp transparent proxy type",
47+
));
48+
}
49+
50+
let socket = Socket::new(Domain::for_address(addr), Type::DGRAM, Some(Protocol::UDP))?;
51+
set_socket_before_bind(&addr, &socket)?;
52+
53+
socket.set_nonblocking(true)?;
54+
socket.set_reuse_address(true)?;
55+
if reuse_port {
56+
if let Err(err) = socket.set_reuse_port(true) {
57+
if let Some(errno) = err.raw_os_error() {
58+
match errno {
59+
libc::ENOPROTOOPT => {
60+
trace!("failed to set SO_REUSEPORT, error: {}", err);
61+
}
62+
_ => {
63+
error!("failed to set SO_REUSEPORT, error: {}", err);
64+
return Err(err);
65+
}
66+
}
67+
} else {
68+
error!("failed to set SO_REUSEPORT, error: {}", err);
69+
return Err(err);
70+
}
71+
}
72+
}
73+
74+
let sock_addr = SockAddr::from(addr);
75+
76+
if is_dual_stack_addr(&addr) {
77+
// set IP_ORIGDSTADDR before bind()
78+
79+
match set_ipv6_only(&socket, false) {
80+
Ok(..) => {
81+
if let Err(err) = socket.bind(&sock_addr) {
82+
warn!(
83+
"bind() dual-stack address {} failed, error: {}, fallback to IPV6_V6ONLY=true",
84+
addr, err
85+
);
86+
87+
if let Err(err) = set_ipv6_only(&socket, true) {
88+
warn!(
89+
"set IPV6_V6ONLY=true failed, error: {}, bind() to {} directly",
90+
err, addr
91+
);
92+
}
93+
94+
socket.bind(&sock_addr)?;
95+
}
96+
}
97+
Err(err) => {
98+
warn!(
99+
"set IPV6_V6ONLY=false failed, error: {}, bind() to {} directly",
100+
err, addr
101+
);
102+
socket.bind(&sock_addr)?;
103+
}
104+
}
105+
} else {
106+
socket.bind(&sock_addr)?;
107+
}
108+
109+
let io = AsyncFd::new(socket.into())?;
110+
Ok(UdpRedirSocket { io })
31111
}
32112

33113
/// Send data to the socket to the given target address
34-
pub async fn send_to(&self, _buf: &[u8], _target: SocketAddr) -> io::Result<usize> {
35-
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
114+
pub async fn send_to(&self, buf: &[u8], target: SocketAddr) -> io::Result<usize> {
115+
poll_fn(|cx| self.poll_send_to(cx, buf, target)).await
116+
}
117+
118+
fn poll_send_to(&self, cx: &mut Context<'_>, buf: &[u8], target: SocketAddr) -> Poll<io::Result<usize>> {
119+
loop {
120+
let mut write_guard = ready!(self.io.poll_write_ready(cx))?;
121+
122+
match self.io.get_ref().send_to(buf, target) {
123+
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
124+
write_guard.clear_ready();
125+
}
126+
x => return Poll::Ready(x),
127+
}
128+
}
36129
}
37130

38131
/// Returns the local address that this socket is bound to.
39132
pub fn local_addr(&self) -> io::Result<SocketAddr> {
40-
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
133+
self.io.get_ref().local_addr()
41134
}
42135
}
43136

44137
impl UdpSocketRedir for UdpRedirSocket {
45138
fn poll_recv_dest_from(
46139
&self,
47-
_cx: &mut Context<'_>,
48-
_buf: &mut [u8],
140+
cx: &mut Context<'_>,
141+
buf: &mut [u8],
49142
) -> Poll<io::Result<(usize, SocketAddr, SocketAddr)>> {
50-
unimplemented!("UDP transparent proxy is not supported on macOS, iOS, ...")
143+
loop {
144+
let mut read_guard = ready!(self.io.poll_read_ready(cx))?;
145+
146+
let (n, peer_addr) = match self.io.get_ref().recv_from(buf) {
147+
Err(ref e) if e.kind() == ErrorKind::WouldBlock => {
148+
read_guard.clear_ready();
149+
continue;
150+
}
151+
Err(e) => return Err(e).into(),
152+
Ok(x) => x,
153+
};
154+
155+
let bind_addr = self.local_addr()?;
156+
let actual_addr = PF.natlook(&bind_addr, &peer_addr, Protocol::UDP)?;
157+
158+
return Ok((n, peer_addr, actual_addr)).into();
159+
}
160+
}
161+
}
162+
163+
fn set_disable_ip_fragmentation(level: libc::c_int, socket: &Socket) -> io::Result<()> {
164+
// https://www.freebsd.org/cgi/man.cgi?query=ip&sektion=4&manpath=FreeBSD+9.0-RELEASE
165+
166+
// sys/netinet/in.h
167+
const IP_DONTFRAG: libc::c_int = 67; // don't fragment packet
168+
169+
// sys/netinet6/in6.h
170+
const IPV6_DONTFRAG: libc::c_int = 62; // bool; disable IPv6 fragmentation
171+
172+
let enable: libc::c_int = 1;
173+
174+
let opt = match level {
175+
libc::IPPROTO_IP => IP_DONTFRAG,
176+
libc::IPPROTO_IPV6 => IPV6_DONTFRAG,
177+
_ => unreachable!("level can only be IPPROTO_IP or IPPROTO_IPV6"),
178+
};
179+
180+
unsafe {
181+
let ret = libc::setsockopt(
182+
socket.as_raw_fd(),
183+
level,
184+
opt,
185+
&enable as *const _ as *const _,
186+
mem::size_of_val(&enable) as libc::socklen_t,
187+
);
188+
189+
if ret < 0 {
190+
return Err(io::Error::last_os_error());
191+
}
51192
}
193+
194+
Ok(())
195+
}
196+
197+
fn set_socket_before_bind(addr: &SocketAddr, socket: &Socket) -> io::Result<()> {
198+
// https://www.freebsd.org/cgi/man.cgi?query=ip&sektion=4&manpath=FreeBSD+9.0-RELEASE
199+
let level = match *addr {
200+
SocketAddr::V4(..) => libc::IPPROTO_IP,
201+
SocketAddr::V6(..) => libc::IPPROTO_IPV6,
202+
};
203+
204+
// 1. disable IP fragmentation
205+
set_disable_ip_fragmentation(level, socket)?;
206+
207+
Ok(())
52208
}

0 commit comments

Comments
 (0)