Skip to content

Commit 490e979

Browse files
bors[bot]Gleb Pomykalov
andauthored
Merge #1209
1209: Support UDP GSO and GRO on linux r=asomers a=glebpom This PR implements support for UDP GSO and GRO on Linux. It provides the way to send/receive UDP payloads bigger than interface MTU. The goal is to improve UDP performance. GSO was introduced in Linux 4.18, GRO in 5.3 Co-authored-by: Gleb Pomykalov <[email protected]>
2 parents 4707736 + b1b64ee commit 490e979

File tree

6 files changed

+170
-8
lines changed

6 files changed

+170
-8
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ This project adheres to [Semantic Versioning](http://semver.org/).
2121
- Derived `Ord`, `PartialOrd` for `unistd::Pid` (#[1189](https://github.com/nix-rust/nix/pull/1189))
2222
- Added `select::FdSet::fds` method to iterate over file descriptors in a set.
2323
([#1207](https://github.com/nix-rust/nix/pull/1207))
24+
- Added support for UDP generic segmentation offload (GSO) and generic
25+
receive offload (GRO) ([#1209](https://github.com/nix-rust/nix/pull/1209))
2426

2527
### Changed
2628
- Changed `fallocate` return type from `c_int` to `()` (#[1201](https://github.com/nix-rust/nix/pull/1201))

Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ exclude = [
1616
]
1717

1818
[dependencies]
19-
libc = { git = "https://github.com/rust-lang/libc/", features = [ "extra_traits" ] }
19+
libc = { version = "0.2.69", features = [ "extra_traits" ] }
2020
bitflags = "1.1"
2121
cfg-if = "0.1.10"
2222
void = "1.0.2"
@@ -29,6 +29,7 @@ bytes = "0.4.8"
2929
lazy_static = "1.2"
3030
rand = "0.6"
3131
tempfile = "3.0.5"
32+
semver = "0.9.0"
3233

3334
[target.'cfg(any(target_os = "android", target_os = "linux"))'.dev-dependencies]
3435
caps = "0.3.1"

src/sys/socket/mod.rs

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -463,6 +463,18 @@ pub enum ControlMessageOwned {
463463
target_os = "openbsd",
464464
))]
465465
Ipv4RecvDstAddr(libc::in_addr),
466+
467+
/// UDP Generic Receive Offload (GRO) allows receiving multiple UDP
468+
/// packets from a single sender.
469+
/// Fixed-size payloads are following one by one in a receive buffer.
470+
/// This Control Message indicates the size of all smaller packets,
471+
/// except, maybe, the last one.
472+
///
473+
/// `UdpGroSegment` socket option should be enabled on a socket
474+
/// to allow receiving GRO packets.
475+
#[cfg(target_os = "linux")]
476+
UdpGroSegments(u16),
477+
466478
/// Catch-all variant for unimplemented cmsg types.
467479
#[doc(hidden)]
468480
Unknown(UnknownCmsg),
@@ -546,6 +558,11 @@ impl ControlMessageOwned {
546558
let dl = ptr::read_unaligned(p as *const libc::in_addr);
547559
ControlMessageOwned::Ipv4RecvDstAddr(dl)
548560
},
561+
#[cfg(target_os = "linux")]
562+
(libc::SOL_UDP, libc::UDP_GRO) => {
563+
let gso_size: u16 = ptr::read_unaligned(p as *const _);
564+
ControlMessageOwned::UdpGroSegments(gso_size)
565+
},
549566
(_, _) => {
550567
let sl = slice::from_raw_parts(p, len);
551568
let ucmsg = UnknownCmsg(*header, Vec::<u8>::from(&sl[..]));
@@ -617,6 +634,16 @@ pub enum ControlMessage<'a> {
617634
))]
618635
AlgSetAeadAssoclen(&'a u32),
619636

637+
/// UDP GSO makes it possible for applications to generate network packets
638+
/// for a virtual MTU much greater than the real one.
639+
/// The length of the send data no longer matches the expected length on
640+
/// the wire.
641+
/// The size of the datagram payload as it should appear on the wire may be
642+
/// passed through this control message.
643+
/// Send buffer should consist of multiple fixed-size wire payloads
644+
/// following one by one, and the last, possibly smaller one.
645+
#[cfg(target_os = "linux")]
646+
UdpGsoSegments(&'a u16),
620647
}
621648

622649
// An opaque structure used to prevent cmsghdr from being a public type
@@ -687,6 +714,10 @@ impl<'a> ControlMessage<'a> {
687714
ControlMessage::AlgSetAeadAssoclen(len) => {
688715
len as *const _ as *const u8
689716
},
717+
#[cfg(target_os = "linux")]
718+
ControlMessage::UdpGsoSegments(gso_size) => {
719+
gso_size as *const _ as *const u8
720+
},
690721
};
691722
unsafe {
692723
ptr::copy_nonoverlapping(
@@ -719,6 +750,10 @@ impl<'a> ControlMessage<'a> {
719750
ControlMessage::AlgSetAeadAssoclen(len) => {
720751
mem::size_of_val(len)
721752
},
753+
#[cfg(target_os = "linux")]
754+
ControlMessage::UdpGsoSegments(gso_size) => {
755+
mem::size_of_val(gso_size)
756+
},
722757
}
723758
}
724759

@@ -730,7 +765,9 @@ impl<'a> ControlMessage<'a> {
730765
ControlMessage::ScmCredentials(_) => libc::SOL_SOCKET,
731766
#[cfg(any(target_os = "android", target_os = "linux"))]
732767
ControlMessage::AlgSetIv(_) | ControlMessage::AlgSetOp(_) |
733-
ControlMessage::AlgSetAeadAssoclen(_) => libc::SOL_ALG ,
768+
ControlMessage::AlgSetAeadAssoclen(_) => libc::SOL_ALG,
769+
#[cfg(target_os = "linux")]
770+
ControlMessage::UdpGsoSegments(_) => libc::SOL_UDP,
734771
}
735772
}
736773

@@ -752,6 +789,10 @@ impl<'a> ControlMessage<'a> {
752789
ControlMessage::AlgSetAeadAssoclen(_) => {
753790
libc::ALG_SET_AEAD_ASSOCLEN
754791
},
792+
#[cfg(target_os = "linux")]
793+
ControlMessage::UdpGsoSegments(_) => {
794+
libc::UDP_SEGMENT
795+
},
755796
}
756797
}
757798

src/sys/socket/sockopt.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,10 @@ sockopt_impl!(Both, Ipv4RecvIf, libc::IPPROTO_IP, libc::IP_RECVIF, bool);
308308
target_os = "openbsd",
309309
))]
310310
sockopt_impl!(Both, Ipv4RecvDstAddr, libc::IPPROTO_IP, libc::IP_RECVDSTADDR, bool);
311-
311+
#[cfg(target_os = "linux")]
312+
sockopt_impl!(Both, UdpGsoSegment, libc::SOL_UDP, libc::UDP_SEGMENT, libc::c_int);
313+
#[cfg(target_os = "linux")]
314+
sockopt_impl!(Both, UdpGroSegment, libc::IPPROTO_UDP, libc::UDP_GRO, bool);
312315

