Repository: quinn-rs/quinn
Pull request #2179 (2025-03-20 08:13:24)

feat: Allow changing the UDP send/receive buffer sizes
Add `UdpSocketState::set_send_buffer_size` and `UdpSocketState::set_recv_buffer_size`, which allow resizing the respective socket buffers.

CC @mxinden

mxinden: Worth having the code below use the new functions?

https://github.com/quinn-rs/quinn/blob/434c35861e68aac1da568bcd0b1523603f73f255/perf/src/lib.rs#L28-L49
mxinden: Changes look good to me.

CI is failing, e.g. on Windows with:

```
---- socket_buffers stdout ----

thread 'socket_buffers' panicked at quinn-udp\tests\tests.rs:211:58:
called `Result::unwrap()` on an `Err` value: Os { code: 10022, kind: InvalidInput, message: "An invalid argument was supplied." }
```
larseggert: I am thinking that I will change the test to check that a "set" operation results in _any_ change of the buffer size in the right direction (increase or decrease).
larseggert: I can't figure out why `UdpSocketState::new` fails on WIndows 🤷 
mxinden: Do you know which of the syscalls in `UdpSocketState::new` fails?

https://github.com/quinn-rs/quinn/blob/434c35861e68aac1da568bcd0b1523603f73f255/quinn-udp/src/windows.rs#L31-L120
larseggert: > Do you know which of the syscalls in `UdpSocketState::new` fails?

`thread 'socket_buffers' panicked at quinn-udp\src\windows.rs:44:42:`
https://github.com/quinn-rs/quinn/actions/runs/13993457832/job/39182608862?pr=2179#step:6:350
larseggert: Turns out one needs to bind both sockets on Windows.
larseggert: I'll squash Monday. I also want to do a follow-up with methods for reading the buffer sizes. Bump afterwards would be nice.
djc: > Bump afterwards would be nice.

