Skip to main content

irtt_server/
runtime.rs

1//! Tokio UDP orchestration around [`ServerCore`](crate::ServerCore).
2
3use std::{
4    future::Future,
5    io,
6    net::{IpAddr, SocketAddr},
7    time::Duration,
8};
9
10use socket2::SockRef;
11use thiserror::Error;
12use tokio::{net::UdpSocket, time::MissedTickBehavior};
13
14use crate::{
15    socket_io::{self, ReceivedDatagram},
16    socket_options::{marks_with_ipv4_option, set_reply_traffic_class},
17    ServerConfig, ServerCore, ServerError,
18};
19
20/// Fixed transport receive capacity.
21///
22/// Standard UDP payloads fit in this buffer; IPv6 jumbograms are not supported.
23/// This is allocation policy only: whether a received datagram may reach the
24/// protocol core — the conservative rejection of one filling the buffer
25/// included — belongs to [`socket_io::receive`].
26const RECEIVE_BUFFER_LEN: usize = 65_536;
27const MAINTENANCE_INTERVAL: Duration = Duration::from_secs(1);
28
29/// A Tokio UDP listener backed by one deterministic server core.
30///
31/// Processing is sequential: one task receives a datagram, lets the core
32/// process it, and sends the optional reply from the same socket. No
33/// per-datagram tasks or locks are involved.
34///
35/// **That sequential ownership is what makes the reply traffic class correct.**
36/// The class is a socket-wide setting, and every reply explicitly applies its
37/// own — zero included — immediately before its send, so the reply about to go
38/// out is always the one that set it. Nothing else sends from this socket, not
39/// even while a send is suspended, so no reply can inherit another session's
40/// marking. Adding a concurrent sender would break that and require per-packet
41/// control messages instead.
42///
43/// # Reply source address
44///
45/// A reply must leave from the exact address the request was sent to. An
46/// explicit-address listener gets that from the bind: it can send from nothing
47/// else. A wildcard listener (`0.0.0.0` or `[::]`) cannot, because the routing
48/// table would choose the source, so it asks the kernel for each request's
49/// local destination and sends that request's reply from it.
50///
51/// That per-packet path exists on Linux, macOS and FreeBSD. Elsewhere a
52/// wildcard bind is **refused** at construction — see
53/// [`ServerRuntimeError::WildcardSourceSelectionUnsupported`] — rather than
54/// served by a listener whose replies a client on a second address would
55/// silently discard. Explicit-address listeners are unaffected everywhere.
56///
57/// # Example
58///
59/// Bind one listener, inspect the resolved endpoint, and let the caller decide
60/// when serving ends. This example type-checks without opening a socket:
61///
62/// ```no_run
63/// use std::{net::{Ipv4Addr, SocketAddr}, time::Duration};
64///
65/// use irtt_server::{Server, ServerConfig};
66///
67/// # async fn serve() -> Result<(), Box<dyn std::error::Error>> {
68/// let mut server = Server::bind(
69///     SocketAddr::from((Ipv4Addr::LOCALHOST, 0)),
70///     ServerConfig::default(),
71/// )
72/// .await?;
73/// println!("listening on {}", server.local_addr()?);
74/// server.run(tokio::time::sleep(Duration::from_secs(60))).await?;
75/// # Ok(())
76/// # }
77/// ```
78#[derive(Debug)]
79pub struct Server {
80    socket: UdpSocket,
81    core: ServerCore,
82    recv_buffer: Vec<u8>,
83    /// The listener's own address family, which selects the IPv4 TOS or IPv6
84    /// Traffic Class option.
85    listener_is_ipv4: bool,
86    /// Whether this listener is wildcard-bound and must therefore recover each
87    /// request's local destination and reply from it.
88    ///
89    /// Decided once, from the bound address. It is never inferred later from a
90    /// peer: a listener that accepted a wildcard bind owes correct reply
91    /// sources to every request, not to the ones that look multi-homed.
92    select_reply_source: bool,
93    /// Whether this server has ever successfully applied a nonzero traffic
94    /// class to the socket.
95    ///
96    /// This is not a cache and never elides a call — the class is applied
97    /// before every send regardless. It is consulted only when applying one
98    /// *fails*, to tell "the socket may be carrying a marking of ours" from
99    /// "it cannot be carrying one".
100    socket_is_marked: bool,
101}
102
103impl Server {
104    /// Binds one UDP listener at `addr` and creates its independent session
105    /// namespace.
106    ///
107    /// A wildcard `addr` additionally configures reply source selection, and
108    /// fails where that is unavailable; see [`Server::from_socket`].
109    pub async fn bind(addr: SocketAddr, config: ServerConfig) -> Result<Self, ServerRuntimeError> {
110        let socket = UdpSocket::bind(addr)
111            .await
112            .map_err(|source| ServerRuntimeError::Bind { addr, source })?;
113        Self::from_socket(socket, config)
114    }
115
116    /// Wraps an already prepared Tokio UDP socket in a server.
117    ///
118    /// # Errors
119    ///
120    /// Construction is fallible because a wildcard listener has setup to do
121    /// before it may serve anything. It fails when the socket's bound address
122    /// cannot be queried — which is what says whether this is a wildcard
123    /// listener at all — and, for a wildcard socket, when this target has no
124    /// reply source selection or the kernel refuses to configure it.
125    ///
126    /// Failing here is the point. A wildcard listener that cannot recover a
127    /// request's local destination would start, run, and answer clients on a
128    /// second local address from an endpoint they never contacted, which they
129    /// discard as though the network had dropped it.
130    pub fn from_socket(
131        socket: UdpSocket,
132        config: ServerConfig,
133    ) -> Result<Self, ServerRuntimeError> {
134        Self::with_core(socket, ServerCore::new(config))
135    }
136
137    /// Test-only seam: builds a listener around an already-constructed core
138    /// instead of one this method would build from a [`ServerConfig`].
139    ///
140    /// This exists so a unit test can substitute a scripted
141    /// [`TokenSource`](crate::token::TokenSource) and make one listener in a
142    /// [`ServerSet`](crate::ServerSet) fail deterministically after it has
143    /// already bound and answered real traffic — the runtime otherwise offers
144    /// no way to make a core failure happen on command without OS-level
145    /// socket sabotage. Every other code path here — wildcard setup, kernel
146    /// timestamp configuration — is unchanged from [`Server::from_socket`].
147    #[cfg(test)]
148    pub(crate) fn from_socket_with_core(
149        socket: UdpSocket,
150        core: ServerCore,
151    ) -> Result<Self, ServerRuntimeError> {
152        Self::with_core(socket, core)
153    }
154
155    fn with_core(socket: UdpSocket, core: ServerCore) -> Result<Self, ServerRuntimeError> {
156        let addr = socket
157            .local_addr()
158            .map_err(|source| ServerRuntimeError::LocalAddr { source })?;
159        let select_reply_source = is_wildcard(addr);
160
161        if select_reply_source {
162            if !socket_io::SUPPORTED {
163                return Err(ServerRuntimeError::WildcardSourceSelectionUnsupported { addr });
164            }
165            socket_io::configure_destination_metadata(&socket, addr.is_ipv4())
166                .map_err(|source| ServerRuntimeError::SourceSelectionSetup { addr, source })?;
167        }
168
169        // Kernel receive timestamps, for every listener and strictly after the
170        // mandatory setup above so a refusal here can neither hide nor stand in
171        // for a wildcard listener's failure to secure its reply sources. This
172        // one is allowed to fail: it buys accuracy, not correctness, and a
173        // server that cannot have it is still a correct server.
174        let _ = socket_io::try_configure_kernel_rx_timestamp(&socket);
175
176        Ok(Self {
177            socket,
178            core,
179            recv_buffer: vec![0; RECEIVE_BUFFER_LEN],
180            listener_is_ipv4: marks_with_ipv4_option(addr),
181            select_reply_source,
182            socket_is_marked: false,
183        })
184    }
185
186    /// Applies one reply's raw traffic class to the listener socket, and
187    /// reports whether that reply may be sent.
188    ///
189    /// The class is applied before *every* send, and zero as deliberately as
190    /// any other value: a listener serves many sessions from one socket, so
191    /// skipping the call for an unmarked reply — an open reply, or a session
192    /// that negotiated nothing — would send it under whichever marking the
193    /// previous reply left behind.
194    ///
195    /// When the option cannot be applied, [`may_send_unappliable`] decides. A
196    /// marking this server asked for and did not get must never be replaced by
197    /// silently sending under the previous one, so those replies are dropped;
198    /// but a host that refuses the option outright — some Windows builds do not
199    /// support `IP_TOS`, and a few targets have no safe setter at all — must
200    /// still be able to run a server, and it can, because a socket this server
201    /// has never marked has no marking of ours to clear.
202    fn prepare_reply_traffic_class(&mut self, traffic_class: u8) -> bool {
203        match self.apply_reply_traffic_class(traffic_class) {
204            Ok(()) => {
205                self.socket_is_marked = traffic_class != 0;
206                true
207            }
208            Err(_) => may_send_unappliable(traffic_class, self.socket_is_marked),
209        }
210    }
211
212    fn apply_reply_traffic_class(&self, traffic_class: u8) -> io::Result<()> {
213        set_reply_traffic_class(
214            SockRef::from(&self.socket),
215            self.listener_is_ipv4,
216            traffic_class,
217        )
218    }
219
220    /// Returns the local endpoint selected for the listener.
221    pub fn local_addr(&self) -> Result<SocketAddr, ServerRuntimeError> {
222        self.socket
223            .local_addr()
224            .map_err(|source| ServerRuntimeError::LocalAddr { source })
225    }
226
227    /// Serves datagrams until `shutdown` completes.
228    ///
229    /// Graceful shutdown sends no session-close packets and leaves no hidden
230    /// receive task behind, even while a reply send is waiting for socket
231    /// writability. Per-packet send failures and short sends drop only that
232    /// reply, and so does a failure to apply a marking that reply needed —
233    /// preparing one packet is not a reason to stop serving every other
234    /// session, and the next reply makes its own independent attempt. Receive
235    /// failures and internal core failures terminate the loop with an error.
236    ///
237    /// A reply whose marking could not be applied is not sent instead: sending
238    /// it would put it on the wire under the previous reply's marking. An
239    /// *unmarked* reply on a socket this server has never marked is the one
240    /// exception, so a host that does not support the option at all — some
241    /// Windows builds do not support `IP_TOS` — still runs a working server.
242    ///
243    /// A wildcard listener drops a request whose local destination did not
244    /// arrive with it, before the core sees it. There is no fall back to a
245    /// routing-table source: the listener promised a correct reply source when
246    /// it accepted the bind, and a request it cannot answer correctly must not
247    /// move a session's receive, rate or lifetime state either.
248    pub async fn run<F>(&mut self, shutdown: F) -> Result<(), ServerRuntimeError>
249    where
250        F: Future<Output = ()>,
251    {
252        tokio::pin!(shutdown);
253        let mut maintenance = tokio::time::interval(MAINTENANCE_INTERVAL);
254        maintenance.set_missed_tick_behavior(MissedTickBehavior::Skip);
255        maintenance.tick().await;
256
257        loop {
258            tokio::select! {
259                _ = &mut shutdown => return Ok(()),
260                _ = maintenance.tick() => self.core.maintain(),
261                received = socket_io::receive(
262                    &self.socket,
263                    &mut self.recv_buffer,
264                    self.select_reply_source,
265                ) => {
266                    let received = match received {
267                        Ok(Some(received)) => received,
268                        Ok(None) => continue,
269                        Err(source) if source.kind() == io::ErrorKind::Interrupted => continue,
270                        Err(source) => return Err(ServerRuntimeError::Receive { source }),
271                    };
272                    let ReceivedDatagram {
273                        len,
274                        peer,
275                        reply_source,
276                        // Handed to the core with the datagram it describes, as
277                        // an explicit per-datagram input. The core samples its
278                        // own receive instant regardless and decides whether
279                        // this one is usable; nothing here judges it, and there
280                        // is no "next receive" state between the two.
281                        kernel_rx_timestamp,
282                    } = received;
283                    // `reply_source` stays on this side of the boundary: which
284                    // of the host's addresses a request arrived on is transport
285                    // state, not session policy.
286                    if let Some(reply) = self.core.handle_received_datagram(
287                        peer,
288                        &self.recv_buffer[..len],
289                        kernel_rx_timestamp,
290                    )? {
291                        if !self.prepare_reply_traffic_class(reply.traffic_class()) {
292                            continue;
293                        }
294                        let send = socket_io::send(
295                            &self.socket,
296                            reply.bytes(),
297                            peer,
298                            reply_source,
299                        );
300                        tokio::pin!(send);
301                        let sent = loop {
302                            tokio::select! {
303                                _ = &mut shutdown => return Ok(()),
304                                _ = maintenance.tick() => self.core.maintain(),
305                                sent = &mut send => break sent,
306                            }
307                        };
308                        if !matches!(sent, Ok(len) if len == reply.bytes().len()) {
309                            continue;
310                        }
311                    }
312                }
313            }
314        }
315    }
316}
317
318/// Whether a bound address names no particular local address, and so owes every
319/// reply an explicitly selected source.
320///
321/// `0.0.0.0` and `[::]` are the obvious forms. The third is the IPv4-mapped
322/// unspecified address, `[::ffff:0.0.0.0]`: Linux accepts it as an IPv4 wildcard
323/// bind and reports it back verbatim from `getsockname`, so asking
324/// `is_unspecified` alone would take a working wildcard listener for an explicit
325/// one and answer its requests from whatever source the routing table picked.
326/// (macOS normalizes that bind to `[::]` and never reaches the second test.)
327///
328/// A mapped address that names an actual host address — `[::ffff:127.0.0.1]` —
329/// is explicit, and stays on the plain path.
330fn is_wildcard(addr: SocketAddr) -> bool {
331    match addr.ip() {
332        IpAddr::V4(address) => address.is_unspecified(),
333        IpAddr::V6(address) => {
334            address.is_unspecified()
335                || address
336                    .to_ipv4_mapped()
337                    .is_some_and(|mapped| mapped.is_unspecified())
338        }
339    }
340}
341
342/// Whether a reply may still be sent after its traffic class could not be
343/// applied.
344///
345/// Only an unmarked reply on a socket this server has never successfully marked
346/// qualifies. Both halves matter: a reply that wanted a marking must not go out
347/// without one, and once this server has put a nonzero class on the socket, a
348/// failure to clear it means the socket may still be carrying it.
349///
350/// A socket handed to [`Server::from_socket`] pre-marked by its creator is
351/// outside what this can know; explicit marking is the server's own to manage
352/// from a fresh listener.
353fn may_send_unappliable(traffic_class: u8, socket_is_marked: bool) -> bool {
354    traffic_class == 0 && !socket_is_marked
355}
356
357/// Failure to create or run a Tokio UDP server.
358#[derive(Debug, Error)]
359pub enum ServerRuntimeError {
360    /// Binding the requested listener failed.
361    #[error("could not bind UDP listener at {addr}: {source}")]
362    Bind {
363        addr: SocketAddr,
364        #[source]
365        source: io::Error,
366    },
367    /// Querying the listener's selected local endpoint failed.
368    #[error("could not query UDP listener address: {source}")]
369    LocalAddr {
370        #[source]
371        source: io::Error,
372    },
373    /// A wildcard listener was requested on a target with no safe per-packet
374    /// reply source selection.
375    ///
376    /// The listener would answer from whichever local address the routing table
377    /// chose, which a client that contacted a different one discards.
378    #[error(
379        "wildcard listener {addr} cannot select its reply source address on this target: \
380         bind an explicit local address instead"
381    )]
382    WildcardSourceSelectionUnsupported { addr: SocketAddr },
383    /// Configuring destination-address metadata for a wildcard listener failed.
384    #[error(
385        "could not configure reply source-address selection for wildcard listener {addr}: \
386         {source}; bind an explicit local address instead"
387    )]
388    SourceSelectionSetup {
389        addr: SocketAddr,
390        #[source]
391        source: io::Error,
392    },
393    /// Receiving from the listener failed irrecoverably.
394    #[error("UDP receive failed: {source}")]
395    Receive {
396        #[source]
397        source: io::Error,
398    },
399    /// The deterministic protocol/session core failed internally.
400    #[error(transparent)]
401    Core(#[from] ServerError),
402}
403
404#[cfg(test)]
405mod tests {
406    use std::{net::Ipv4Addr, time::Duration};
407
408    use irtt_proto::{
409        decode_echo_reply, decode_open_reply, encode_request, Clock, Params, ReceivedStats,
410        RequestToEncode, StampAt,
411    };
412    use tokio::{net::UdpSocket, sync::oneshot, time::timeout};
413
414    use super::*;
415
416    #[tokio::test(flavor = "current_thread")]
417    async fn bind_reports_local_address_and_shutdown_stops_run() {
418        let requested = SocketAddr::from((Ipv4Addr::LOCALHOST, 0));
419        let mut server = Server::bind(requested, ServerConfig::default())
420            .await
421            .unwrap();
422        let bound = server.local_addr().unwrap();
423        assert_eq!(bound.ip(), requested.ip());
424        assert_ne!(bound.port(), 0);
425
426        let (shutdown_tx, shutdown_rx) = oneshot::channel();
427        shutdown_tx.send(()).unwrap();
428        server
429            .run(async {
430                let _ = shutdown_rx.await;
431            })
432            .await
433            .unwrap();
434    }
435
436    /// Construction is where a wildcard listener's promise is made or refused.
437    ///
438    /// Both outcomes are asserted from the same code, because which one a target
439    /// gets is exactly the thing under test: a target with a source-selection
440    /// path configures it and serves; one without refuses the bind rather than
441    /// starting a listener whose replies may come from an address no client
442    /// contacted. An explicit bind is unaffected either way.
443    #[tokio::test(flavor = "current_thread")]
444    async fn from_socket_settles_wildcard_source_selection_before_serving_anything() {
445        let explicit = UdpSocket::bind("127.0.0.1:0").await.unwrap();
446        assert!(Server::from_socket(explicit, ServerConfig::default()).is_ok());
447
448        let wildcard = UdpSocket::bind("0.0.0.0:0").await.unwrap();
449        let wildcard = Server::from_socket(wildcard, ServerConfig::default());
450        if socket_io::SUPPORTED {
451            let server = wildcard.expect("a supported target configures source selection");
452            assert!(server.select_reply_source);
453        } else {
454            assert!(matches!(
455                wildcard,
456                Err(ServerRuntimeError::WildcardSourceSelectionUnsupported { .. })
457            ));
458        }
459    }
460
461    #[tokio::test(flavor = "current_thread")]
462    async fn ipv4_open_echo_close_crosses_a_real_udp_socket() {
463        exercise_udp_path(SocketAddr::from((Ipv4Addr::LOCALHOST, 0))).await;
464    }
465
466    #[tokio::test(flavor = "current_thread")]
467    async fn ipv6_open_echo_close_when_loopback_is_available() {
468        let addr = "[::1]:0".parse().unwrap();
469        let server = match Server::bind(addr, unthrottled()).await {
470            Ok(server) => server,
471            Err(ServerRuntimeError::Bind { source, .. })
472                if matches!(
473                    source.kind(),
474                    io::ErrorKind::AddrNotAvailable | io::ErrorKind::Unsupported
475                ) =>
476            {
477                return;
478            }
479            Err(error) => panic!("unexpected IPv6 loopback bind failure: {error}"),
480        };
481        exercise_bound_server(server).await;
482    }
483
484    async fn exercise_udp_path(addr: SocketAddr) {
485        let server = Server::bind(addr, unthrottled()).await.unwrap();
486        exercise_bound_server(server).await;
487    }
488
489    async fn exercise_bound_server(mut server: Server) {
490        let server_addr = server.local_addr().unwrap();
491        let client_bind = match server_addr {
492            SocketAddr::V4(_) => "127.0.0.1:0",
493            SocketAddr::V6(_) => "[::1]:0",
494        };
495        let client = UdpSocket::bind(client_bind).await.unwrap();
496        let (shutdown_tx, shutdown_rx) = oneshot::channel();
497        let server_task = tokio::spawn(async move {
498            server
499                .run(async {
500                    let _ = shutdown_rx.await;
501                })
502                .await
503        });
504
505        let requested = Params {
506            protocol_version: 1,
507            duration_ns: 1_000_000_000,
508            interval_ns: 100_000_000,
509            received_stats: ReceivedStats::Both,
510            stamp_at: StampAt::Both,
511            clock: Clock::Both,
512            ..Params::default()
513        };
514        let open = encode_request(
515            RequestToEncode::Open {
516                params: &requested,
517                no_test: false,
518            },
519            None,
520        )
521        .unwrap();
522        client.send_to(&open, server_addr).await.unwrap();
523
524        let mut buffer = vec![0; RECEIVE_BUFFER_LEN];
525        let (len, reply_source) = timeout(Duration::from_secs(1), client.recv_from(&mut buffer))
526            .await
527            .expect("OPEN reply timeout")
528            .unwrap();
529        assert_eq!(reply_source, server_addr);
530        let open_reply = decode_open_reply(&buffer[..len], None).unwrap();
531        assert_ne!(open_reply.token, 0);
532
533        let echo = encode_request(
534            RequestToEncode::Echo {
535                token: open_reply.token,
536                sequence: 0,
537                params: &open_reply.params,
538                payload: &[],
539            },
540            None,
541        )
542        .unwrap();
543        client.send_to(&echo, server_addr).await.unwrap();
544        let (len, reply_source) = timeout(Duration::from_secs(1), client.recv_from(&mut buffer))
545            .await
546            .expect("ECHO reply timeout")
547            .unwrap();
548        assert_eq!(reply_source, server_addr);
549        let echo_reply = decode_echo_reply(&buffer[..len], &open_reply.params, None).unwrap();
550        assert_eq!(echo_reply.token, open_reply.token);
551        assert_eq!(echo_reply.sequence, 0);
552        assert_eq!(echo_reply.recv_count, Some(1));
553        assert_eq!(echo_reply.recv_window, Some(1));
554
555        let close = encode_request(
556            RequestToEncode::Close {
557                token: open_reply.token,
558            },
559            None,
560        )
561        .unwrap();
562        client.send_to(&close, server_addr).await.unwrap();
563        client.send_to(&echo, server_addr).await.unwrap();
564        assert!(
565            timeout(Duration::from_millis(100), client.recv_from(&mut buffer))
566                .await
567                .is_err()
568        );
569
570        shutdown_tx.send(()).unwrap();
571        server_task.await.unwrap().unwrap();
572    }
573
574    fn unthrottled() -> ServerConfig {
575        ServerConfig::default().with_min_send_interval(Duration::ZERO)
576    }
577
578    /// Which bound addresses owe their replies a selected source.
579    ///
580    /// The IPv4-mapped unspecified address is the one that is not obvious, and
581    /// it is not hypothetical: Linux accepts `[::ffff:0.0.0.0]` as an IPv4
582    /// wildcard bind and reports it back unchanged, so missing it would leave a
583    /// working wildcard listener on the plain send path. A mapped address
584    /// naming a real host address is explicit and must stay there.
585    #[test]
586    fn a_bound_address_naming_no_particular_local_address_is_a_wildcard() {
587        for (addr, wildcard) in [
588            ("0.0.0.0:2112", true),
589            ("[::]:2112", true),
590            ("[::ffff:0.0.0.0]:2112", true),
591            ("127.0.0.1:2112", false),
592            ("192.0.2.10:2112", false),
593            ("[::1]:2112", false),
594            ("[::ffff:127.0.0.1]:2112", false),
595        ] {
596            assert_eq!(
597                is_wildcard(addr.parse().unwrap()),
598                wildcard,
599                "{addr} classified wrongly"
600            );
601        }
602    }
603
604    /// The rule that decides what an unappliable traffic class means, which no
605    /// normal interface can reach: it needs a host that refuses the socket
606    /// option, and the hosts these tests run on do not.
607    #[test]
608    fn only_an_unmarked_reply_on_a_never_marked_socket_survives_an_apply_failure() {
609        for (traffic_class, socket_is_marked, sendable, why) in [
610            (
611                0,
612                false,
613                true,
614                "no marking wanted, and none of ours to clear",
615            ),
616            (0, true, false, "our own marking may still be on the socket"),
617            (
618                0xb8,
619                false,
620                false,
621                "a marking that was wanted but not applied",
622            ),
623            (0xb8, true, false, "and likewise over a marking already set"),
624        ] {
625            assert_eq!(
626                may_send_unappliable(traffic_class, socket_is_marked),
627                sendable,
628                "{why}"
629            );
630        }
631    }
632}