313316
#[cfg(any(target_os = "android", target_os = "linux"))]
314317
#[derive(Copy, Clone, Debug)]

test/sys/test_socket.rs

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,10 @@ mod recvfrom {
169169

170170
const MSG: &'static [u8] = b"Hello, World!";
171171

172-
fn sendrecv<F>(rsock: RawFd, ssock: RawFd, f: F) -> Option<SockAddr>
173-
where F: Fn(RawFd, &[u8], MsgFlags) -> Result<usize> + Send + 'static
172+
fn sendrecv<Fs, Fr>(rsock: RawFd, ssock: RawFd, f_send: Fs, mut f_recv: Fr) -> Option<SockAddr>
173+
where
174+
Fs: Fn(RawFd, &[u8], MsgFlags) -> Result<usize> + Send + 'static,
175+
Fr: FnMut(usize, Option<SockAddr>),
174176
{
175177
let mut buf: [u8; 13] = [0u8; 13];
176178
let mut l = 0;
@@ -179,12 +181,13 @@ mod recvfrom {
179181
let send_thread = thread::spawn(move || {
180182
let mut l = 0;
181183
while l < std::mem::size_of_val(MSG) {
182-
l += f(ssock, &MSG[l..], MsgFlags::empty()).unwrap();
184+
l += f_send(ssock, &MSG[l..], MsgFlags::empty()).unwrap();
183185
}
184186
});
185187

186188
while l < std::mem::size_of_val(MSG) {
187189
let (len, from_) = recvfrom(rsock, &mut buf[l..]).unwrap();
190+
f_recv(len, from_);
188191
from = from_;
189192
l += len;
190193
}
@@ -200,7 +203,7 @@ mod recvfrom {
200203
// Ignore from for stream sockets
201204
let _ = sendrecv(fd1, fd2, |s, m, flags| {
202205
send(s, m, flags)
203-
});
206+
}, |_, _| {});
204207
}
205208

206209
#[test]
@@ -222,10 +225,85 @@ mod recvfrom {
222225
).expect("send socket failed");
223226
let from = sendrecv(rsock, ssock, move |s, m, flags| {
224227
sendto(s, m, &sock_addr, flags)
225-
});
228+
},|_, _| {});
226229
// UDP sockets should set the from address
227230
assert_eq!(AddressFamily::Inet, from.unwrap().family());
228231
}
232+
233+
#[cfg(target_os = "linux")]
234+
mod udp_offload {
235+
use super::*;
236+
use nix::sys::uio::IoVec;
237+
use nix::sys::socket::sockopt::{UdpGroSegment, UdpGsoSegment};
238+
239+
#[test]
240+
pub fn gso() {
241+
require_kernel_version!(udp_offload::gso, ">= 4.18");
242+
243+
// In this test, we send the data and provide a GSO segment size.
244+
// Since we are sending the buffer of size 13, six UDP packets
245+
// with size 2 and two UDP packet with size 1 will be sent.
246+
let segment_size: u16 = 2;
247+
248+
let std_sa = SocketAddr::from_str("127.0.0.1:6791").unwrap();
249+
let inet_addr = InetAddr::from_std(&std_sa);
250+
let sock_addr = SockAddr::new_inet(inet_addr);
251+
let rsock = socket(AddressFamily::Inet,
252+
SockType::Datagram,
253+
SockFlag::empty(),
254+
None
255+
).unwrap();
256+
257+
setsockopt(rsock, UdpGsoSegment, &(segment_size as _))
258+
.expect("setsockopt UDP_SEGMENT failed");
259+
260+
bind(rsock, &sock_addr).unwrap();
261+
let ssock = socket(
262+
AddressFamily::Inet,
263+
SockType::Datagram,
264+
SockFlag::empty(),
265+
None,
266+
).expect("send socket failed");
267+
268+
let mut num_packets_received: i32 = 0;
269+
270+
sendrecv(rsock, ssock, move |s, m, flags| {
271+
let iov = [IoVec::from_slice(m)];
272+
let cmsg = ControlMessage::UdpGsoSegments(&segment_size);
273+
sendmsg(s, &iov, &[cmsg], flags, Some(&sock_addr))
274+
}, {
275+
let num_packets_received_ref = &mut num_packets_received;
276+
277+
move |len, _| {
278+
// check that we receive UDP packets with payload size
279+
// less or equal to segment size
280+
assert!(len <= segment_size as usize);
281+
*num_packets_received_ref += 1;
282+
}
283+
});
284+
285+
// Buffer size is 13, we will receive six packets of size 2,
286+
// and one packet of size 1.
287+
assert_eq!(7, num_packets_received);
288+
}
289+
290+
#[test]
291+
pub fn gro() {
292+
require_kernel_version!(udp_offload::gro, ">= 5.3");
293+
294+
// It's hard to guarantee receiving GRO packets. Just checking
295+
// that `setsockopt` doesn't fail with error
296+
297+
let rsock = socket(AddressFamily::Inet,
298+
SockType::Datagram,
299+
SockFlag::empty(),
300+
None
301+
).unwrap();
302+
303+
setsockopt(rsock, UdpGroSegment, &true)
304+
.expect("setsockopt UDP_GRO failed");
305+
}
306+
}
229307
}
230308