Just add a commit with a version bump when you're ready.
larseggert: I have no idea why (unrelated) tests are failing in CI, they are succeeding locally.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index f18fa66c..ab5fa38b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1596,7 +1596,7 @@ dependencies = [
 
 [[package]]
 name = "quinn-udp"
-version = "0.5.10"
+version = "0.5.11"
 dependencies = [
  "cfg_aliases",
  "criterion",
diff --git a/perf/src/lib.rs b/perf/src/lib.rs
index 46d29acd..7e10fcf4 100644
--- a/perf/src/lib.rs
+++ b/perf/src/lib.rs
@@ -1,6 +1,7 @@
 use std::net::SocketAddr;
 
 use anyhow::{Context, Result};
+use quinn::udp::UdpSocketState;
 use rustls::crypto::ring::cipher_suite;
 use socket2::{Domain, Protocol, Socket, Type};
 use tracing::warn;
@@ -25,14 +26,18 @@ pub fn bind_socket(
     socket
         .bind(&socket2::SockAddr::from(addr))
         .context("binding endpoint")?;
-    socket
-        .set_send_buffer_size(send_buffer_size)
+
+    let socket_state = UdpSocketState::new((&socket).into())?;
+    socket_state
+        .set_send_buffer_size((&socket).into(), send_buffer_size)
         .context("send buffer size")?;
-    socket
-        .set_recv_buffer_size(recv_buffer_size)
+    socket_state
+        .set_recv_buffer_size((&socket).into(), recv_buffer_size)
         .context("recv buffer size")?;
 
-    let buf_size = socket.send_buffer_size().context("send buffer size")?;
+    let buf_size = socket_state
+        .send_buffer_size((&socket).into())
+        .context("send buffer size")?;
     if buf_size < send_buffer_size {
         warn!(
             "Unable to set desired send buffer size. Desired: {}, Actual: {}",
@@ -40,7 +45,9 @@ pub fn bind_socket(
         );
     }
 
-    let buf_size = socket.recv_buffer_size().context("recv buffer size")?;
+    let buf_size = socket_state
+        .recv_buffer_size((&socket).into())
+        .context("recv buffer size")?;
     if buf_size < recv_buffer_size {
         warn!(
             "Unable to set desired recv buffer size. Desired: {}, Actual: {}",
diff --git a/quinn-udp/Cargo.toml b/quinn-udp/Cargo.toml
index 53d3ee78..70789607 100644
--- a/quinn-udp/Cargo.toml
+++ b/quinn-udp/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "quinn-udp"
-version = "0.5.10"
+version = "0.5.11"
 edition.workspace = true
 rust-version.workspace = true
 license.workspace = true
diff --git a/quinn-udp/src/fallback.rs b/quinn-udp/src/fallback.rs
index b35ab343..4444bed8 100644
--- a/quinn-udp/src/fallback.rs
+++ b/quinn-udp/src/fallback.rs
@@ -86,6 +86,30 @@ impl UdpSocketState {
         1
     }
 
+    /// Resize the send buffer of `socket` to `bytes`
+    #[inline]
+    pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
+        socket.0.set_send_buffer_size(bytes)
+    }
+
+    /// Resize the receive buffer of `socket` to `bytes`
+    #[inline]
+    pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
+        socket.0.set_recv_buffer_size(bytes)
+    }
+
+    /// Get the size of the `socket` send buffer
+    #[inline]
+    pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
+        socket.0.send_buffer_size()
+    }
+
+    /// Get the size of the `socket` receive buffer
+    #[inline]
+    pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
+        socket.0.recv_buffer_size()
+    }
+
     #[inline]
     pub fn may_fragment(&self) -> bool {
         true
diff --git a/quinn-udp/src/unix.rs b/quinn-udp/src/unix.rs
index 0dce8aad..d6a0345d 100644
--- a/quinn-udp/src/unix.rs
+++ b/quinn-udp/src/unix.rs
@@ -249,6 +249,30 @@ impl UdpSocketState {
         self.gro_segments
     }
 
+    /// Resize the send buffer of `socket` to `bytes`
+    #[inline]
+    pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
+        socket.0.set_send_buffer_size(bytes)
+    }
+
+    /// Resize the receive buffer of `socket` to `bytes`
+    #[inline]
+    pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
+        socket.0.set_recv_buffer_size(bytes)
+    }
+
+    /// Get the size of the `socket` send buffer
+    #[inline]
+    pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
+        socket.0.send_buffer_size()
+    }
+
+    /// Get the size of the `socket` receive buffer
+    #[inline]
+    pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
+        socket.0.recv_buffer_size()
+    }
+
     /// Whether transmitted datagrams might get fragmented by the IP layer
     ///
     /// Returns `false` on targets which employ e.g. the `IPV6_DONTFRAG` socket option.
diff --git a/quinn-udp/src/windows.rs b/quinn-udp/src/windows.rs
index 81b28e13..1e041072 100644
--- a/quinn-udp/src/windows.rs
+++ b/quinn-udp/src/windows.rs
@@ -292,6 +292,30 @@ impl UdpSocketState {
         64
     }
 
