Expand description
LAN instance discovery via mDNS/DNS-SD.
Each AgentMux backend advertises itself as _agentmux._tcp.local. and
continuously browses for peers. Discovered instances are tracked in memory
and broadcast to frontend clients via EventBus.
Re-exports§
pub use events::LanDiscoverability;pub use events::LanDiscoveryError;pub use events::LanInstanceList;
Modules§
- agent_
names 🔒 - Reading an instance’s
/agentmux/reactive/agent-namesanswer: the names a LAN peer’s poll (and the Swarm’s ask of this machine’s other channels) list, and each listed agent’s state and summary line, for the Swarm’s remote rows (SPEC_SWARM_OTHER_HOSTS_AND_CHANNELS_2026_10_02.md §3.4 Phase 4). - events 🔒
- The LAN discovery events’ payloads (
mps_events:laninstances,:error,:health). - lan_
instances 🔒 - A host-global record of this machine’s LAN-enabled instances, so the one
instance that holds the UDP probe port can list the others as
siblingsin its reply (docs/specs/SPEC_LAN_FLEET_FEED_2026_10_03.md§3, which links the mobile spec that owns the wire contract). - udp_
peers 🔒 - Desktop-to-desktop discovery that does not depend on mDNS
(
docs/specs/SPEC_LAN_FIREWALL_SETUP_2026_10_01.md§4.7 item 5,docs/specs/SPEC_LAN_UDP_PEER_DISCOVERY_2026_10_02.md).
Structs§
- LanCache
Entry 🔒 - LanDiscovery
- LanDiscovery
Controller - Controller for live start/stop of
LanDiscoveryin response to setting changes. - LanInstance
- LanPubkey
Cache 🔒Entry - Lookup
Rate 🔒Limiter - Minimal token bucket, refilled once per second — same shape as
backend::reactive::handler::RateLimiter, duplicated locally rather than widening that one’s visibility across modules for a single caller.
Enums§
- LanPubkey
Lookup 🔒 - Outcome of
find_agent_lan_pubkey. Three states, not two — reagentx P0 follow-up on the LAN signing PR: collapsing “the lookup was skipped (rate-limited)” into the sameNone/“not found” outcome as “genuinely no peer has this key” let an attacker exhaustLAN_PUBKEY_LOOKUP_RATE_LIMITwith junk lookups, then slip a forged signature for a REAL agent’s identity through as unverified (benign) instead of a verification failure (forced sensitive). By the time this function is ever called,verify_lan_signaturehas already confirmed alan_sigwas actually presented — so “we didn’t check” must be distinguishable from “there was nothing to check.”
Constants§
- HEALTH_
TICK 🔒 - How often the watchdog looks at discoverability. Two consecutive
undiscoverable ticks are needed before a rebuild
(
lan_mdns_health::STRIKES_BEFORE_REBUILD). - LAN_
AGENT_ 🔒CACHE_ TTL_ SECS - LAN_
AGENT_ 🔒NAMES_ MAX_ COUNT - Defense in depth past the byte cap above: even a response that fits in 64 KiB could be an adversarially compact array of many short strings.
- LAN_
AGENT_ 🔒NAMES_ MAX_ NAME_ LEN - A legitimate agent name is short; anything absurdly long is more likely an attempt to waste memory than a real name, so it’s dropped rather than truncated (truncating could quietly merge two distinct long names).
- LAN_
AGENT_ 🔒NAMES_ MAX_ RESPONSE_ BYTES - Hard cap on a single peer’s
/agentmux/reactive/agent-namesresponse body (Codex P1 on PR #3102): thelan_keya peer advertised authenticates THIS client TO that peer, not the peer to us — anything that can fake an mDNS advertisement for_agentmux._tcp.localmakes it intoself.instancesand gets polled by this loop. Enforced while reading, not viaContent-Length(a malicious peer can omit or lie about that while still streaming an unbounded chunked body). 64 KiB is generous for a plain name list — real fleets are agents-per-machine, not per-thousand. - LAN_
AGENT_ 🔒NAMES_ REFRESH_ SECS - How often each known LAN peer is asked for its agent-name list. Peers are
few (one per machine) and the request is a single small GET, so this is
cheap; it’s deliberately slower than
LAN_AGENT_CACHE_TTL_SECSbecause this list is for display (“who lives on that host”), not for delivery routing —find_agentstill does its own authoritative lookup. - LAN_
PEER_ 🔒QUERY_ TIMEOUT_ SECS - LAN_
PEER_ 🔒STALE_ TIMEOUT_ CEIL_ SECS - Ceiling on
other_ttl_secs-driven staleness (ReAgent P1, PR #3301):other_ttl_secsis taken directly from a peer’s own mDNS advertisement, which this file already treats as adversarial elsewhere (seeLAN_AGENT_NAMES_MAX_*above — “anything that can fake an mDNS advertisement… makes it intoself.instances”). Without a cap, a spoofed peer advertisingother_ttlnearu32::MAX(~136 years) would be honored by bothget_instances()and the periodic GC, permanently pinning it as “alive” — defeating the entire self-pruning design this exists for. 2x the RFC 6762 §10 / mdns-sd 0.12 default (4500s) comfortably covers every legitimate cadence actually observed on this LAN (narko/starpower’s ~46min gaps) with margin, while bounding attacker-controlled input the same way the byte/count/length caps above already do. - LAN_
PEER_ 🔒STALE_ TIMEOUT_ FLOOR_ SECS - How long a peer is kept after its most recent
ServiceResolvedbeforeget_instances()treats it as gone. mdns-sd’s ownServiceRemovedevent is NOT used for this (see theServiceRemovedarm ofhandle_eventbelow) — live logs showed it firing on ordinary TTL churn seconds before the same peer re-resolved, and every re-resolution after aremove()came back with a blank TXT record, so deleting onServiceRemovedpermanently wiped a live peer’s hostname/instance_id/version/auth_key for no real disappearance. - LAN_
PUBKEY_ 🔒LOOKUP_ RATE_ LIMIT - Fan-outs per second
find_agent_lan_pubkeywill perform before failing closed (returningNonewithout querying any peer). Generous for legitimate traffic — LAN jekts to previously-unseen senders are rare once agents have been talking a while, and the 60s positive/negative cache absorbs repeat lookups for the SAME agent_id — while still bounding the worst case to a small, fixed number of outbound requests per second regardless of how many distinct agent_ids are probed. - SERVICE_
TYPE 🔒 - UDP_
DISCOVERY_ 🔒PORT - UDP broadcast discovery fallback (Layer 2), for LANs where mDNS multicast is filtered (common on corporate/guest WiFi). Mobile clients broadcast a small JSON probe to this port; any listening desktop instance unicasts a response straight back to the sender’s source address.
- UDP_
PROBE_ 🔒TYPE - Wire-protocol
typevalue a probe datagram must carry. - UDP_
PROTOCOL_ 🔒VERSION - Wire-protocol version. Bump alongside the mobile client if the schema
changes;
is_valid_proberejects anything else. - UDP_
RESPONDER_ 🔒RETRY - How often an instance that could not bind
UDP_DISCOVERY_PORTtries again, so another LAN-enabled channel on this host takes over the probe port when its holder stops (SPEC_LAN_FLEET_FEED_2026_10_03.md §3). - UDP_
RESPONSE_ 🔒TYPE - Wire-protocol
typevalue this responder replies with.
Functions§
- is_
lan_ 🔒source - Trust-boundary check for the UDP responder: is
addrreachable only from a private/link-local network, i.e. plausibly on this LAN? Unlike mDNS (link-local multicast, structurally non-routable off-subnet), this socket binds0.0.0.0and would otherwise answer any routable unicast probe withauth_key— an internet-facing disclosure oracle / UDP reflection vector if the port is ever reachable externally (port forward, hairpin NAT). Mirrors the private/loopback/link-local classification used for the opposite purpose (SSRF egress checks) indrone/executor/blocks/api.rs::is_reserved_v4/is_reserved_v6, minus the broadcast/unspecified/multicast cases that don’t apply to a source addr. - is_
probe_ 🔒version_ match - Match the probe’s
vfield againstUDP_PROTOCOL_VERSION, accepting either JSON integer or float encoding. Some JSON serializers (e.g. a client whose numeric type is inferred asdouble) emit a whole number like1as1.0;Value::as_u64()alone returnsNonefor that representation, which would silently drop an otherwise-conformant probe. - is_
self_ 🔒resolution - Pure predicate behind
LanDiscovery::resolves_to_this_instance, split out so it can be tested without standing up an mDNSServiceDaemon. - is_
valid_ 🔒probe - Validate a received UDP datagram as an in-protocol discovery probe.
Anything that fails to parse as JSON, or does not carry the exact
type/vfields, is treated as noise (unrelated LAN traffic or a future/older protocol version) and silently ignored by the caller. - mdns_
hostname 🔒 - Normalize an OS hostname into a valid mDNS host name by appending the
.local.suffix that mdns-sd’sServiceInfo::newrequires. Idempotent — already-normalized inputs pass through unchanged. We also strip any trailing dot first so"foo.local"doesn’t end up as"foo.local..local.". - mdns_
instance_ 🔒label - Build this instance’s DNS-SD instance name — the first label of the service fullname. Must be BOTH unique per running instance AND free of dots.
- monitor_
loop 🔒 - Reads the daemon’s monitor channel and records the IPv4 addresses it announced our service on. Ends when the daemon shuts down (the channel disconnects). Runs on a blocking thread, like the browse loop.
- peer_
staleness_ 🔒window_ secs - A peer is stale once
last_seenexceeds ITS OWN advertisedother_ttl— the standards-defined lifetime of the PTR/TXT records that make up an mDNS announcement — not one fixed cutoff shared by every peer regardless of how often it actually re-announces. Clamped betweenLAN_PEER_STALE_TIMEOUT_FLOOR_SECS(guards an implausibly small advertised TTL) andLAN_PEER_STALE_TIMEOUT_CEIL_SECS(guards an adversarially large one —other_ttl_secsis untrusted peer input). - probe_
response_ 🔒json - Build the JSON response payload for a valid probe. Pure/free function
(no
&self) so it is trivially testable without spinning up a fullLanDiscovery(which owns a real mDNSServiceDaemon).LanDiscovery’s production loop calls this viabuild_probe_response. - query_
peers_ 🔒concurrently - Query EVERY eligible LAN peer concurrently for
agent_id, yielding results as they arrive. - record_
daemon_ 🔒event - Folds one daemon event into the set of IPv4 addresses we announced on.
Type Aliases§
- Peer
Query 🔒Outcome - One in-flight
GET /agentmux/reactive/agent?id=…against a single peer.