From be518cedbb97462ca29651ddaf633cfed30e7000 Mon Sep 17 00:00:00 2001 From: zonyitoo Date: Sun, 10 Apr 2022 01:07:17 +0800 Subject: [PATCH] disable IP fragmentation for UDP sockets ref https://github.com/shadowsocks/shadowsocks-libev/commit/46382c23fac8fbabb62130ef49d3f0bf4c807b43 Setting IP_DONTFRAG on UDP sockets will reject packets that are too large to be fit into a MTU. This will help to prevent clients to send too large packets and probably improve performance. --- Cargo.lock | 4 +- Cargo.toml | 2 +- crates/shadowsocks/Cargo.toml | 2 +- .../src/net/sys/unix/bsd/freebsd.rs | 56 ++++++++++++++- .../shadowsocks/src/net/sys/unix/bsd/macos.rs | 44 +++++++++++- .../shadowsocks/src/net/sys/unix/linux/mod.rs | 44 +++++++++++- crates/shadowsocks/src/net/sys/unix/mod.rs | 19 +++-- crates/shadowsocks/src/net/sys/unix/others.rs | 11 ++- crates/shadowsocks/src/net/sys/windows/mod.rs | 72 ++++++++++++++++++- 9 files changed, 240 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5df7b1d9..10f1ae3f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2000,7 +2000,7 @@ dependencies = [ [[package]] name = "shadowsocks" -version = "1.14.2" +version = "1.14.3" dependencies = [ "arc-swap 1.5.0", "async-trait", @@ -2055,7 +2055,7 @@ dependencies = [ [[package]] name = "shadowsocks-rust" -version = "1.14.3" +version = "1.14.4" dependencies = [ "build-time", "byte_string", diff --git a/Cargo.toml b/Cargo.toml index afa9f1da..3cc55bfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks-rust" -version = "1.14.3" +version = "1.14.4" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/crates/shadowsocks/Cargo.toml b/crates/shadowsocks/Cargo.toml index fe4d09f1..1c106a56 100644 --- a/crates/shadowsocks/Cargo.toml +++ b/crates/shadowsocks/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "shadowsocks" -version = "1.14.2" +version = "1.14.3" authors = ["Shadowsocks Contributors"] description = "shadowsocks is a fast tunnel proxy that helps you bypass firewalls." repository = "https://github.com/shadowsocks/shadowsocks-rust" diff --git a/crates/shadowsocks/src/net/sys/unix/bsd/freebsd.rs b/crates/shadowsocks/src/net/sys/unix/bsd/freebsd.rs index a532b5a1..7abbbcfd 100644 --- a/crates/shadowsocks/src/net/sys/unix/bsd/freebsd.rs +++ b/crates/shadowsocks/src/net/sys/unix/bsd/freebsd.rs @@ -7,7 +7,7 @@ use std::{ task::{self, Poll}, }; -use log::error; +use log::{error, warn}; use pin_project::pin_project; use tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, @@ -166,6 +166,53 @@ pub fn set_tcp_fastopen(socket: &S) -> io::Result<()> { Ok(()) } +/// Disable IP fragmentation +#[inline] +pub fn set_disable_ip_fragmentation(af: AddrFamily, socket: &S) -> 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 + + unsafe { + match af { + AddrFamily::Ipv4 => { + let enable: i32 = 1; + let ret = libc::setsockopt( + socket.as_raw_fd(), + libc::IPPROTO_IP, + IP_DONTFRAG, + &enable as *const _ as *const _, + mem::size_of_val(&enable) as libc::socklen_t, + ); + + if ret < 0 { + return Err(io::Error::last_os_error()); + } + } + AddrFamily::Ipv6 => { + let enable: i32 = 1; + let ret = libc::setsockopt( + socket.as_raw_fd(), + libc::IPPROTO_IPV6, + IPV6_DONTFRAG, + &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(()) +} + /// Create a `UdpSocket` for connecting to `addr` #[inline(always)] pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> io::Result { @@ -176,5 +223,10 @@ pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> (AddrFamily::Ipv6, ..) => SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0), }; - UdpSocket::bind(bind_addr).await + let socket = UdpSocket::bind(bind_addr).await?; + if let Err(err) = set_disable_ip_fragmentation(af, &socket) { + warn!("failed to disable IP fragmentation, error: {}", err); + } + + Ok(socket) } diff --git a/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs b/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs index 2be50eb5..2c78a7f5 100644 --- a/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs +++ b/crates/shadowsocks/src/net/sys/unix/bsd/macos.rs @@ -8,7 +8,7 @@ use std::{ task::{self, Poll}, }; -use log::error; +use log::{error, warn}; use pin_project::pin_project; use tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, @@ -202,6 +202,45 @@ fn set_ip_bound_if(socket: &S, addr: SocketAddr, iface: &str) -> io: Ok(()) } +/// Disable IP fragmentation +#[inline] +pub fn set_disable_ip_fragmentation(af: AddrFamily, socket: &S) -> io::Result<()> { + unsafe { + match af { + AddrFamily::Ipv4 => { + let enable: i32 = 1; + let ret = libc::setsockopt( + socket.as_raw_fd(), + libc::IPPROTO_IP, + libc::IP_DONTFRAG, + &enable as *const _ as *const _, + mem::size_of_val(&enable) as libc::socklen_t, + ); + + if ret < 0 { + return Err(io::Error::last_os_error()); + } + } + AddrFamily::Ipv6 => { + let enable: i32 = 1; + let ret = libc::setsockopt( + socket.as_raw_fd(), + libc::IPPROTO_IPV6, + libc::IPV6_DONTFRAG, + &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(()) +} + /// Create a `UdpSocket` for connecting to `addr` pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> io::Result { let bind_addr = match (af, config.bind_local_addr) { @@ -212,6 +251,9 @@ pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> }; let socket = UdpSocket::bind(bind_addr).await?; + if let Err(err) = set_disable_ip_fragmentation(af, &socket) { + warn!("failed to disable IP fragmentation, error: {}", err); + } // Set IP_BOUND_IF for BSD-like if let Some(ref iface) = config.bind_interface { diff --git a/crates/shadowsocks/src/net/sys/unix/linux/mod.rs b/crates/shadowsocks/src/net/sys/unix/linux/mod.rs index ee656065..cd487412 100644 --- a/crates/shadowsocks/src/net/sys/unix/linux/mod.rs +++ b/crates/shadowsocks/src/net/sys/unix/linux/mod.rs @@ -8,7 +8,7 @@ use std::{ }; use cfg_if::cfg_if; -use log::error; +use log::{error, warn}; use pin_project::pin_project; use tokio::{ io::{AsyncRead, AsyncWrite, ReadBuf}, @@ -197,6 +197,45 @@ pub fn set_tcp_fastopen(socket: &S) -> io::Result<()> { Ok(()) } +/// Disable IP fragmentation +#[inline] +pub fn set_disable_ip_fragmentation(af: AddrFamily, socket: &S) -> io::Result<()> { + // For Linux, IP_MTU_DISCOVER should be enabled for both IPv4 and IPv6 sockets + // https://man7.org/linux/man-pages/man7/ip.7.html + + unsafe { + let value: i32 = libc::IP_PMTUDISC_DO; + let ret = libc::setsockopt( + socket.as_raw_fd(), + libc::IPPROTO_IP, + libc::IP_MTU_DISCOVER, + &value as *const _ as *const _, + mem::size_of_val(&value) as libc::socklen_t, + ); + + if ret < 0 { + return Err(io::Error::last_os_error()); + } + + if af == AddrFamily::Ipv6 { + let value: i32 = libc::IP_PMTUDISC_DO; + let ret = libc::setsockopt( + socket.as_raw_fd(), + libc::IPPROTO_IPV6, + libc::IPV6_MTU_DISCOVER, + &value as *const _ as *const _, + mem::size_of_val(&value) as libc::socklen_t, + ); + + if ret < 0 { + return Err(io::Error::last_os_error()); + } + } + } + + Ok(()) +} + /// Create a `UdpSocket` for connecting to `addr` pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> io::Result { let bind_addr = match (af, config.bind_local_addr) { @@ -207,6 +246,9 @@ pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> }; let socket = UdpSocket::bind(bind_addr).await?; + if let Err(err) = set_disable_ip_fragmentation(af, &socket) { + warn!("failed to disable IP fragmentation, error: {}", err); + } // Any traffic except localhost should be protected // This is a workaround for VPNService diff --git a/crates/shadowsocks/src/net/sys/unix/mod.rs b/crates/shadowsocks/src/net/sys/unix/mod.rs index 3f0b9085..335cd039 100644 --- a/crates/shadowsocks/src/net/sys/unix/mod.rs +++ b/crates/shadowsocks/src/net/sys/unix/mod.rs @@ -5,10 +5,11 @@ use std::{ }; use cfg_if::cfg_if; +use log::warn; use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type}; use tokio::net::UdpSocket; -use crate::net::{is_dual_stack_addr, sys::socket_bind_dual_stack, ConnectOpts}; +use crate::net::{is_dual_stack_addr, sys::socket_bind_dual_stack, AddrFamily, ConnectOpts}; cfg_if! { if #[cfg(any(target_os = "linux", target_os = "android"))] { @@ -36,16 +37,26 @@ pub mod uds; pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io::Result { let set_dual_stack = is_dual_stack_addr(addr); - if !set_dual_stack { - UdpSocket::bind(addr).await + let socket = if !set_dual_stack { + UdpSocket::bind(addr).await? } else { let socket = Socket::new(Domain::for_address(*addr), Type::DGRAM, Some(Protocol::UDP))?; socket_bind_dual_stack(&socket, addr, ipv6_only)?; // UdpSocket::from_std requires socket to be non-blocked socket.set_nonblocking(true)?; - UdpSocket::from_std(socket.into()) + UdpSocket::from_std(socket.into())? + }; + + let addr_family = match addr { + SocketAddr::V4(..) => AddrFamily::Ipv4, + SocketAddr::V6(..) => AddrFamily::Ipv6, + }; + if let Err(err) = set_disable_ip_fragmentation(addr_family, &socket) { + warn!("failed to disable IP fragmentation, error: {}", err); } + + Ok(socket) } pub fn set_common_sockopt_after_connect(stream: &S, opts: &ConnectOpts) -> io::Result<()> { diff --git a/crates/shadowsocks/src/net/sys/unix/others.rs b/crates/shadowsocks/src/net/sys/unix/others.rs index 8913040c..752503a2 100644 --- a/crates/shadowsocks/src/net/sys/unix/others.rs +++ b/crates/shadowsocks/src/net/sys/unix/others.rs @@ -71,6 +71,12 @@ impl AsyncWrite for TcpStream { } } +/// Disable IP fragmentation +#[inline] +pub fn set_disable_ip_fragmentation(_af: AddrFamily, _socket: &S) -> io::Result<()> { + Ok(()) +} + /// Create a `UdpSocket` for connecting to `addr` #[inline(always)] pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> io::Result { @@ -81,7 +87,10 @@ pub async fn create_outbound_udp_socket(af: AddrFamily, config: &ConnectOpts) -> (AddrFamily::Ipv6, ..) => SocketAddr::new(Ipv6Addr::UNSPECIFIED.into(), 0), }; - UdpSocket::bind(bind_addr).await + let socket = UdpSocket::bind(bind_addr).await?; + let _ = set_disable_ip_fragmentation(af, &socket); + + Ok(socket) } pub fn set_tcp_fastopen(_: &S) -> io::Result<()> { diff --git a/crates/shadowsocks/src/net/sys/windows/mod.rs b/crates/shadowsocks/src/net/sys/windows/mod.rs index e5ecdd43..f4489ce3 100644 --- a/crates/shadowsocks/src/net/sys/windows/mod.rs +++ b/crates/shadowsocks/src/net/sys/windows/mod.rs @@ -9,7 +9,7 @@ use std::{ task::{self, Poll}, }; -use log::error; +use log::{error, warn}; use pin_project::pin_project; use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type}; use tokio::{ @@ -48,6 +48,10 @@ const TCP_FASTOPEN: DWORD = 15; // https://github.com/retep998/winapi-rs/pull/1007 const IP_UNICAST_IF: DWORD = 31; +// ws2ipdef.h +const IP_MTU_DISCOVER: DWORD = 71; // Set/get path MTU discover state. +const IPV6_MTU_DISCOVER: DWORD = 71; // Set/get path MTU discover state. + /// A `TcpStream` that supports TFO (TCP Fast Open) #[pin_project(project = TcpStreamProj)] pub enum TcpStream { @@ -270,6 +274,61 @@ fn disable_connection_reset(socket: &UdpSocket) -> io::Result<()> { Ok(()) } +/// Disable IP fragmentation +#[inline] +pub fn set_disable_ip_fragmentation(af: AddrFamily, socket: &S) -> io::Result<()> { + // This is an enum PMTUD_STATE + // + // ```c + // typedef enum _PMTUD_STATE { + // IP_PMTUDISC_NOT_SET, + // IP_PMTUDISC_DO, + // IP_PMTUDISC_DONT, + // IP_PMTUDISC_PROBE, + // IP_PMTUDISC_MAX + // } PMTUD_STATE, *PPMTUD_STATE; + // ``` + const IP_PMTUDISC_DO: DWORD = 1; + + let handle = socket.as_raw_socket() as SOCKET; + + unsafe { + // For Windows, IP_MTU_DISCOVER should be enabled for both IPv4 and IPv6 sockets + // https://docs.microsoft.com/en-us/windows/win32/winsock/ipproto-ip-socket-options + let value: DWORD = IP_PMTUDISC_DO; + let ret = setsockopt( + handle, + IPPROTO_IP as c_int, + IP_MTU_DISCOVER as c_int, + &value as *const _ as *const c_char, + mem::size_of_val(&value) as c_int, + ); + + if ret == SOCKET_ERROR { + let err = io::Error::from_raw_os_error(WSAGetLastError()); + return Err(err); + } + + if af == AddrFamily::Ipv6 { + let value: DWORD = IP_PMTUDISC_DO; + let ret = setsockopt( + handle, + IPPROTO_IPV6 as c_int, + IPV6_MTU_DISCOVER as c_int, + &value as *const _ as *const c_char, + mem::size_of_val(&value) as c_int, + ); + + if ret == SOCKET_ERROR { + let err = io::Error::from_raw_os_error(WSAGetLastError()); + return Err(err); + } + } + } + + Ok(()) +} + /// Create a `UdpSocket` binded to `addr` /// /// It also disables `WSAECONNRESET` for UDP socket @@ -287,7 +346,15 @@ pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io UdpSocket::from_std(socket.into())? }; + let addr_family = match addr { + SocketAddr::V4(..) => AddrFamily::Ipv4, + SocketAddr::V6(..) => AddrFamily::Ipv6, + }; + if let Err(err) = set_disable_ip_fragmentation(addr_family, &socket) { + warn!("failed to disable IP fragmentation, error: {}", err); + } disable_connection_reset(&socket)?; + Ok(socket) } @@ -302,6 +369,9 @@ pub async fn create_outbound_udp_socket(af: AddrFamily, opts: &ConnectOpts) -> i }; let socket = UdpSocket::bind(bind_addr).await?; + if let Err(err) = set_disable_ip_fragmentation(af, &socket) { + warn!("failed to disable IP fragmentation, error: {}", err); + } disable_connection_reset(&socket)?; if let Some(ref iface) = opts.bind_interface {