+    /// Resize the send buffer of `socket` to `bytes`
+    #[inline]
+    pub fn set_send_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
+        socket.0.set_send_buffer_size(bytes)
+    }
+
+    /// Resize the receive buffer of `socket` to `bytes`
+    #[inline]
+    pub fn set_recv_buffer_size(&self, socket: UdpSockRef<'_>, bytes: usize) -> io::Result<()> {
+        socket.0.set_recv_buffer_size(bytes)
+    }
+
+    /// Get the size of the `socket` send buffer
+    #[inline]
+    pub fn send_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
+        socket.0.send_buffer_size()
+    }
+
+    /// Get the size of the `socket` receive buffer
+    #[inline]
+    pub fn recv_buffer_size(&self, socket: UdpSockRef<'_>) -> io::Result<usize> {
+        socket.0.recv_buffer_size()
+    }
+
     #[inline]
     pub fn may_fragment(&self) -> bool {
         false


Test patch:
diff --git a/quinn-udp/tests/tests.rs b/quinn-udp/tests/tests.rs
index 25de5cfc..96f8415b 100644
--- a/quinn-udp/tests/tests.rs
+++ b/quinn-udp/tests/tests.rs
@@ -1,8 +1,8 @@
 #[cfg(not(any(target_os = "openbsd", target_os = "netbsd", solarish)))]
-use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
+use std::net::{SocketAddr, SocketAddrV6};
 use std::{
     io::IoSliceMut,
-    net::{IpAddr, Ipv4Addr, Ipv6Addr, UdpSocket},
+    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddrV4, UdpSocket},
     slice,
 };
 
@@ -186,6 +186,79 @@ fn gso() {
     );
 }
 
+#[test]
+fn socket_buffers() {
+    const BUFFER_SIZE: usize = 123456;
+    const FACTOR: usize = if cfg!(any(target_os = "linux", target_os = "android")) {
+        2 // Linux and Android set the buffer to double the requested size
+    } else {
+        1 // Everyone else is sane.
+    };
+
+    let send = socket2::Socket::new(
+        socket2::Domain::IPV4,
+        socket2::Type::DGRAM,
+        Some(socket2::Protocol::UDP),
+    )
+    .unwrap();
+    let recv = socket2::Socket::new(
+        socket2::Domain::IPV4,
+        socket2::Type::DGRAM,
+        Some(socket2::Protocol::UDP),
+    )
+    .unwrap();
+    for sock in [&send, &recv] {
+        sock.bind(&socket2::SockAddr::from(SocketAddrV4::new(
+            Ipv4Addr::LOCALHOST,
+            0,
+        )))
+        .unwrap();
+
+        let socket_state = UdpSocketState::new(sock.into()).expect("created socket state");
+
+        // Change the send buffer size.
+        let buffer_before = socket_state.send_buffer_size(sock.into()).unwrap();
+        assert_ne!(
+            buffer_before,
+            BUFFER_SIZE * FACTOR,
+            "make sure buffer is not already desired size"
+        );
+        socket_state
+            .set_send_buffer_size(sock.into(), BUFFER_SIZE)
+            .expect("set send buffer size {buffer_before} -> {BUFFER_SIZE}");
+        let buffer_after = socket_state.send_buffer_size(sock.into()).unwrap();
+        assert_eq!(
+            buffer_after,
+            BUFFER_SIZE * FACTOR,
+            "setting send buffer size to {BUFFER_SIZE} resulted in {buffer_before} -> {buffer_after}",
+        );
+
+        // Change the receive buffer size.
+        let buffer_before = socket_state.recv_buffer_size(sock.into()).unwrap();
+        socket_state
+            .set_recv_buffer_size(sock.into(), BUFFER_SIZE)
+            .expect("set recv buffer size {buffer_before} -> {BUFFER_SIZE}");
+        let buffer_after = socket_state.recv_buffer_size(sock.into()).unwrap();
+        assert_eq!(
+            buffer_after,
+            BUFFER_SIZE * FACTOR,
+            "setting recv buffer size to {BUFFER_SIZE} resulted in {buffer_before} -> {buffer_after}",
+        );
+    }
+
+    test_send_recv(
+        &send,
+        &recv,
+        Transmit {
+            destination: recv.local_addr().unwrap().as_socket().unwrap(),
+            ecn: None,
+            contents: b"hello",
+            segment_size: None,
+            src_ip: None,
+        },
+    );
+}
+
 fn test_send_recv(send: &Socket, recv: &Socket, transmit: Transmit) {
     let send_state = UdpSocketState::new(send.into()).unwrap();
     let recv_state = UdpSocketState::new(recv.into()).unwrap();