Skip to content

Commit 40c4af7

Browse files
authored
net: add TcpSocket::set_{send, recv}_buffer_size (#1384)
This commit adds methods for setting `SO_RECVBUF` and `SO_SNDBUF` to Mio's `TcpSocket` type. It would be nice to eventually expose these in Tokio, so adding them to Mio is the first step. See tokio-rs/tokio#3082 for details. Signed-off-by: Eliza Weisman <[email protected]>
1 parent 27fbd5f commit 40c4af7

File tree

5 files changed

+236
-3
lines changed

5 files changed

+236
-3
lines changed

src/net/tcp/socket.rs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,64 @@ impl TcpSocket {
106106
sys::tcp::set_linger(self.sys, dur)
107107
}
108108

109+
/// Sets the value of `SO_RCVBUF` on this socket.
110+
pub fn set_recv_buffer_size(&self, size: u32) -> io::Result<()> {
111+
sys::tcp::set_recv_buffer_size(self.sys, size)
112+
}
113+
114+
/// Get the value of `SO_RCVBUF` set on this socket.
115+
///
116+
/// Note that if [`set_recv_buffer_size`] has been called on this socket
117+
/// previously, the value returned by this function may not be the same as
118+
/// the argument provided to `set_recv_buffer_size`. This is for the
119+
/// following reasons:
120+
///
121+
/// * Most operating systems have minimum and maximum allowed sizes for the
122+
/// receive buffer, and will clamp the provided value if it is below the
123+
/// minimum or above the maximum. The minimum and maximum buffer sizes are
124+
/// OS-dependent.
125+
/// * Linux will double the buffer size to account for internal bookkeeping
126+
/// data, and returns the doubled value from `getsockopt(2)`. As per `man
127+
/// 7 socket`:
128+
/// > Sets or gets the maximum socket receive buffer in bytes. The
129+
/// > kernel doubles this value (to allow space for bookkeeping
130+
/// > overhead) when it is set using `setsockopt(2)`, and this doubled
131+
/// > value is returned by `getsockopt(2)`.
132+
///
133+
/// [`set_recv_buffer_size`]: #method.set_recv_buffer_size
134+
pub fn get_recv_buffer_size(&self) -> io::Result<u32> {
135+
sys::tcp::get_recv_buffer_size(self.sys)
136+
}
137+
138+
/// Sets the value of `SO_SNDBUF` on this socket.
139+
pub fn set_send_buffer_size(&self, size: u32) -> io::Result<()> {
140+
sys::tcp::set_send_buffer_size(self.sys, size)
141+
}
142+
143+
/// Get the value of `SO_SNDBUF` set on this socket.
144+
///
145+
/// Note that if [`set_send_buffer_size`] has been called on this socket
146+
/// previously, the value returned by this function may not be the same as
147+
/// the argument provided to `set_send_buffer_size`. This is for the
148+
/// following reasons:
149+
///
150+
/// * Most operating systems have minimum and maximum allowed sizes for the
151+
/// receive buffer, and will clamp the provided value if it is below the
152+
/// minimum or above the maximum. The minimum and maximum buffer sizes are
153+
/// OS-dependent.
154+
/// * Linux will double the buffer size to account for internal bookkeeping
155+
/// data, and returns the doubled value from `getsockopt(2)`. As per `man
156+
/// 7 socket`:
157+
/// > Sets or gets the maximum socket send buffer in bytes. The
158+
/// > kernel doubles this value (to allow space for bookkeeping
159+
/// > overhead) when it is set using `setsockopt(2)`, and this doubled
160+
/// > value is returned by `getsockopt(2)`.
161+
///
162+
/// [`set_send_buffer_size`]: #method.set_send_buffer_size
163+
pub fn get_send_buffer_size(&self) -> io::Result<u32> {
164+
sys::tcp::get_send_buffer_size(self.sys)
165+
}
166+
109167
/// Returns the local address of this socket
110168
///
111169
/// Will return `Err` result in windows if called before calling `bind`

src/sys/shell/tcp.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,22 @@ pub(crate) fn set_linger(_: TcpSocket, _: Option<Duration>) -> io::Result<()> {
5050
os_required!();
5151
}
5252

53+
pub(crate) fn set_recv_buffer_size(_: TcpSocket, _: u32) -> io::Result<()> {
54+
os_required!();
55+
}
56+
57+
pub(crate) fn get_recv_buffer_size(_: TcpSocket) -> io::Result<u32> {
58+
os_required!();
59+
}
60+
61+
pub(crate) fn set_send_buffer_size(_: TcpSocket, _: u32) -> io::Result<()> {
62+
os_required!();
63+
}
64+
65+
pub(crate) fn get_send_buffer_size(_: TcpSocket) -> io::Result<u32> {
66+
os_required!();
67+
}
68+
5369
pub fn accept(_: &net::TcpListener) -> io::Result<(net::TcpStream, SocketAddr)> {
5470
os_required!();
5571
}

src/sys/unix/tcp.rs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use std::io;
2+
use std::convert::TryInto;
23
use std::mem;
34
use std::mem::{size_of, MaybeUninit};
45
use std::net::{self, SocketAddr};
@@ -37,8 +38,6 @@ pub(crate) fn connect(socket: TcpSocket, addr: SocketAddr) -> io::Result<net::Tc
3738
}
3839

