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.
This commit is contained in:
zonyitoo
2022-04-10 01:43:39 +08:00
parent 3331500b6e
commit be518cedbb
9 changed files with 240 additions and 14 deletions
Generated
+2 -2
View File
@@ -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",
+1 -1
View File
@@ -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"
+1 -1
View File
@@ -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"
@@ -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<S: AsRawFd>(socket: &S) -> io::Result<()> {
Ok(())
}
/// Disable IP fragmentation
#[inline]
pub fn set_disable_ip_fragmentation<S: AsRawFd>(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<UdpSocket> {
@@ -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)
}
@@ -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<S: AsRawFd>(socket: &S, addr: SocketAddr, iface: &str) -> io:
Ok(())
}
/// Disable IP fragmentation
#[inline]
pub fn set_disable_ip_fragmentation<S: AsRawFd>(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<UdpSocket> {
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 {
@@ -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<S: AsRawFd>(socket: &S) -> io::Result<()> {
Ok(())
}
/// Disable IP fragmentation
#[inline]
pub fn set_disable_ip_fragmentation<S: AsRawFd>(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<UdpSocket> {
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
+15 -4
View File
@@ -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<UdpSocket> {
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<S: AsRawFd>(stream: &S, opts: &ConnectOpts) -> io::Result<()> {
+10 -1
View File
@@ -71,6 +71,12 @@ impl AsyncWrite for TcpStream {
}
}
/// Disable IP fragmentation
#[inline]
pub fn set_disable_ip_fragmentation<S: AsRawFd>(_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<UdpSocket> {
@@ -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: AsRawFd>(_: &S) -> io::Result<()> {
+71 -1
View File
@@ -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<S: AsRawSocket>(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 {