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 client.http_conn.is_some() {
495                let conn = &mut client.conn;
496                let http_conn = client.http_conn.as_mut().unwrap();
497                let partial_responses = &mut client.partial_responses;
498
499                // Visit all writable response streams to send any remaining HTTP
500                // content.
501                for stream_id in writable_response_streams(conn) {
502                    http_conn.handle_writable(conn, partial_responses, stream_id);
503                }
504
505                if http_conn
506                    .handle_requests(
507                        conn,
508                        &mut client.partial_requests,
509                        partial_responses,
510                        &args.root,
511                        &args.index,
512                        &mut buf,
513                    )
514                    .is_err()
515                {
516                    continue 'read;
517                }
518            }
519
520            handle_path_events(client);
521
522            // See whether source Connection IDs have been retired.
523            while let Some(retired_scid) = client.conn.retired_scid_next() {
524                info!("Retiring source CID {retired_scid:?}");
525                clients_ids.remove(&retired_scid);
526            }
527
528            // Provides as many CIDs as possible.
529            while client.conn.scids_left() > 0 {
530                let (scid, reset_token) = generate_cid_and_reset_token(&rng);
531                if client.conn.new_scid(&scid, reset_token, false).is_err() {
532                    break;
533                }
534
535                clients_ids.insert(scid, client.client_id);
536            }
537        }
538
539        // Generate outgoing QUIC packets for all active connections and send
540        // them on the UDP socket, until quiche reports that there are no more
541        // packets to be sent.
542        continue_write = false;
543        for client in clients.values_mut() {
544            // Reduce max_send_burst by 25% if loss is increasing more than 0.1%.
545            let loss_rate =
546                client.conn.stats().lost as f64 / client.conn.stats().sent as f64;
547            if loss_rate > client.loss_rate + 0.001 {
548                client.max_send_burst = client.max_send_burst / 4 * 3;
549                // Minimum bound of 10xMSS.
550                client.max_send_burst =
551                    client.max_send_burst.max(client.max_datagram_size * 10);
552                client.loss_rate = loss_rate;
553            }
554
555            let max_send_burst =
556                client.conn.send_quantum().min(client.max_send_burst) /
557                    client.max_datagram_size *
558                    client.max_datagram_size;
559            let mut total_write = 0;
560            let mut dst_info = None;
561
562            while total_write < max_send_burst {
563                let (write, send_info) = match client
564                    .conn
565                    .send(&mut out[total_write..max_send_burst])
566                {
567                    Ok(v) => v,
568
569                    Err(quiche::Error::Done) => {
570                        trace!("{} done writing", client.conn.trace_id());
571                        break;
572                    },
573
574                    Err(e) => {
575                        error!("{} send failed: {:?}", client.conn.trace_id(), e);
576
577                        client.conn.close(false, 0x1, b"fail").ok();
578                        break;
579                    },
580                };
581
582                total_write += write;
583
584                // Use the first packet time to send, not the last.
585                let _ = dst_info.get_or_insert(send_info);
586
587                if write < client.max_datagram_size {
588                    continue_write = true;
589                    break;
590                }
591            }
592
593            if total_write == 0 || dst_info.is_none() {
594                continue;
595            }
596
597            if let Err(e) = send_to(
598                &socket,
599                &out[..total_write],
600                &dst_info.unwrap(),
601                client.max_datagram_size,
602                pacing,
603                enable_gso,
604            ) {
605                if e.kind() == std::io::ErrorKind::WouldBlock {
606                    trace!("send() would block");
607                    break;
608                }
609
610                panic!("send_to() failed: {e:?}");
611            }
612
613            trace!(
614                "{} written {total_write} bytes with {dst_info:?}",
615                client.conn.trace_id()
616            );
617
618            if total_write >= max_send_burst {
619                trace!("{} pause writing", client.conn.trace_id(),);
620                continue_write = true;
621                break;
622            }
623        }
624
625        // Garbage collect closed connections.
626        clients.retain(|_, ref mut c| {
627            trace!("Collecting garbage");
628
629            if c.conn.is_closed() {
630                info!(
631                    "{} connection collected {:?} {:?}",
632                    c.conn.trace_id(),
633                    c.conn.stats(),
634                    c.conn.path_stats().collect::<Vec<quiche::PathStats>>()
635                );
636
637                for id in c.conn.source_ids() {
638                    let id_owned = id.clone().into_owned();
639                    clients_ids.remove(&id_owned);
640                }
641            }
642
643            !c.conn.is_closed()
644        });
645    }
646}
647
648/// Generate a stateless retry token.
649///
650/// The token includes the static string `"quiche"` followed by the IP address
651/// of the client and by the original destination connection ID generated by the
652/// client.
653///
654/// Note that this function is only an example and doesn't do any cryptographic
655/// authenticate of the token. *It should not be used in production system*.
656fn mint_token(hdr: &quiche::Header, src: &net::SocketAddr) -> Vec<u8> {
657    let mut token = Vec::new();
658
659    token.extend_from_slice(b"quiche");
660
661    let addr = match src.ip() {
662        std::net::IpAddr::V4(a) => a.octets().to_vec(),
663        std::net::IpAddr::V6(a) => a.octets().to_vec(),
664    };
665
666    token.extend_from_slice(&addr);
667    token.extend_from_slice(&hdr.dcid);
668
669    token
670}
671
672/// Validates a stateless retry token.
673///
674/// This checks that the ticket includes the `"quiche"` static string, and that
675/// the client IP address matches the address stored in the ticket.
676///
677/// Note that this function is only an example and doesn't do any cryptographic
678/// authenticate of the token. *It should not be used in production system*.
679fn validate_token<'a>(
680    src: &net::SocketAddr, token: &'a [u8],
681) -> Option<quiche::ConnectionId<'a>> {
682    if token.len() < 6 {
683        return None;
684    }
685
686    if &token[..6] != b"quiche" {
687        return None;
688    }
689
690    let token = &token[6..];
691
692    let addr = match src.ip() {
693        std::net::IpAddr::V4(a) => a.octets().to_vec(),
694        std::net::IpAddr::V6(a) => a.octets().to_vec(),
695    };
696
697    if token.len() < addr.len() || &token[..addr.len()] != addr.as_slice() {
698        return None;
699    }
700
701    Some(quiche::ConnectionId::from_ref(&token[addr.len()..]))
702}
703
704fn handle_path_events(client: &mut Client) {
705    while let Some(qe) = client.conn.path_event_next() {
706        match qe {
707            quiche::PathEvent::New(local_addr, peer_addr) => {
708                info!(
709                    "{} Seen new path ({}, {})",
710                    client.conn.trace_id(),
711                    local_addr,
712                    peer_addr
713                );
714
715                // Directly probe the new path.
716                client
717                    .conn
718                    .probe_path(local_addr, peer_addr)
719                    .expect("cannot probe");
720            },
721
722            quiche::PathEvent::Validated(local_addr, peer_addr) => {
723                info!(
724                    "{} Path ({}, {}) is now validated",
725                    client.conn.trace_id(),
726                    local_addr,
727                    peer_addr
728                );
729            },
730
731            quiche::PathEvent::FailedValidation(local_addr, peer_addr) => {
732                info!(
733                    "{} Path ({}, {}) failed validation",
734                    client.conn.trace_id(),
735                    local_addr,
736                    peer_addr
737                );
738            },
739
740            quiche::PathEvent::Closed(local_addr, peer_addr) => {
741                info!(
742                    "{} Path ({}, {}) is now closed and unusable",
743                    client.conn.trace_id(),
744                    local_addr,
745                    peer_addr
746                );
747            },
748
749            quiche::PathEvent::ReusedSourceConnectionId(cid_seq, old, new) => {
750                info!(
751                    "{} Peer reused cid seq {} (initially {:?}) on {:?}",
752                    client.conn.trace_id(),
753                    cid_seq,
754                    old,
755                    new
756                );
757            },
758
759            quiche::PathEvent::PeerMigrated(local_addr, peer_addr) => {
760                info!(
761                    "{} Connection migrated to ({}, {})",
762                    client.conn.trace_id(),
763                    local_addr,
764                    peer_addr
765                );
766            },
767        }
768    }
769}
770
771/// Set SO_TXTIME socket option.
772///
773/// This socket option is set to send to kernel the outgoing UDP
774/// packet transmission time in the sendmsg syscall.
775///
776/// Note that this socket option is set only on linux platforms.
777#[cfg(target_os = "linux")]
778fn set_txtime_sockopt(sock: &mio::net::UdpSocket) -> io::Result<()> {
779    use nix::sys::socket::setsockopt;
780    use nix::sys::socket::sockopt::TxTime;
781    use std::os::unix::io::AsRawFd;
782
783    let config = nix::libc::sock_txtime {
784        clockid: libc::CLOCK_MONOTONIC,
785        flags: 0,
786    };
787
788    setsockopt(sock.as_raw_fd(), TxTime, &config)?;
789
790    Ok(())
791}
792
793#[cfg(not(target_os = "linux"))]
794fn set_txtime_sockopt(_: &mio::net::UdpSocket) -> io::Result<()> {
795    use std::io::Error;
796
797    Err(Error::other("Not supported on this platform"))
798}