3940
pub(crate) fn listen(socket: TcpSocket, backlog: u32) -> io::Result<net::TcpListener> {
40-
use std::convert::TryInto;
41-
4241
let backlog = backlog.try_into().unwrap_or(i32::max_value());
4342
syscall!(listen(socket, backlog))?;
4443
Ok(unsafe { net::TcpListener::from_raw_fd(socket) })
@@ -130,6 +129,60 @@ pub(crate) fn set_linger(socket: TcpSocket, dur: Option<Duration>) -> io::Result
130129
)).map(|_| ())
131130
}
132131

132+
pub(crate) fn set_recv_buffer_size(socket: TcpSocket, size: u32) -> io::Result<()> {
133+
let size = size.try_into().ok().unwrap_or_else(i32::max_value);
134+
syscall!(setsockopt(
135+
socket,
136+
libc::SOL_SOCKET,
137+
libc::SO_RCVBUF,
138+
&size as *const _ as *const libc::c_void,
139+
size_of::<libc::c_int>() as libc::socklen_t
140+
))
141+
.map(|_| ())
142+
}
143+
144+
pub(crate) fn get_recv_buffer_size(socket: TcpSocket) -> io::Result<u32> {
145+
let mut optval: libc::c_int = 0;
146+
let mut optlen = size_of::<libc::c_int>() as libc::socklen_t;
147+
148+
syscall!(getsockopt(
149+
socket,
150+
libc::SOL_SOCKET,
151+
libc::SO_RCVBUF,
152+
&mut optval as *mut _ as *mut _,
153+
&mut optlen,
154+
))?;
155+
156+
Ok(optval as u32)
157+
}
158+
159+
pub(crate) fn set_send_buffer_size(socket: TcpSocket, size: u32) -> io::Result<()> {
160+
let size = size.try_into().ok().unwrap_or_else(i32::max_value);
161+
syscall!(setsockopt(
162+
socket,
163+
libc::SOL_SOCKET,
164+
libc::SO_SNDBUF,
165+
&size as *const _ as *const libc::c_void,
166+
size_of::<libc::c_int>() as libc::socklen_t
167+
))
168+
.map(|_| ())
169+
}
170+
171+
pub(crate) fn get_send_buffer_size(socket: TcpSocket) -> io::Result<u32> {
172+
let mut optval: libc::c_int = 0;
173+
let mut optlen = size_of::<libc::c_int>() as libc::socklen_t;
174+
175+
syscall!(getsockopt(
176+
socket,
177+
libc::SOL_SOCKET,
178+
libc::SO_SNDBUF,
179+
&mut optval as *mut _ as *mut _,
180+
&mut optlen,
181+
))?;
182+
183+
Ok(optval as u32)
184+
}
185+
133186
pub fn accept(listener: &net::TcpListener) -> io::Result<(net::TcpStream, SocketAddr)> {
134187
let mut addr: MaybeUninit<libc::sockaddr_storage> = MaybeUninit::uninit();
135188
let mut length = size_of::<libc::sockaddr_storage>() as libc::socklen_t;

src/sys/windows/tcp.rs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::io;
22
use std::mem::size_of;
33
use std::net::{self, SocketAddr, SocketAddrV4, SocketAddrV6};
44
use std::time::Duration;
5+
use std::convert::TryInto;
56
use std::os::windows::io::FromRawSocket;
67
use std::os::windows::raw::SOCKET as StdSocket; // winapi uses usize, stdlib uses u32/u64.
78

@@ -12,7 +13,7 @@ use winapi::shared::ws2ipdef::SOCKADDR_IN6_LH;
1213
use winapi::shared::minwindef::{BOOL, TRUE, FALSE};
1314
use winapi::um::winsock2::{
1415
self, closesocket, linger, setsockopt, getsockopt, getsockname, PF_INET, PF_INET6, SOCKET, SOCKET_ERROR,
15-
SOCK_STREAM, SOL_SOCKET, SO_LINGER, SO_REUSEADDR,
16+
SOCK_STREAM, SOL_SOCKET, SO_LINGER, SO_REUSEADDR, SO_RCVBUF, SO_SNDBUF,
1617
};
1718

1819
use crate::sys::windows::net::{init, new_socket, socket_addr};
@@ -149,6 +150,66 @@ pub(crate) fn set_linger(socket: TcpSocket, dur: Option<Duration>) -> io::Result
149150
}
150151
}
151152