231309
// Test error handling of our recvmsg wrapper

test/test.rs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ extern crate rand;
1212
#[cfg(target_os = "freebsd")]
1313
extern crate sysctl;
1414
extern crate tempfile;
15+
extern crate semver;
1516

1617
cfg_if! {
1718
if #[cfg(any(target_os = "android", target_os = "linux"))] {
@@ -100,6 +101,42 @@ cfg_if! {
100101
}
101102
}
102103

104+
cfg_if! {
105+
if #[cfg(any(target_os = "android", target_os = "linux"))] {
106+
macro_rules! require_kernel_version {
107+
($name:expr, $version_requirement:expr) => {
108+
use ::std::io::Write;
109+
use semver::{Version, VersionReq};
110+
111+
let version_requirement = VersionReq::parse($version_requirement)
112+
.expect("Bad match_version provided");
113+
114+
let uname = nix::sys::utsname::uname();
115+
116+
let mut version = Version::parse(uname.release()).unwrap();
117+
118+
//Keep only numeric parts
119+
version.pre.clear();
120+
version.build.clear();
121+
122+
if !version_requirement.matches(&version) {
123+
let stderr = ::std::io::stderr();
124+
let mut handle = stderr.lock();
125+
126+
writeln!(handle,
127+
"Skip {} because kernel version `{}` doesn't match the requirement `{}`",
128+
stringify!($name), version, version_requirement).unwrap();
129+
return;
130+
}
131+
}
132+
}
133+
} else {
134+
macro_rules! require_kernel_version {
135+
($name:expr) => {}
136+
}
137+
}
138+
}
139+
103140
mod sys;
104141
mod test_dir;
105142
mod test_fcntl;

0 commit comments

Comments
 (0)