irtt-server 0.5.3

IRTT-compatible Rust server library
Documentation
//! Applying a reply's traffic class to the listener socket.
//!
//! The value is the raw IPv4 TOS / IPv6 Traffic Class byte an
//! [`OutboundDatagram`](crate::OutboundDatagram) asks for, and which option
//! carries it is decided by the listener's own address family — one `Server`
//! owns one bound socket, so the peer a reply happens to be going to never
//! selects the option.
//!
//! Everything here is `socket2`'s safe API; the crate forbids unsafe code and
//! there is no raw `setsockopt` or per-packet control message anywhere in the
//! server.

use std::{
    io,
    net::{IpAddr, SocketAddr},
};

use socket2::SockRef;

/// Whether this listener's replies are marked with the IPv4 TOS option rather
/// than the IPv6 Traffic Class one.
///
/// The listener's own bound family answers this, not the peer's — one socket,
/// one answer. The exception is an IPv4-mapped bound address, where the socket
/// is `AF_INET6` but every packet it emits is IPv4, and the platforms disagree
/// about it:
///
/// - **Linux** binds such a socket to the IPv4 side. `IPV6_TCLASS` does not
///   reach the packets it sends — their TOS stays zero and a negotiated marking
///   is silently lost — while `IP_TOS` on that same socket marks them. So a
///   mapped bind is read as the IPv4 listener it is.
/// - **macOS** rejects `IP_TOS` on an `AF_INET6` socket with `EINVAL` whatever
///   it is bound to, and marks nothing through either option. Reading a mapped
///   bind as IPv4 there would turn an unmarked reply into a *dropped* one, since
///   an unappliable marking stops the send — worse than the marking simply not
///   arriving. It keeps the IPv6 option and its existing behavior. (The mapped
///   *wildcard* never reaches this at all: macOS normalizes that bind to `[::]`.)
/// - **FreeBSD** refuses a mapped bind under its default `IPV6_V6ONLY`, so the
///   case does not arise.
pub(crate) fn marks_with_ipv4_option(addr: SocketAddr) -> bool {
    match addr.ip() {
        IpAddr::V4(_) => true,
        IpAddr::V6(address) => cfg!(target_os = "linux") && address.to_ipv4_mapped().is_some(),
    }
}

/// Applies `traffic_class` to a socket bound in the given family.
///
/// `is_ipv4` is the *listener's* family, not the peer's.
pub(crate) fn set_reply_traffic_class(
    socket: SockRef<'_>,
    is_ipv4: bool,
    traffic_class: u8,
) -> io::Result<()> {
    let traffic_class = u32::from(traffic_class);
    if is_ipv4 {
        set_ipv4_traffic_class(socket, traffic_class)
    } else {
        set_ipv6_traffic_class(socket, traffic_class)
    }
}

#[cfg(not(any(
    target_os = "fuchsia",
    target_os = "redox",
    target_os = "solaris",
    target_os = "haiku",
    target_os = "wasi",
)))]
fn set_ipv4_traffic_class(socket: SockRef<'_>, traffic_class: u32) -> io::Result<()> {
    socket.set_tos_v4(traffic_class)
}

#[cfg(any(
    target_os = "fuchsia",
    target_os = "redox",
    target_os = "solaris",
    target_os = "haiku",
    target_os = "wasi",
))]
fn set_ipv4_traffic_class(_socket: SockRef<'_>, traffic_class: u32) -> io::Result<()> {
    unsupported_traffic_class(traffic_class, "IPv4 TOS socket options")
}

#[cfg(any(
    target_os = "android",
    target_os = "dragonfly",
    target_os = "freebsd",
    target_os = "fuchsia",
    target_os = "linux",
    target_os = "macos",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "cygwin",
    target_os = "illumos",
))]
fn set_ipv6_traffic_class(socket: SockRef<'_>, traffic_class: u32) -> io::Result<()> {
    socket.set_tclass_v6(traffic_class)
}

#[cfg(not(any(
    target_os = "android",
    target_os = "dragonfly",
    target_os = "freebsd",
    target_os = "fuchsia",
    target_os = "linux",
    target_os = "macos",
    target_os = "netbsd",
    target_os = "openbsd",
    target_os = "cygwin",
    target_os = "illumos",
)))]
fn set_ipv6_traffic_class(_socket: SockRef<'_>, traffic_class: u32) -> io::Result<()> {
    unsupported_traffic_class(traffic_class, "IPv6 Traffic Class socket options")
}

/// A target where the safe API cannot express this option.
///
/// This reports [`io::ErrorKind::Unsupported`] for *every* value, zero
/// included. Returning success for zero would claim the socket had been cleared
/// when nothing was written to it, which is not the same statement — a caller
/// handing [`Server::from_socket`](crate::Server::from_socket) an already
/// prepared socket may have marked it by some other means. Whether an
/// unappliable zero is nevertheless safe to send is the runtime's decision, and
/// it is made from what this server itself has applied; this helper's job is
/// only to report honestly.
#[cfg(any(
    target_os = "fuchsia",
    target_os = "redox",
    target_os = "solaris",
    target_os = "haiku",
    target_os = "wasi",
    not(any(
        target_os = "android",
        target_os = "dragonfly",
        target_os = "freebsd",
        target_os = "fuchsia",
        target_os = "linux",
        target_os = "macos",
        target_os = "netbsd",
        target_os = "openbsd",
        target_os = "cygwin",
        target_os = "illumos",
    )),
))]
fn unsupported_traffic_class(_traffic_class: u32, option: &'static str) -> io::Result<()> {
    Err(io::Error::new(
        io::ErrorKind::Unsupported,
        format!("{option} are unsupported on this target"),
    ))
}

#[cfg(test)]
mod tests {
    use super::*;

    /// Which listeners mark with the IPv4 option.
    ///
    /// The mapped forms are the point: such a socket is `AF_INET6` but sends
    /// IPv4, and `IPV6_TCLASS` does not reach those packets. Linux marks them
    /// through `IP_TOS`; the platforms that would reject that keep the IPv6
    /// option, because an unappliable marking stops the send and a dropped
    /// reply is worse than an unmarked one.
    #[test]
    fn a_mapped_listener_marks_with_the_ipv4_option_where_that_option_works() {
        let mapped = cfg!(target_os = "linux");

        for (addr, is_ipv4) in [
            ("0.0.0.0:2112", true),
            ("127.0.0.1:2112", true),
            ("[::ffff:0.0.0.0]:2112", mapped),
            ("[::ffff:127.0.0.1]:2112", mapped),
            ("[::]:2112", false),
            ("[::1]:2112", false),
            ("[2001:db8::1]:2112", false),
        ] {
            assert_eq!(
                marks_with_ipv4_option(addr.parse().unwrap()),
                is_ipv4,
                "{addr} would mark with the wrong option"
            );
        }
    }
}