153+
154+
pub(crate) fn set_recv_buffer_size(socket: TcpSocket, size: u32) -> io::Result<()> {
155+
let size = size.try_into().ok().unwrap_or_else(i32::max_value);
156+
match unsafe { setsockopt(
157+
socket,
158+
SOL_SOCKET,
159+
SO_RCVBUF,
160+
&size as *const _ as *const c_char,
161+
size_of::<c_int>() as c_int
162+
) } {
163+
SOCKET_ERROR => Err(io::Error::last_os_error()),
164+
_ => Ok(()),
165+
}
166+
}
167+
168+
pub(crate) fn get_recv_buffer_size(socket: TcpSocket) -> io::Result<u32> {
169+
let mut optval: c_int = 0;
170+
let mut optlen = size_of::<c_int>() as c_int;
171+
match unsafe { getsockopt(
172+
socket,
173+
SOL_SOCKET,
174+
SO_RCVBUF,
175+
&mut optval as *mut _ as *mut _,
176+
&mut optlen as *mut _,
177+
) } {
178+
SOCKET_ERROR => Err(io::Error::last_os_error()),
179+
_ => Ok(optval as u32),
180+
}
181+
}
182+
183+
pub(crate) fn set_send_buffer_size(socket: TcpSocket, size: u32) -> io::Result<()> {
184+
let size = size.try_into().ok().unwrap_or_else(i32::max_value);
185+
match unsafe { setsockopt(
186+
socket,
187+
SOL_SOCKET,
188+
SO_SNDBUF,
189+
&size as *const _ as *const c_char,
190+
size_of::<c_int>() as c_int
191+
) } {
192+
SOCKET_ERROR => Err(io::Error::last_os_error()),
193+
_ => Ok(()),
194+
}
195+
}
196+
197+
pub(crate) fn get_send_buffer_size(socket: TcpSocket) -> io::Result<u32> {
198+
let mut optval: c_int = 0;
199+
let mut optlen = size_of::<c_int>() as c_int;
200+
match unsafe { getsockopt(
201+
socket,
202+
SOL_SOCKET,
203+
SO_SNDBUF,
204+
&mut optval as *mut _ as *mut _,
205+
&mut optlen as *mut _,
206+
) } {
207+
SOCKET_ERROR => Err(io::Error::last_os_error()),
208+
_ => Ok(optval as u32),
209+
}
210+
}
211+
212+
152213
pub(crate) fn accept(listener: &net::TcpListener) -> io::Result<(net::TcpStream, SocketAddr)> {
153214
// The non-blocking state of `listener` is inherited. See
154215
// https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept#remarks.

tests/tcp_socket.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#![cfg(all(feature = "os-poll", feature = "tcp"))]
22

33
use mio::net::TcpSocket;
4+
use std::io;
45

56
#[test]
67
fn is_send_and_sync() {
@@ -56,3 +57,47 @@ fn get_localaddr() {
5657

5758
let _ = socket.listen(128).unwrap();
5859
}
60+
61+
#[test]
62+
fn send_buffer_size_roundtrips() {
63+
test_buffer_sizes(
64+
TcpSocket::set_send_buffer_size,
65+
TcpSocket::get_send_buffer_size,
66+
)
67+
}
68+
69+
#[test]
70+
fn recv_buffer_size_roundtrips() {
71+
test_buffer_sizes(
72+
TcpSocket::set_recv_buffer_size,
73+
TcpSocket::get_recv_buffer_size,
74+
)
75+
}
76+
77+
// Helper for testing send/recv buffer size.
78+
fn test_buffer_sizes(
79+
set: impl Fn(&TcpSocket, u32) -> io::Result<()>,
80+
get: impl Fn(&TcpSocket) -> io::Result<u32>,
81+
) {
82+
let test = |size: u32| {
83+
println!("testing buffer size: {}", size);
84+
let socket = TcpSocket::new_v4().unwrap();
85+
set(&socket, size).unwrap();
86+
// Note that this doesn't assert that the values are equal: on Linux,
87+
// the kernel doubles the requested buffer size, and returns the doubled
88+
// value from `getsockopt`. As per `man socket(7)`:
89+
// > Sets or gets the maximum socket send buffer in bytes. The
90+
// > kernel doubles this value (to allow space for bookkeeping
91+
// > overhead) when it is set using setsockopt(2), and this doubled
92+
// > value is returned by getsockopt(2).
93+
//
94+
// Additionally, the buffer size may be clamped above a minimum value,
95+
// and this minimum value is OS-dependent.
96+
let actual = get(&socket).unwrap();
97+
assert!(actual >= size, "\tactual: {}\n\texpected: {}", actual, size);
98+
};
99+
100+
test(256);
101+
test(4096);
102+
test(65512);
103+
}

0 commit comments

Comments
 (0)