feat(client): retain QUIC datagram diagnostics

Client connection setup already fails when QUIC DATAGRAM is unavailable and
clamps the advertised datagram budget before sending hello. Keep that clamped
value on ClientSession and expose it through QuicDiagnostics so the Windows
client can report the negotiated budget without recomputing handshake details.

The value remains owned by client-core because it is derived from the live
quinn connection and the configured client budget during handshake. TAP and UI
code can sample the snapshot later alongside tunnel counters.

Test Plan:
- cargo fmt --check
- cargo test -p lanparty-client-core
- cargo clippy -p lanparty-client-core --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 20:13:52 +02:00
parent 802fe3d082
commit e8d7cf7ff5
2 changed files with 37 additions and 4 deletions
+1
View File
@@ -47,6 +47,7 @@ Platform-neutral remote client relay session:
- relay QUIC connection with pinned relay certificate trust
- client hello with room, virtual MAC, and datagram budget
- welcome/reject handling with assigned peer id and effective TAP MTU
- QUIC DATAGRAM support and negotiated datagram budget diagnostics
- Ethernet frame send/receive helpers over QUIC DATAGRAM
- client tunnel statistics for frame/datagram rx/tx and drops
+36 -4
View File
@@ -22,7 +22,7 @@ use lanparty_ctrl::{
CONTROL_LENGTH_PREFIX_LEN, ControlMessage, EndpointHello, MAX_CONTROL_MESSAGE_LEN, RELAY_ALPN,
RoomCode, ServerWelcome, decode_control_frame, encode_control_message,
};
use lanparty_obs::TunnelStats;
use lanparty_obs::{QuicDiagnostics, TunnelStats};
use lanparty_proto::{EthernetFrame, FrameType, MacAddr, decode_datagram, encode_datagram};
use quinn::{ClientConfig, Endpoint, crypto::rustls::QuicClientConfig};
use rustls::pki_types::CertificateDer;
@@ -214,6 +214,7 @@ pub struct ClientSession {
connection: quinn::Connection,
config: ClientSessionConfig,
welcome: ServerWelcome,
quic_max_datagram_size: u16,
stats: Arc<ClientTunnelStats>,
}
@@ -246,6 +247,16 @@ impl ClientSession {
&self.welcome
}
#[must_use]
pub const fn quic_max_datagram_size(&self) -> u16 {
self.quic_max_datagram_size
}
#[must_use]
pub const fn quic_diagnostics(&self) -> QuicDiagnostics {
QuicDiagnostics::new(true, Some(self.quic_max_datagram_size))
}
#[must_use]
pub fn relay_io(&self) -> ClientRelayIo {
ClientRelayIo::new(
@@ -415,9 +426,8 @@ pub async fn connect_client(config: ClientSessionConfig) -> Result<ClientSession
let peer_datagram_size = connection
.max_datagram_size()
.context("relay did not negotiate QUIC DATAGRAM support")?;
let hello_datagram_size = usize::from(config.max_datagram_size())
.min(peer_datagram_size)
.min(usize::from(u16::MAX)) as u16;
let hello_datagram_size =
negotiated_quic_datagram_size(config.max_datagram_size(), peer_datagram_size);
let hello = EndpointHello::client(
config.room().clone(),
config.virtual_mac(),
@@ -432,6 +442,7 @@ pub async fn connect_client(config: ClientSessionConfig) -> Result<ClientSession
connection,
config,
welcome,
quic_max_datagram_size: hello_datagram_size,
stats: Arc::default(),
}),
ControlMessage::Reject(reject) => bail!(
@@ -443,6 +454,11 @@ pub async fn connect_client(config: ClientSessionConfig) -> Result<ClientSession
}
}
#[must_use]
fn negotiated_quic_datagram_size(configured: u16, peer: usize) -> u16 {
usize::from(configured).min(peer).min(usize::from(u16::MAX)) as u16
}
fn relay_client_config(relay_ca_cert_der: &[u8]) -> Result<ClientConfig> {
let mut roots = rustls::RootCertStore::empty();
roots
@@ -550,6 +566,16 @@ mod tests {
assert!(identity.virtual_mac().is_valid_client_identity());
}
#[test]
fn clamps_negotiated_quic_datagram_size() {
assert_eq!(negotiated_quic_datagram_size(1400, 1350), 1350);
assert_eq!(negotiated_quic_datagram_size(1300, 1400), 1300);
assert_eq!(
negotiated_quic_datagram_size(u16::MAX, usize::MAX),
u16::MAX
);
}
#[test]
fn identity_store_creates_and_reuses_mac() {
let path = unique_temp_identity_path();
@@ -648,6 +674,12 @@ mod tests {
);
assert_eq!(client.welcome().room_id(), 7);
assert_eq!(client.welcome().peer_id(), 2);
assert!(client.quic_max_datagram_size() <= 1400);
assert!(client.quic_diagnostics().datagram_supported());
assert_eq!(
client.quic_diagnostics().max_datagram_size(),
Some(client.quic_max_datagram_size())
);
let relay_io = client.relay_io();
assert_eq!(relay_io.welcome().peer_id(), 2);