feat(net): accept relay hostnames

PLAN.md describes the first client flow as entering a relay domain and room
code, but the client and gateway CLIs only accepted socket-address literals.
Add a small shared RelayEndpoint parser so bare hosts default to UDP/443 while
IP literals and explicit host:port values stay supported.

The runtime configs still store resolved SocketAddr values. That keeps the
Windows route-pinning path on a concrete relay IP before TAP activation while
avoiding duplicated endpoint grammar between client and gateway. The relay
listen config reuses the same default port constant so UDP/443 has one source.

README examples now use lanparty-relay.local and document the shared endpoint
syntax.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-net
- cargo test -p lanparty-client-win \
  accepts_relay_domain_with_default_port -- --nocapture
- cargo test -p lanparty-gateway \
  accepts_iface_alias_for_gateway_interface -- --nocapture
- cargo test -p lanparty-net -p lanparty-client-win -p lanparty-gateway
- cargo clippy -p lanparty-net -p lanparty-client-win -p lanparty-gateway \
  --all-targets -- -D warnings
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md
This commit is contained in:
2026-05-21 21:40:00 +02:00
parent 829ffe9b95
commit bdb571799a
11 changed files with 301 additions and 25 deletions
+1
View File
@@ -8,6 +8,7 @@ anyhow.workspace = true
bytes.workspace = true
clap.workspace = true
lanparty-ctrl = { path = "../lanparty-ctrl" }
lanparty-net = { path = "../lanparty-net" }
lanparty-obs = { path = "../lanparty-obs" }
lanparty-proto = { path = "../lanparty-proto" }
libc.workspace = true
+14 -5
View File
@@ -27,6 +27,7 @@ use lanparty_ctrl::{
MAX_CONTROL_MESSAGE_LEN, PeerInfo, RELAY_ALPN, Role, RoomCode, ServerWelcome,
decode_control_frame, encode_control_message,
};
use lanparty_net::RelayEndpoint;
use lanparty_obs::TunnelStats;
#[cfg(target_os = "linux")]
use lanparty_obs::{FrameAction, FrameDirection, FrameLog};
@@ -62,9 +63,9 @@ const MIN_ETHERNET_FRAME_WITHOUT_FCS: usize = 60;
about = "Linux LAN gateway for the LAN party L2 tunnel"
)]
pub struct GatewayArgs {
/// Relay UDP socket address, for example 203.0.113.10:443.
#[arg(long)]
relay: SocketAddr,
/// Relay DNS name or UDP socket address; bare hosts default to UDP/443.
#[arg(long, value_name = "HOST[:PORT]")]
relay: RelayEndpoint,
/// TLS server name expected in the relay certificate.
#[arg(long, default_value = "lanparty-relay.local")]
@@ -96,8 +97,13 @@ impl GatewayArgs {
)
})?;
let relay_addr = self
.relay
.resolve()
.with_context(|| format!("failed to resolve relay endpoint {}", self.relay))?;
GatewayConfig::new(
self.relay,
relay_addr,
self.server_name,
relay_ca_cert,
self.room,
@@ -776,6 +782,7 @@ mod tests {
use std::time::Duration;
use bytes::Bytes;
use lanparty_net::DEFAULT_RELAY_PORT;
use quinn::{ServerConfig, TransportConfig, crypto::rustls::QuicServerConfig};
use rustls::pki_types::{PrivateKeyDer, PrivatePkcs8KeyDer};
@@ -837,7 +844,7 @@ mod tests {
let args = GatewayArgs::parse_from([
"lanparty-gateway",
"--relay",
"127.0.0.1:443",
"relay.example.test",
"--relay-ca-cert",
"relay-cert.der",
"--room",
@@ -846,6 +853,8 @@ mod tests {
"eth0",
]);
assert_eq!(args.relay.host(), "relay.example.test");
assert_eq!(args.relay.port(), DEFAULT_RELAY_PORT);
assert_eq!(args.interface, "eth0");
}