|
1 | 1 | use std::{
|
2 |
| - io, |
3 |
| - net::SocketAddr, |
| 2 | + io::{self, Error, ErrorKind}, |
| 3 | + mem, |
| 4 | + net::{SocketAddr, UdpSocket}, |
| 5 | + os::unix::io::AsRawFd, |
4 | 6 | task::{Context, Poll},
|
5 | 7 | };
|
6 | 8 |
|
| 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 | + |
7 | 15 | use crate::{
|
8 | 16 | 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 | + }, |
10 | 21 | };
|
11 | 22 |
|
12 |
| -pub struct UdpRedirSocket; |
| 23 | +pub struct UdpRedirSocket { |
| 24 | + io: AsyncFd<UdpSocket>, |
| 25 | +} |
13 | 26 |
|
14 | 27 | impl UdpRedirSocket {
|
15 | 28 | /// Create a new UDP socket binded to `addr`
|
16 | 29 | ///
|
17 | 30 | /// 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) |
20 | 33 | }
|
21 | 34 |
|
22 | 35 | /// Create a new UDP socket binded to `addr`
|
23 | 36 | ///
|
24 | 37 | /// 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 }) |
31 | 111 | }
|
32 | 112 |
|
33 | 113 | /// 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 | + } |
36 | 129 | }
|
37 | 130 |
|
38 | 131 | /// Returns the local address that this socket is bound to.
|
39 | 132 | 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() |
41 | 134 | }
|
42 | 135 | }
|
43 | 136 |
|
44 | 137 | impl UdpSocketRedir for UdpRedirSocket {
|
45 | 138 | fn poll_recv_dest_from(
|
46 | 139 | &self,
|
47 |
| - _cx: &mut Context<'_>, |
48 |
| - _buf: &mut [u8], |
| 140 | + cx: &mut Context<'_>, |
| 141 | + buf: &mut [u8], |
49 | 142 | ) -> 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 | + } |
51 | 192 | }
|
| 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(()) |
52 | 208 | }
|
0 commit comments