Files
irtt-rs/crates/irtt-client/src/socket_options.rs
T
Nils Andreas Svee dc5b19059e
Rust CI / cargo fmt (push) Successful in 14s
Rust CI / cargo check (x86_64-unknown-linux-gnu) (push) Successful in 32s
Rust CI / cargo clippy (push) Successful in 34s
Rust CI / cargo test (x86_64-unknown-linux-gnu) (push) Failing after 34s
Rust CI / cargo check (x86_64-unknown-linux-musl) (push) Failing after 7s
Rust CI / cargo test (x86_64-unknown-linux-musl) (push) Failing after 10s
Rust CI / cargo doc (push) Successful in 26s
Fuzz Smoke / cargo fuzz smoke (push) Failing after 32s
Supply chain / cargo-deny advisories (push) Successful in 6s
Supply chain / cargo-deny bans licenses sources (push) Successful in 5s
Supply chain / actionlint (push) Successful in 2s
Netem Interop / netem interop (push) Successful in 1m3s
tests(client): cleanup unnecessary tests and hooks
2026-10-02 08:33:49 +07:00

283 lines
7.4 KiB
Rust

use std::{
io,
net::{SocketAddr, UdpSocket},
};
use socket2::SockRef;
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux"
))]
use socket2::Socket;
use crate::{
config::{MAX_DSCP_CODEPOINT, MAX_TTL},
error::ClientError,
};
#[cfg(any(
target_os = "android",
target_os = "freebsd",
target_os = "fuchsia",
target_os = "linux"
))]
pub(crate) fn apply_routing_options(
socket: &Socket,
config: &crate::SocketConfig,
remote: SocketAddr,
) -> Result<(), ClientError> {
#[cfg(any(target_os = "android", target_os = "fuchsia", target_os = "linux"))]
{
if let Some(device) = &config.bind_to_device {
socket
.bind_device(Some(device.as_bytes()))
.map_err(|source| ClientError::SocketOption {
operation: "bind socket to device",
remote,
source,
})?;
}
if let Some(mark) = config.mark {
socket
.set_mark(mark)
.map_err(|source| ClientError::SocketOption {
operation: "set firewall mark",
remote,
source,
})?;
}
}
#[cfg(target_os = "freebsd")]
if let Some(fib) = config.fib {
socket
.set_fib(fib)
.map_err(|source| ClientError::SocketOption {
operation: "set FIB",
remote,
source,
})?;
}
Ok(())
}
/// Converts a public DSCP codepoint (`0..=`[`MAX_DSCP_CODEPOINT`]) into the
/// raw IP TOS / Traffic Class byte it occupies the upper six bits of.
///
/// This is the single place `codepoint << 2` is spelled out; both the
/// config-to-wire conversion and any direct codepoint-based socket
/// application go through this function so the shift cannot be duplicated or
/// accidentally applied twice.
pub(crate) fn dscp_codepoint_to_traffic_class(dscp: u8) -> Result<u32, ClientError> {
if dscp > MAX_DSCP_CODEPOINT {
return Err(ClientError::InvalidConfig {
reason: format!("dscp must be <= {MAX_DSCP_CODEPOINT}"),
});
}
Ok(u32::from(dscp) << 2)
}
/// Applies an already-raw IP TOS / Traffic Class byte to the socket directly,
/// without any codepoint shift.
pub(crate) fn apply_traffic_class_to_socket(
socket: &UdpSocket,
remote: SocketAddr,
traffic_class: u8,
) -> Result<(), ClientError> {
set_socket_traffic_class(
socket,
remote,
u32::from(traffic_class),
"set negotiated DSCP",
)
}
pub(crate) fn clear_dscp_on_socket(
socket: &UdpSocket,
remote: SocketAddr,
) -> Result<(), ClientError> {
set_socket_traffic_class(socket, remote, 0, "clear DSCP before close")
}
/// Applies an already-raw IP TOS / Traffic Class byte to the Tokio socket
/// directly, without any codepoint shift.
#[cfg(feature = "tokio")]
pub(crate) fn apply_traffic_class_to_tokio_socket(
socket: &tokio::net::UdpSocket,
remote: SocketAddr,
traffic_class: u8,
) -> Result<(), ClientError> {
set_tokio_socket_traffic_class(
socket,
remote,
u32::from(traffic_class),
"set negotiated DSCP",
)
}
#[cfg(feature = "tokio")]
pub(crate) fn clear_dscp_on_tokio_socket(
socket: &tokio::net::UdpSocket,
remote: SocketAddr,
) -> Result<(), ClientError> {
set_tokio_socket_traffic_class(socket, remote, 0, "clear DSCP before close")
}
pub(crate) fn validate_ttl(ttl: u32) -> Result<(), ClientError> {
if ttl == 0 || ttl > MAX_TTL {
return Err(ClientError::InvalidConfig {
reason: format!("ttl must be in range 1..={MAX_TTL}"),
});
}
Ok(())
}
pub(crate) fn apply_ttl_to_socket(
socket: &UdpSocket,
remote: SocketAddr,
ttl: u32,
) -> Result<(), ClientError> {
validate_ttl(ttl)?;
set_socket_ttl_ref(SockRef::from(socket), remote, ttl).map_err(|source| {
ClientError::SocketOption {
operation: "set TTL/hop limit",
remote,
source,
}
})
}
fn set_socket_ttl_ref(socket: SockRef<'_>, remote: SocketAddr, ttl: u32) -> io::Result<()> {
if remote.is_ipv4() {
socket.set_ttl_v4(ttl)
} else {
socket.set_unicast_hops_v6(ttl)
}
}
fn set_socket_traffic_class(
socket: &UdpSocket,
remote: SocketAddr,
traffic_class: u32,
operation: &'static str,
) -> Result<(), ClientError> {
set_socket_traffic_class_ref(SockRef::from(socket), remote, traffic_class).map_err(|source| {
ClientError::SocketOption {
operation,
remote,
source,
}
})
}
#[cfg(feature = "tokio")]
fn set_tokio_socket_traffic_class(
socket: &tokio::net::UdpSocket,
remote: SocketAddr,
traffic_class: u32,
operation: &'static str,
) -> Result<(), ClientError> {
set_socket_traffic_class_ref(SockRef::from(socket), remote, traffic_class).map_err(|source| {
ClientError::SocketOption {
operation,
remote,
source,
}
})
}
fn set_socket_traffic_class_ref(
socket: SockRef<'_>,
remote: SocketAddr,
traffic_class: u32,
) -> io::Result<()> {
if remote.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 = "illumos",
target_os = "haiku",
)))]
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 = "illumos",
target_os = "haiku",
))]
fn set_ipv4_traffic_class(_socket: SockRef<'_>, traffic_class: u32) -> io::Result<()> {
unsupported_traffic_class(traffic_class, "IPv4 DSCP 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",
))]
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",
)))]
fn set_ipv6_traffic_class(_socket: SockRef<'_>, traffic_class: u32) -> io::Result<()> {
unsupported_traffic_class(traffic_class, "IPv6 DSCP socket options")
}
#[cfg(any(
target_os = "fuchsia",
target_os = "redox",
target_os = "solaris",
target_os = "illumos",
target_os = "haiku",
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",
)),
))]
fn unsupported_traffic_class(traffic_class: u32, feature: &'static str) -> io::Result<()> {
if traffic_class == 0 {
Ok(())
} else {
Err(io::Error::new(
io::ErrorKind::Unsupported,
format!("{feature} are unsupported on this target"),
))
}
}