diff --git a/crates/shadowsocks-service/src/manager/mod.rs b/crates/shadowsocks-service/src/manager/mod.rs index d174d63f..8cd16c4f 100644 --- a/crates/shadowsocks-service/src/manager/mod.rs +++ b/crates/shadowsocks-service/src/manager/mod.rs @@ -68,6 +68,7 @@ pub async fn run(config: Config) -> io::Result<()> { accept_opts.tcp.keepalive = config.keep_alive.or(Some(SERVER_DEFAULT_KEEPALIVE_TIMEOUT)); accept_opts.tcp.mptcp = config.mptcp; accept_opts.udp.mtu = config.udp_mtu; + accept_opts.udp.allow_fragmentation = config.outbound_udp_allow_fragmentation; if let Some(resolver) = build_dns_resolver(config.dns, config.ipv6_first, config.dns_cache_size, &connect_opts).await diff --git a/crates/shadowsocks-service/src/server/mod.rs b/crates/shadowsocks-service/src/server/mod.rs index 88c6d3db..800d835c 100644 --- a/crates/shadowsocks-service/src/server/mod.rs +++ b/crates/shadowsocks-service/src/server/mod.rs @@ -100,6 +100,7 @@ pub async fn run(config: Config) -> io::Result<()> { accept_opts.tcp.fastopen = config.fast_open; accept_opts.tcp.keepalive = config.keep_alive.or(Some(SERVER_DEFAULT_KEEPALIVE_TIMEOUT)); accept_opts.tcp.mptcp = config.mptcp; + accept_opts.udp.allow_fragmentation = config.outbound_udp_allow_fragmentation; accept_opts.udp.mtu = config.udp_mtu; let resolver = build_dns_resolver(config.dns, config.ipv6_first, config.dns_cache_size, &connect_opts) @@ -118,7 +119,7 @@ pub async fn run(config: Config) -> io::Result<()> { } let mut connect_opts = connect_opts.clone(); - let accept_opts = accept_opts.clone(); + let mut accept_opts = accept_opts.clone(); #[cfg(any(target_os = "linux", target_os = "android"))] if let Some(fwmark) = inst.outbound_fwmark { @@ -140,6 +141,7 @@ pub async fn run(config: Config) -> io::Result<()> { if let Some(udp_allow_fragmentation) = inst.outbound_udp_allow_fragmentation { connect_opts.udp.allow_fragmentation = udp_allow_fragmentation; + accept_opts.udp.allow_fragmentation = udp_allow_fragmentation; } let has_outbound_proxies = !inst.outbound_proxy.is_empty() || !config.outbound_proxy.is_empty(); diff --git a/crates/shadowsocks/src/net/option.rs b/crates/shadowsocks/src/net/option.rs index 35f24502..29b3d896 100644 --- a/crates/shadowsocks/src/net/option.rs +++ b/crates/shadowsocks/src/net/option.rs @@ -38,7 +38,7 @@ pub struct UdpSocketOpts { /// NOTE: MTU includes IP header, UDP header, UDP payload pub mtu: Option, - /// Outbound UDP socket allows IP fragmentation + /// UDP socket allows IP fragmentation pub allow_fragmentation: bool, } diff --git a/crates/shadowsocks/src/net/sys/unix/mod.rs b/crates/shadowsocks/src/net/sys/unix/mod.rs index 3586d8a1..403af901 100644 --- a/crates/shadowsocks/src/net/sys/unix/mod.rs +++ b/crates/shadowsocks/src/net/sys/unix/mod.rs @@ -30,7 +30,11 @@ cfg_if! { pub mod uds; /// Create a `UdpSocket` binded to `addr` -pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io::Result { +pub async fn create_inbound_udp_socket( + addr: &SocketAddr, + ipv6_only: bool, + allow_fragmentation: bool, +) -> io::Result { let set_dual_stack = is_dual_stack_addr(addr); let socket = if !set_dual_stack { @@ -48,7 +52,7 @@ pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io SocketAddr::V4(..) => AddrFamily::Ipv4, SocketAddr::V6(..) => AddrFamily::Ipv6, }; - if let Err(err) = set_disable_ip_fragmentation(addr_family, &socket) { + if !allow_fragmentation && let Err(err) = set_disable_ip_fragmentation(addr_family, &socket) { warn!("failed to disable IP fragmentation, error: {}", err); } diff --git a/crates/shadowsocks/src/net/sys/windows/mod.rs b/crates/shadowsocks/src/net/sys/windows/mod.rs index 06b9213c..50710521 100644 --- a/crates/shadowsocks/src/net/sys/windows/mod.rs +++ b/crates/shadowsocks/src/net/sys/windows/mod.rs @@ -431,7 +431,11 @@ pub fn set_disable_ip_fragmentation(af: AddrFamily, socket: &S) /// Create a `UdpSocket` binded to `addr` /// /// It also disables `WSAECONNRESET` for UDP socket -pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io::Result { +pub async fn create_inbound_udp_socket( + addr: &SocketAddr, + ipv6_only: bool, + allow_fragmentation: bool, +) -> io::Result { let set_dual_stack = is_dual_stack_addr(addr); let socket = if !set_dual_stack { @@ -449,7 +453,7 @@ pub async fn create_inbound_udp_socket(addr: &SocketAddr, ipv6_only: bool) -> io SocketAddr::V4(..) => AddrFamily::Ipv4, SocketAddr::V6(..) => AddrFamily::Ipv6, }; - if let Err(err) = set_disable_ip_fragmentation(addr_family, &socket) { + if !allow_fragmentation && let Err(err) = set_disable_ip_fragmentation(addr_family, &socket) { warn!("failed to disable IP fragmentation, error: {}", err); } disable_connection_reset(&socket)?; diff --git a/crates/shadowsocks/src/net/udp.rs b/crates/shadowsocks/src/net/udp.rs index d24afa48..83d31eb2 100644 --- a/crates/shadowsocks/src/net/udp.rs +++ b/crates/shadowsocks/src/net/udp.rs @@ -189,7 +189,7 @@ impl UdpSocket { /// Binds to a specific address (inbound) pub async fn listen_with_opts(addr: &SocketAddr, opts: AcceptOpts) -> io::Result { - let socket = create_inbound_udp_socket(addr, opts.ipv6_only).await?; + let socket = create_inbound_udp_socket(addr, opts.ipv6_only, opts.udp.allow_fragmentation).await?; Ok(Self { socket, mtu: opts.udp.mtu, diff --git a/crates/shadowsocks/tests/udp.rs b/crates/shadowsocks/tests/udp.rs index 8b257eec..96e104da 100644 --- a/crates/shadowsocks/tests/udp.rs +++ b/crates/shadowsocks/tests/udp.rs @@ -8,10 +8,45 @@ use shadowsocks::{ config::{ServerConfig, ServerType}, context::{Context, SharedContext}, crypto::CipherKind, - net::UdpSocket as ShadowUdpSocket, + net::{AcceptOpts, UdpSocket as ShadowUdpSocket}, relay::{socks5::Address, udprelay::ProxySocket}, }; +#[cfg(target_os = "linux")] +fn ipv4_mtu_discovery(socket: &ShadowUdpSocket) -> i32 { + use std::{mem, os::fd::AsRawFd}; + + let mut value = 0; + let mut value_len = mem::size_of_val(&value) as libc::socklen_t; + let result = unsafe { + libc::getsockopt( + socket.as_raw_fd(), + libc::IPPROTO_IP, + libc::IP_MTU_DISCOVER, + &mut value as *mut _ as *mut libc::c_void, + &mut value_len, + ) + }; + assert_eq!(result, 0); + value +} + +#[cfg(target_os = "linux")] +#[tokio::test] +async fn inbound_udp_fragmentation_option() { + let addr = "127.0.0.1:0".parse().unwrap(); + + let socket = ShadowUdpSocket::listen_with_opts(&addr, AcceptOpts::default()) + .await + .unwrap(); + assert_eq!(ipv4_mtu_discovery(&socket), libc::IP_PMTUDISC_DO); + + let mut opts = AcceptOpts::default(); + opts.udp.allow_fragmentation = true; + let socket = ShadowUdpSocket::listen_with_opts(&addr, opts).await.unwrap(); + assert_ne!(ipv4_mtu_discovery(&socket), libc::IP_PMTUDISC_DO); +} + async fn handle_udp_server_client( peer_addr: SocketAddr, remote_addr: Address,