feat(client): persist virtual MAC identity

Remote clients need a stable locally administered MAC address so the relay,
gateway, DHCP lease, and LAN peers keep seeing the same tunnel identity across
runs. Requiring users to pass `--virtual-mac` made that responsibility manual.

Add a platform-neutral client identity store that loads a JSON identity file or
generates a new valid virtual MAC with OS randomness and persists it. The file
stores the MAC in the same string form shown by the CLI. The Windows client now
uses `lanparty-client-identity.json` by default while keeping `--virtual-mac` as
a manual test override.

TAP binding still remains future work; this slice only owns the client identity
that will be assigned to the TAP adapter.

Test Plan:
- cargo fmt --check
- cargo test --workspace
- cargo clippy --workspace --all-targets -- -D warnings
- git diff --check

Refs: PLAN.md MAC identity
This commit is contained in:
2026-05-21 18:35:20 +02:00
parent 25157ad1a6
commit a3d24a1173
6 changed files with 196 additions and 10 deletions
+19 -4
View File
@@ -2,7 +2,9 @@ use std::{fs, net::SocketAddr, path::PathBuf};
use anyhow::{Context, Result};
use clap::Parser;
use lanparty_client_core::{ClientSessionConfig, connect_client};
use lanparty_client_core::{
ClientIdentity, ClientIdentityStore, ClientSessionConfig, connect_client,
};
use lanparty_ctrl::RoomCode;
use lanparty_proto::MacAddr;
@@ -28,9 +30,17 @@ struct ClientArgs {
#[arg(long)]
room: RoomCode,
/// Locally administered unicast MAC address assigned to the TAP adapter.
/// Identity JSON file used to persist the generated virtual MAC.
#[arg(
long = "identity-file",
value_name = "PATH",
default_value = "lanparty-client-identity.json"
)]
identity_file: PathBuf,
/// Override the generated locally administered TAP MAC address.
#[arg(long)]
virtual_mac: MacAddr,
virtual_mac: Option<MacAddr>,
/// Client's advertised QUIC datagram budget before relay clamping.
#[arg(long, default_value_t = 1400)]
@@ -46,12 +56,17 @@ impl ClientArgs {
)
})?;
let identity = match self.virtual_mac {
Some(virtual_mac) => ClientIdentity::new(virtual_mac)?,
None => ClientIdentityStore::new(self.identity_file)?.load_or_create()?,
};
ClientSessionConfig::new(
self.relay,
self.server_name,
relay_ca_cert,
self.room,
self.virtual_mac,
identity.virtual_mac(),
self.max_datagram_size,
)
}