Skip to main content

quiche_server/
quiche-server.rs

1// Copyright (C) 2020, 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
27#[macro_use]
28extern crate log;
29
30use std::io;
31
32use std::net;
33
34use std::io::prelude::*;
35
36use std::collections::HashMap;
37
38use std::convert::TryFrom;
39
40use std::rc::Rc;
41
42use std::cell::RefCell;
43
44use ring::rand::*;
45
46use quiche_apps::args::*;
47
48use quiche_apps::common::*;
49
50use quiche_apps::sendto::*;
51
52const MAX_BUF_SIZE: usize = 65507;
53
54const MAX_DATAGRAM_SIZE: usize = 1350;
55
56fn main() {
57    let mut buf = [0; MAX_BUF_SIZE];
58    let mut out = [0; MAX_BUF_SIZE];
59    let mut pacing = false;
60
61    env_logger::builder().format_timestamp_nanos().init();
62
63    // Parse CLI parameters.
64    let docopt = docopt::Docopt::new(SERVER_USAGE).unwrap();
65    let conn_args = CommonArgs::with_docopt(&docopt);
66    let args = ServerArgs::with_docopt(&docopt);
67
68    // Setup the event loop.
69    let mut poll = mio::Poll::new().unwrap();
70    let mut events = mio::Events::with_capacity(1024);
71
72    // Create the UDP listening socket, and register it with the event loop.
73    let mut socket =
74        mio::net::UdpSocket::bind(args.listen.parse().unwrap()).unwrap();
75
76    // Set SO_TXTIME socket option on the listening UDP socket for pacing
77    // outgoing packets.
78    if !args.disable_pacing {
79        match set_txtime_sockopt(&socket) {
80            Ok(_) => {
81                pacing = true;
82                debug!("successfully set SO_TXTIME socket option");
83            },
84            Err(e) => debug!("setsockopt failed {e:?}"),
85        };
86    }
87
88    info!("listening on {:}", socket.local_addr().unwrap());
89
90    poll.registry()
91        .register(&mut socket, mio::Token(0), mio::Interest::READABLE)
92        .unwrap();
93
94    let max_datagram_size = MAX_DATAGRAM_SIZE;
95    let enable_gso = if args.disable_gso {
96        false
97    } else {
98        detect_gso(&socket, max_datagram_size)
99    };
100
101    trace!("GSO detected: {enable_gso}");
102
103    // Create the configuration for the QUIC connections.
104    let mut config = quiche::Config::new(quiche::PROTOCOL_VERSION).unwrap();
105
106    config.load_cert_chain_from_pem_file(&args.cert).unwrap();
107    config.load_priv_key_from_pem_file(&args.key).unwrap();
108
109    config.set_application_protos(&conn_args.alpns).unwrap();
110
111    config.discover_pmtu(args.enable_pmtud);
112    config.set_initial_rtt(conn_args.initial_rtt);
113    config.set_max_idle_timeout(conn_args.idle_timeout);
114    config.set_max_recv_udp_payload_size(max_datagram_size);
115    config.set_max_send_udp_payload_size(max_datagram_size);
116    config.set_initial_max_data(conn_args.max_data);
117    config.set_initial_max_stream_data_bidi_local(conn_args.max_stream_data);
118    config.set_initial_max_stream_data_bidi_remote(conn_args.max_stream_data);
119    config.set_initial_max_stream_data_uni(conn_args.max_stream_data);
120    config.set_initial_max_streams_bidi(conn_args.max_streams_bidi);
121    config.set_initial_max_streams_uni(conn_args.max_streams_uni);
122    config.set_disable_active_migration(!conn_args.enable_active_migration);
123    config.set_active_connection_id_limit(conn_args.max_active_cids);
124    config.set_initial_congestion_window_packets(
125        usize::try_from(conn_args.initial_cwnd_packets).unwrap(),
126    );
127
128    config.set_max_connection_window(conn_args.max_window);
129    config.set_max_stream_window(conn_args.max_stream_window);
130
131    config.enable_pacing(pacing);
132
133    let mut keylog = None;
134
135    if let Some(keylog_path) = std::env::var_os("SSLKEYLOGFILE") {
136        let file = std::fs::OpenOptions::new()
137            .create(true)
138            .append(true)
139            .open(keylog_path)
140            .unwrap();
141
142        keylog = Some(file);
143
144        config.log_keys();
145    }
146
147    if conn_args.early_data {
148        config.enable_early_data();
149    }
150
151    if conn_args.no_grease {
152        config.grease(false);
153    }
154
155    config
156        .set_cc_algorithm_name(&conn_args.cc_algorithm)
157        .unwrap();
158
159    if conn_args.disable_hystart {
160        config.enable_hystart(false);
161    }
162
163    if conn_args.dgrams_enabled {
164        config.enable_dgram(true, 1000, 1000);
165    }
166
167    let rng = SystemRandom::new();
168    let conn_id_seed =
169        ring::hmac::Key::generate(ring::hmac::HMAC_SHA256, &rng).unwrap();
170
171    let mut next_client_id = 0;
172    let mut clients_ids = ClientIdMap::new();
173    let mut clients = ClientMap::new();
174
175    let mut pkt_count = 0;
176
177    let mut continue_write = false;
178
179    let local_addr = socket.local_addr().unwrap();
180
181    loop {
182        // Find the shorter timeout from all the active connections.
183        //
184        // TODO: use event loop that properly supports timers
185        let timeout = match continue_write {
186            true => Some(std::time::Duration::from_secs(0)),
187
188            false => clients.values().filter_map(|c| c.conn.timeout()).min(),
189        };
190
191        let mut poll_res = poll.poll(&mut events, timeout);
192        while let Err(e) = poll_res.as_ref() {
193            if e.kind() == std::io::ErrorKind::Interrupted {
194                trace!("mio poll() call failed, retrying: {e:?}");
195                poll_res = poll.poll(&mut events, timeout);
196            } else {
197                panic!("mio poll() call failed fatally: {e:?}");
198            }
199        }
200
201        // Read incoming UDP packets from the socket and feed them to quiche,
202        // until there are no more packets to read.
203        'read: loop {
204            // If the event loop reported no events, it means that the timeout
205            // has expired, so handle it without attempting to read packets. We
206            // will then proceed with the send loop.
207            if events.is_empty() && !continue_write {
208                trace!("timed out");
209
210                clients.values_mut().for_each(|c| c.conn.on_timeout());
211
212                break 'read;
213            }
214
215            let (len, from) = match socket.recv_from(&mut buf) {
216                Ok(v) => v,
217
218                Err(e) => {
219                    // There are no more UDP packets to read, so end the read
220                    // loop.
221                    if e.kind() == std::io::ErrorKind::WouldBlock {
222                        trace!("recv() would block");
223                        break 'read;
224                    }
225
226                    panic!("recv() failed: {e:?}");
227                },
228            };
229
230            trace!("got {len} bytes from {from} to {local_addr}");
231
232            let pkt_buf = &mut buf[..len];
233
234            if let Some(target_path) = conn_args.dump_packet_path.as_ref() {
235                let path = format!("{target_path}/{pkt_count}.pkt");
236
237                if let Ok(f) = std::fs::File::create(path) {
238                    let mut f = std::io::BufWriter::new(f);
239                    f.write_all(pkt_buf).ok();
240                }
241            }
242
243            pkt_count += 1;
244
245            // Parse the QUIC packet's header.
246            let hdr = match quiche::Header::from_slice(
247                pkt_buf,
248                quiche::MAX_CONN_ID_LEN,
249            ) {
250                Ok(v) => v,
251
252                Err(e) => {
253                    error!("Parsing packet header failed: {e:?}");
254                    continue 'read;
255                },
256            };
257
258            trace!("got packet {hdr:?}");
259
260            let conn_id = if !cfg!(feature = "fuzzing") {
261                let conn_id = ring::hmac::sign(&conn_id_seed, &hdr.dcid);
262                let conn_id = &conn_id.as_ref()[..quiche::MAX_CONN_ID_LEN];
263                conn_id.to_vec().into()
264            } else {
265                // When fuzzing use an all zero connection ID.
266                [0; quiche::MAX_CONN_ID_LEN].to_vec().into()
267            };
268
269            // Lookup a connection based on the packet's connection ID. If there
270            // is no connection matching, create a new one.
271            let client = if !clients_ids.contains_key(&hdr.dcid) &&
272                !clients_ids.contains_key(&conn_id)
273            {
274                if hdr.ty != quiche::Type::Initial {
275                    error!("Packet is not Initial");
276                    continue 'read;
277                }
278
279                if !quiche::version_is_supported(hdr.version) {
280                    warn!("Doing version negotiation");
281
282                    let len =
283                        quiche::negotiate_version(&hdr.scid, &hdr.dcid, &mut out)
284                            .unwrap();
285
286                    let out = &out[..len];
287
288                    if let Err(e) = socket.send_to(out, from) {
289                        if e.kind() == std::io::ErrorKind::WouldBlock {
290                            trace!("send() would block");
291                            break;
292                        }
293
294                        panic!("send() failed: {e:?}");
295                    }
296                    continue 'read;
297                }
298
299                let mut scid = [0; quiche::MAX_CONN_ID_LEN];
300                scid.copy_from_slice(&conn_id);
301
302                let mut odcid = None;
303
304                if !args.no_retry {
305                    // Token is always present in Initial packets.
306                    let token = hdr.token.as_ref().unwrap();
307
308                    // Do stateless retry if the client didn't send a token.
309                    if token.is_empty() {
310                        warn!("Doing stateless retry");
311
312                        let scid = quiche::ConnectionId::from_ref(&scid);
313                        let new_token = mint_token(&hdr, &from);
314
315                        let len = quiche::retry(
316                            &hdr.scid,
317                            &hdr.dcid,
318                            &scid,
319                            &new_token,
320                            hdr.version,
321                            &mut out,
322                        )
323                        .unwrap();
324
325                        let out = &out[..len];
326
327                        if let Err(e) = socket.send_to(out, from) {
328                            if e.kind() == std::io::ErrorKind::WouldBlock {
329                                trace!("send() would block");
330                                break;
331                            }
332
333                            panic!("send() failed: {e:?}");
334                        }
335                        continue 'read;
336                    }
337
338                    odcid = validate_token(&from, token);
339
340                    // The token was not valid, meaning the retry failed, so
341                    // drop the packet.
342                    if odcid.is_none() {
343                        error!("Invalid address validation token");
344                        continue;
345                    }
346
347                    if scid.len() != hdr.dcid.len() {
348                        error!("Invalid destination connection ID");
349                        continue 'read;
350                    }
351
352                    // Reuse the source connection ID we sent in the Retry
353                    // packet, instead of changing it again.
354                    scid.copy_from_slice(&hdr.dcid);
355                }
356
357                let scid = quiche::ConnectionId::from_vec(scid.to_vec());
358
359                debug!("New connection: dcid={:?} scid={:?}", hdr.dcid, scid);
360
361                #[allow(unused_mut)]
362                let mut conn = quiche::accept(
363                    &scid,
364                    odcid.as_ref(),
365                    local_addr,
366                    from,
367                    &mut config,
368                )
369                .unwrap();
370
371                if let Some(keylog) = &mut keylog {
372                    if let Ok(keylog) = keylog.try_clone() {
373                        conn.set_keylog(Box::new(keylog));
374                    }
375                }
376
377                // Only bother with qlog if the user specified it.
378                #[cfg(feature = "qlog")]
379                {
380                    if let Some(dir) = std::env::var_os("QLOGDIR") {
381                        let id = format!("{:?}", &scid);
382                        let writer = make_qlog_writer(&dir, "server", &id);
383
384                        conn.set_qlog(
385                            std::boxed::Box::new(writer),
386                            "quiche-server qlog".to_string(),
387                            format!("{} id={}", "quiche-server qlog", id),
388                        );
389                    }
390                }
391
392                let client_id = next_client_id;
393
394                let client = Client {
395                    conn,
396                    http_conn: None,
397                    client_id,
398                    partial_requests: HashMap::new(),
399                    partial_responses: HashMap::new(),
400                    app_proto_selected: false,
401                    max_datagram_size,
402                    loss_rate: 0.0,
403                    max_send_burst: MAX_BUF_SIZE,
404                };
405
406                clients.insert(client_id, client);
407                clients_ids.insert(scid.clone(), client_id);
408
409                next_client_id += 1;
410
411                clients.get_mut(&client_id).unwrap()
412            } else {
413                let cid = match clients_ids.get(&hdr.dcid) {
414                    Some(v) => v,
415
416                    None => clients_ids.get(&conn_id).unwrap(),
417                };
418
419                clients.get_mut(cid).unwrap()
420            };
421
422            let recv_info = quiche::RecvInfo {
423                to: local_addr,
424                from,
425            };
426
427            // Process potentially coalesced packets.
428            let read = match client.conn.recv(pkt_buf, recv_info) {
429                Ok(v) => v,
430
431                Err(e) => {
432                    error!("{} recv failed: {:?}", client.conn.trace_id(), e);
433                    continue 'read;
434                },
435            };
436
437            trace!("{} processed {} bytes", client.conn.trace_id(), read);
438
439            // Create a new application protocol session as soon as the QUIC
440            // connection is established.
441            if !client.app_proto_selected &&
442                (client.conn.is_in_early_data() ||
443                    client.conn.is_established())
444            {
445                // At this stage the ALPN negotiation succeeded and selected a
446                // single application protocol name. We'll use this to construct
447                // the correct type of HttpConn but `application_proto()`
448                // returns a slice, so we have to convert it to a str in order
449                // to compare to our lists of protocols. We `unwrap()` because
450                // we need the value and if something fails at this stage, there
451                // is not much anyone can do to recover.
452                let app_proto = client.conn.application_proto();
453
454                #[allow(clippy::box_default)]
455                if alpns::HTTP_09.contains(&app_proto) {
456                    client.http_conn = Some(Box::<Http09Conn>::default());
457
458                    client.app_proto_selected = true;
459                } else if alpns::HTTP_3.contains(&app_proto) {
460                    let dgram_sender = if conn_args.dgrams_enabled {
461                        Some(Http3DgramSender::new(
462                            conn_args.dgram_count,
463                            conn_args.dgram_data.clone(),
464                            1,
465                        ))
466                    } else {
467                        None
468                    };
469
470                    client.http_conn = match Http3Conn::with_conn(
471                        &mut client.conn,
472                        conn_args.max_field_section_size,
473                        conn_args.qpack_max_table_capacity,
474                        conn_args.qpack_blocked_streams,
475                        dgram_sender,
476                        Rc::new(RefCell::new(stdout_sink)),
477                    ) {
478                        Ok(v) => Some(v),
479
480                        Err(e) => {
481                            trace!("{} {}", client.conn.trace_id(), e);
482                            None
483                        },
484                    };
485
486                    client.app_proto_selected = true;
487                }
488
489                // Update max_datagram_size after connection established.
490                client.max_datagram_size =
491                    client.conn.max_send_udp_payload_size();
492            }
493
494            if let Some(http_conn) = client.http_conn.as_mut() {
495                let conn = &mut client.conn;
496                let partial_responses = &mut client.partial_responses;
497
498                // Visit all writable response streams to send any remaining HTTP
499                // content.
500                for stream_id in writable_response_streams(conn) {
501                    http_conn.handle_writable(conn, partial_responses, stream_id);
502                }
503
504                if http_conn
505                    .handle_requests(
506                        conn,
507                        &mut client.partial_requests,
508                        partial_responses,
509                        &args.root,
510                        &args.index,
511                        &mut buf,
512                    )
513                    .is_err()
514                {
515                    continue 'read;
516                }
517            }
518
519            handle_path_events(client);
520
521            // See whether source Connection IDs have been retired.
522            while let Some(retired_scid) = client.conn.retired_scid_next() {
523                info!("Retiring source CID {retired_scid:?}");
524                clients_ids.remove(&retired_scid);
525            }
526
527            // Provides as many CIDs as possible.
528            while client.conn.scids_left() > 0 {
529                let (scid, reset_token) = generate_cid_and_reset_token(&rng);
530                if client.conn.new_scid(&scid, reset_token, false).is_err() {
531                    break;
532                }
533
534                clients_ids.insert(scid, client.client_id);
535            }
536        }
537
538        // Generate outgoing QUIC packets for all active connections and send
539        // them on the UDP socket, until quiche reports that there are no more
540        // packets to be sent.
541        continue_write = false;
542        for client in clients.values_mut() {
543            // Reduce max_send_burst by 25% if loss is increasing more than 0.1%.
544            let loss_rate =
545                client.conn.stats().lost as f64 / client.conn.stats().sent as f64;
546            if loss_rate > client.loss_rate + 0.001 {
547                client.max_send_burst = client.max_send_burst / 4 * 3;
548                // Minimum bound of 10xMSS.
549                client.max_send_burst =
550                    client.max_send_burst.max(client.max_datagram_size * 10);
551                client.loss_rate = loss_rate;
552            }
553
554            let max_send_burst =
555                client.conn.send_quantum().min(client.max_send_burst) /
556                    client.max_datagram_size *
557                    client.max_datagram_size;
558            let mut total_write = 0;
559            let mut dst_info = None;
560
561            while total_write < max_send_burst {
562                let (write, send_info) = match client
563                    .conn
564                    .send(&mut out[total_write..max_send_burst])
565                {
566                    Ok(v) => v,
567
568                    Err(quiche::Error::Done) => {
569                        trace!("{} done writing", client.conn.trace_id());
570                        break;
571                    },
572
573                    Err(e) => {
574                        error!("{} send failed: {:?}", client.conn.trace_id(), e);
575
576                        client.conn.close(false, 0x1, b"fail").ok();
577                        break;
578                    },
579                };
580
581                total_write += write;
582
583                // Use the first packet time to send, not the last.
584                let _ = dst_info.get_or_insert(send_info);
585
586                if write < client.max_datagram_size {
587                    continue_write = true;
588                    break;
589                }
590            }
591
592            if total_write == 0 || dst_info.is_none() {
593                continue;
594            }
595
596            if let Err(e) = send_to(
597                &socket,
598                &out[..total_write],
599                &dst_info.unwrap(),
600                client.max_datagram_size,
601                pacing,
602                enable_gso,
603            ) {
604                if e.kind() == std::io::ErrorKind::WouldBlock {
605                    trace!("send() would block");
606                    break;
607                }
608
609                panic!("send_to() failed: {e:?}");
610            }
611
612            trace!(
613                "{} written {total_write} bytes with {dst_info:?}",
614                client.conn.trace_id()
615            );
616
617            if total_write >= max_send_burst {
618                trace!("{} pause writing", client.conn.trace_id(),);
619                continue_write = true;
620                break;
621            }
622        }
623
624        // Garbage collect closed connections.
625        clients.retain(|_, ref mut c| {
626            trace!("Collecting garbage");
627
628            if c.conn.is_closed() {
629                info!(
630                    "{} connection collected {:?} {:?}",
631                    c.conn.trace_id(),
632                    c.conn.stats(),
633                    c.conn.path_stats().collect::<Vec<quiche::PathStats>>()
634                );
635
636                for id in c.conn.source_ids() {
637                    let id_owned = id.clone().into_owned();
638                    clients_ids.remove(&id_owned);
639                }
640            }
641
642            !c.conn.is_closed()
643        });
644    }
645}
646
647/// Generate a stateless retry token.
648///
649/// The token includes the static string `"quiche"` followed by the IP address
650/// of the client and by the original destination connection ID generated by the
651/// client.
652///
653/// Note that this function is only an example and doesn't do any cryptographic
654/// authenticate of the token. *It should not be used in production system*.
655fn mint_token(hdr: &quiche::Header, src: &net::SocketAddr) -> Vec<u8> {
656    let mut token = Vec::new();
657
658    token.extend_from_slice(b"quiche");
659
660    let addr = match src.ip() {
661        std::net::IpAddr::V4(a) => a.octets().to_vec(),
662        std::net::IpAddr::V6(a) => a.octets().to_vec(),
663    };
664
665    token.extend_from_slice(&addr);
666    token.extend_from_slice(&hdr.dcid);
667
668    token
669}
670
671/// Validates a stateless retry token.
672///
673/// This checks that the ticket includes the `"quiche"` static string, and that
674/// the client IP address matches the address stored in the ticket.
675///
676/// Note that this function is only an example and doesn't do any cryptographic
677/// authenticate of the token. *It should not be used in production system*.
678fn validate_token<'a>(
679    src: &net::SocketAddr, token: &'a [u8],
680) -> Option<quiche::ConnectionId<'a>> {
681    if token.len() < 6 {
682        return None;
683    }
684
685    if &token[..6] != b"quiche" {
686        return None;
687    }
688
689    let token = &token[6..];
690
691    let addr = match src.ip() {
692        std::net::IpAddr::V4(a) => a.octets().to_vec(),
693        std::net::IpAddr::V6(a) => a.octets().to_vec(),
694    };
695
696    if token.len() < addr.len() || &token[..addr.len()] != addr.as_slice() {
697        return None;
698    }
699
700    Some(quiche::ConnectionId::from_ref(&token[addr.len()..]))
701}
702
703fn handle_path_events(client: &mut Client) {
704    while let Some(qe) = client.conn.path_event_next() {
705        match qe {
706            quiche::PathEvent::New(local_addr, peer_addr) => {
707                info!(
708                    "{} Seen new path ({}, {})",
709                    client.conn.trace_id(),
710                    local_addr,
711                    peer_addr
712                );
713
714                // Directly probe the new path.
715                client
716                    .conn
717                    .probe_path(local_addr, peer_addr)
718                    .expect("cannot probe");
719            },
720
721            quiche::PathEvent::Validated(local_addr, peer_addr) => {
722                info!(
723                    "{} Path ({}, {}) is now validated",
724                    client.conn.trace_id(),
725                    local_addr,
726                    peer_addr
727                );
728            },
729
730            quiche::PathEvent::FailedValidation(local_addr, peer_addr) => {
731                info!(
732                    "{} Path ({}, {}) failed validation",
733                    client.conn.trace_id(),
734                    local_addr,
735                    peer_addr
736                );
737            },
738
739            quiche::PathEvent::Closed(local_addr, peer_addr) => {
740                info!(
741                    "{} Path ({}, {}) is now closed and unusable",
742                    client.conn.trace_id(),
743                    local_addr,
744                    peer_addr
745                );
746            },
747
748            quiche::PathEvent::ReusedSourceConnectionId(cid_seq, old, new) => {
749                info!(
750                    "{} Peer reused cid seq {} (initially {:?}) on {:?}",
751                    client.conn.trace_id(),
752                    cid_seq,
753                    old,
754                    new
755                );
756            },
757
758            quiche::PathEvent::PeerMigrated(local_addr, peer_addr) => {
759                info!(
760                    "{} Connection migrated to ({}, {})",
761                    client.conn.trace_id(),
762                    local_addr,
763                    peer_addr
764                );
765            },
766        }
767    }
768}
769
770/// Set SO_TXTIME socket option.
771///
772/// This socket option is set to send to kernel the outgoing UDP
773/// packet transmission time in the sendmsg syscall.
774///
775/// Note that this socket option is set only on linux platforms.
776#[cfg(target_os = "linux")]
777fn set_txtime_sockopt(sock: &mio::net::UdpSocket) -> io::Result<()> {
778    use nix::sys::socket::setsockopt;
779    use nix::sys::socket::sockopt::TxTime;
780    use std::os::unix::io::AsFd;
781
782    let config = nix::libc::sock_txtime {
783        clockid: libc::CLOCK_MONOTONIC,
784        flags: 0,
785    };
786
787    setsockopt(&sock.as_fd(), TxTime, &config)?;
788
789    Ok(())
790}
791
792#[cfg(not(target_os = "linux"))]
793fn set_txtime_sockopt(_: &mio::net::UdpSocket) -> io::Result<()> {
794    use std::io::Error;
795
796    Err(Error::other("Not supported on this platform"))
797}