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