quiche_apps/
sendto.rs

1// Copyright (C) 2021, Cloudflare, Inc.
2// All rights reserved.
3//
4// Redistribution and use in source and binary forms, with or without
5// modification, are permitted provided that the following conditions are
6// met:
7//
8//     * Redistributions of source code must retain the above copyright notice,
9//       this list of conditions and the following disclaimer.
10//
11//     * Redistributions in binary form must reproduce the above copyright
12//       notice, this list of conditions and the following disclaimer in the
13//       documentation and/or other materials provided with the distribution.
14//
15// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
16// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
17// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
19// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
27use std::cmp;
28
29use std::io;
30
31/// For Linux, try to detect GSO is available.
32#[cfg(target_os = "linux")]
33pub fn detect_gso(socket: &mio::net::UdpSocket, segment_size: usize) -> bool {
34    use nix::sys::socket::setsockopt;
35    use nix::sys::socket::sockopt::UdpGsoSegment;
36    use std::os::unix::io::AsRawFd;
37
38    setsockopt(socket.as_raw_fd(), UdpGsoSegment, &(segment_size as i32)).is_ok()
39}
40
41/// For non-Linux, there is no GSO support.
42#[cfg(not(target_os = "linux"))]
43pub fn detect_gso(_socket: &mio::net::UdpSocket, _segment_size: usize) -> bool {
44    false
45}
46
47/// Send packets using sendmsg() with GSO.
48#[cfg(target_os = "linux")]
49fn send_to_gso_pacing(
50    socket: &mio::net::UdpSocket, buf: &[u8], send_info: &quiche::SendInfo,
51    segment_size: usize,
52) -> io::Result<usize> {
53    use nix::sys::socket::sendmsg;
54    use nix::sys::socket::ControlMessage;
55    use nix::sys::socket::MsgFlags;
56    use nix::sys::socket::SockaddrStorage;
57    use std::io::IoSlice;
58    use std::os::unix::io::AsRawFd;
59
60    let iov = [IoSlice::new(buf)];
61    let segment_size = segment_size as u16;
62    let dst = SockaddrStorage::from(send_info.to);
63    let sockfd = socket.as_raw_fd();
64
65    // GSO option.
66    let cmsg_gso = ControlMessage::UdpGsoSegments(&segment_size);
67
68    // Pacing option.
69    let send_time = std_time_to_u64(&send_info.at);
70    let cmsg_txtime = ControlMessage::TxTime(&send_time);
71
72    match sendmsg(
73        sockfd,
74        &iov,
75        &[cmsg_gso, cmsg_txtime],
76        MsgFlags::empty(),
77        Some(&dst),
78    ) {
79        Ok(v) => Ok(v),
80        Err(e) => Err(e.into()),
81    }
82}
83
84/// For non-Linux platforms.
85#[cfg(not(target_os = "linux"))]
86fn send_to_gso_pacing(
87    _socket: &mio::net::UdpSocket, _buf: &[u8], _send_info: &quiche::SendInfo,
88    _segment_size: usize,
89) -> io::Result<usize> {
90    panic!("send_to_gso() should not be called on non-linux platforms");
91}
92
93/// A wrapper function of send_to().
94///
95/// When GSO and SO_TXTIME are enabled, send packets using send_to_gso().
96/// Otherwise, send packets using socket.send_to().
97pub fn send_to(
98    socket: &mio::net::UdpSocket, buf: &[u8], send_info: &quiche::SendInfo,
99    segment_size: usize, pacing: bool, enable_gso: bool,
100) -> io::Result<usize> {
101    if pacing && enable_gso {
102        match send_to_gso_pacing(socket, buf, send_info, segment_size) {
103            Ok(v) => {
104                return Ok(v);
105            },
106            Err(e) => {
107                return Err(e);
108            },
109        }
110    }
111
112    let mut off = 0;
113    let mut left = buf.len();
114    let mut written = 0;
115
116    while left > 0 {
117        let pkt_len = cmp::min(left, segment_size);
118
119        match socket.send_to(&buf[off..off + pkt_len], send_info.to) {
120            Ok(v) => {
121                written += v;
122            },
123            Err(e) => return Err(e),
124        }
125
126        off += pkt_len;
127        left -= pkt_len;
128    }
129
130    Ok(written)
131}
132
133#[cfg(target_os = "linux")]
134fn std_time_to_u64(time: &std::time::Instant) -> u64 {
135    const NANOS_PER_SEC: u64 = 1_000_000_000;
136
137    const INSTANT_ZERO: std::time::Instant =
138        unsafe { std::mem::transmute(std::time::UNIX_EPOCH) };
139
140    let raw_time = time.duration_since(INSTANT_ZERO);
141
142    let sec = raw_time.as_secs();
143    let nsec = raw_time.subsec_nanos();
144
145    sec * NANOS_PER_SEC + nsec as u64
146}