feat(server): give irtt-server sensible zero-argument defaults
With no --bind, the server now binds the wildcard IRTT port on both address families ([::]:2112 then 0.0.0.0:2112) instead of requiring an explicit address, on platforms where wildcard reply-source selection is already supported. Any explicit --bind fully replaces the default pair rather than adding to it; repeated explicit binds are unaffected. If one of the two default listeners fails because its address family has no local support on this host at all (IPv6 disabled on an otherwise IPv4-capable host, for example), the server falls back to serving just the surviving family instead of failing outright. Any other failure reason -- the port already in use, permission denied, no safe wildcard reply-source path -- is not a family-support problem and is not silently worked around; startup fails as it would for an explicit --bind. This distinction is made with the new irtt_server::address_family_available, which actually binds the wildcard address of the failed listener's family on an ephemeral port: a bare socket can construct successfully even when every bind for that family fails (Linux administratively disabling IPv6 per-interface leaves AF_INET6 sockets constructible but every bind EADDRNOTAVAIL), so only a real bind attempt is a reliable signal.
This commit is contained in:
4 files changed
+216
-15
No files matched your search
@@ -32,6 +32,9 @@
|
||||
//! [`ServerConfig`] — `max_sessions` included — applies **per listener**, not
|
||||
//! per process.
|
||||
//!
|
||||
//! See `examples/` in the repository for a runnable single-listener and
|
||||
//! multi-listener example.
|
||||
//!
|
||||
//! # Rejection is silence
|
||||
//!
|
||||
//! The protocol defines no error reply, no reset and no NACK. Malformed,
|
||||
@@ -129,4 +132,4 @@ pub use config::{
|
||||
pub use core::{OutboundDatagram, ServerCore};
|
||||
pub use error::ServerError;
|
||||
pub use runtime::{Server, ServerRuntimeError};
|
||||
pub use set::{ServerSet, ServerSetError};
|
||||
pub use set::{address_family_available, ServerSet, ServerSetError};
|
||||
@@ -218,6 +218,40 @@ impl ServerSet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `addr`'s address family has any local support on this host at
|
||||
/// all, independent of whether this particular address or port can be
|
||||
/// bound.
|
||||
///
|
||||
/// A [`ServerSetError::ListenerSetup`] failure can mean many different
|
||||
/// things — the port is already in use, permission is denied, this target
|
||||
/// has no safe wildcard reply-source path (see [`ServerRuntimeError`]) — and
|
||||
/// only one of them, the address family itself having no local support at
|
||||
/// all (for example IPv6 disabled on the host), is safe for a caller to work
|
||||
/// around by silently trying a different address instead. This checks only
|
||||
/// that, by actually binding the wildcard address of `addr`'s family on an
|
||||
/// ephemeral port: a bare socket of an unsupported family can still
|
||||
/// construct successfully on some systems (Linux administratively disables
|
||||
/// IPv6 per-interface, so `AF_INET6` sockets keep working while every bind
|
||||
/// fails with `EADDRNOTAVAIL`), so construction alone is not a reliable
|
||||
/// signal — only a real bind attempt is.
|
||||
#[must_use]
|
||||
pub fn address_family_available(addr: SocketAddr) -> bool {
|
||||
let wildcard = if addr.is_ipv6() {
|
||||
SocketAddr::from((std::net::Ipv6Addr::UNSPECIFIED, 0))
|
||||
} else {
|
||||
SocketAddr::from((std::net::Ipv4Addr::UNSPECIFIED, 0))
|
||||
};
|
||||
let domain = if addr.is_ipv6() {
|
||||
Domain::IPV6
|
||||
} else {
|
||||
Domain::IPV4
|
||||
};
|
||||
let Ok(socket) = Socket::new(domain, Type::DGRAM, Some(Protocol::UDP)) else {
|
||||
return false;
|
||||
};
|
||||
socket.bind(&SockAddr::from(wildcard)).is_ok()
|
||||
}
|
||||
|
||||
/// Binds one listener, pre-configuring the socket where the bind itself depends
|
||||
/// on it.
|
||||
///
|
||||
|
||||
Reference in new issue
Block a user