feat(core,host): Wake-on-LAN sender + host MAC advertisement

Add a runtime-free Wake-on-LAN sender in punktfunk-core (per-interface subnet-directed broadcast + 255.255.255.255 on ports 9/7, repeated, optional last-known-IP unicast) exposed both as a Rust fn and a punktfunk_wake_on_lan C-ABI (ABI v3), plus a parse_mac helper. The host enumerates its wake-capable NIC MAC(s) and advertises them in a new mDNS `mac` TXT record (routed NIC first), and best-effort detects & warns (never modifies) when the NIC isn't armed for WoL.

MAC delivery is via the unauthenticated mDNS TXT rather than the connection handshake by design: a spoofed MAC only makes a wake fail (the packet is inert; the cert fingerprint still gates the connection), and it avoids threading through the hot connect path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-04 13:37:02 +02:00
parent 097cc6faf4
commit 22c0d92f2e
10 changed files with 450 additions and 5 deletions
+20 -1
View File
@@ -17,7 +17,9 @@
//
// v2: `punktfunk_connect` gained `client_cert_pem`/`client_key_pem` (pairing identities);
// added `punktfunk_pair` / `punktfunk_generate_identity` / `punktfunk_connection_request_mode`.
#define ABI_VERSION 2
// v3: added `punktfunk_wake_on_lan` (Wake-on-LAN magic packet; the host's wake MAC(s) reach
// clients out-of-band via the mDNS `mac` TXT record, so no connection is required to wake).
#define ABI_VERSION 3
// `PunktfunkHidOutput::kind` — lightbar RGB (`r`/`g`/`b` valid).
#define PUNKTFUNK_HIDOUT_LED 1
@@ -804,6 +806,23 @@ extern "C" {
// Current ABI version. Mismatch with [`crate::ABI_VERSION`] means incompatible core.
uint32_t punktfunk_abi_version(void);
// Send a Wake-on-LAN magic packet to wake sleeping host NIC(s).
//
// `macs` points to `mac_count` contiguous 6-byte MAC addresses (`mac_count * 6` bytes total) —
// a host may report several NICs; all are woken. `last_known_ip`, if non-NULL, is an IPv4
// dotted-quad string additionally targeted by unicast (pass NULL to skip). The packet is
// broadcast to every local interface's subnet-directed broadcast and to `255.255.255.255` on
// ports 9 and 7. This does NOT require an open connection and is not part of the QUIC surface.
//
// Returns `Ok` if at least one datagram was sent. Call off the UI thread.
//
// # Safety
// `macs` must point to at least `mac_count * 6` readable bytes. `last_known_ip`, if non-NULL,
// must be a NUL-terminated string.
PunktfunkStatus punktfunk_wake_on_lan(const uint8_t *macs,
uintptr_t mac_count,
const char *last_known_ip);
// Create a session over a real UDP transport (`local`/`peer` are `host:port` strings).
// Returns NULL on error.
//