pub struct LanDiscovery {Show 18 fields
daemon: ServiceDaemon,
instances: Arc<RwLock<HashMap<String, LanInstance>>>,
instance_id: String,
event_bus: Arc<EventBus>,
service_fullname: String,
auth_key: String,
hostname: String,
version: String,
port: u16,
udp_cancel: Mutex<Option<Sender<()>>>,
udp_peer_cancel: Mutex<Option<Sender<()>>>,
agent_names_cancel: Mutex<Option<Sender<()>>>,
instance_record_cancel: Mutex<Option<Sender<()>>>,
instances_dir: Option<PathBuf>,
instance_record_live: Mutex<bool>,
announced_v4: Arc<Mutex<BTreeSet<Ipv4Addr>>>,
monitored: bool,
started_at: Instant,
}Fields§
§daemon: ServiceDaemon§instances: Arc<RwLock<HashMap<String, LanInstance>>>§instance_id: String§event_bus: Arc<EventBus>§service_fullname: String§auth_key: String§hostname: String§version: String§port: u16§udp_cancel: Mutex<Option<Sender<()>>>Cancellation half for the UDP responder task spawned in start().
None once shutdown() has fired. Set even if the UDP socket never
binds (udp_responder_loop then just exits, and shutdown()’s send
is a harmless no-op). Guarded by a sync mutex since
shutdown() is &self and called from both an explicit live-toggle
path and Drop.
udp_peer_cancel: Mutex<Option<Sender<()>>>Cancellation half for the desktop-to-desktop UDP discovery task
(udp_peers). Same contract as udp_cancel: the task holds its own
Arc<LanDiscovery>, so dropping the controller’s Arc does not stop it.
agent_names_cancel: Mutex<Option<Sender<()>>>Cancels agent_names_refresh_loop. Needed for the same reason
udp_cancel is: that task holds its own Arc<LanDiscovery> clone, so
dropping the controller’s Arc on apply(false) never reaches refcount
zero and the loop would otherwise keep issuing authenticated requests
to peers forever — leaking one orphaned task per disable/enable cycle.
instance_record_cancel: Mutex<Option<Sender<()>>>Cancels instance_record_loop. Same contract as udp_cancel.
instances_dir: Option<PathBuf>Where this instance’s lan_instances record is written; None when
there is nowhere to write it (see lan_instances::instances_dir).
instance_record_live: Mutex<bool>true while the record may be (re)written; cleared for good by
withdraw_instance_record.
announced_v4: Arc<Mutex<BTreeSet<Ipv4Addr>>>IPv4 addresses mdns-sd reported announcing our service on, from its
monitor channel. An interface whose socket failed to bind never appears
here (see lan_mdns_health). Filled by monitor_loop.
monitored: boolWhether the monitor channel was obtained. Without it announced_v4 never
fills, so health() has no basis for a verdict and stays Pending.
started_at: InstantWhen this daemon was built, for the start-up grace in health().
Implementations§
Source§impl LanDiscovery
impl LanDiscovery
fn own_record(&self) -> InstanceRecord
Sourcepub(super) fn publish_instance_record(&self)
pub(super) fn publish_instance_record(&self)
Write (or refresh) this instance’s record, unless it has been withdrawn.
Sourcepub(super) fn withdraw_instance_record(&self)
pub(super) fn withdraw_instance_record(&self)
Remove this instance’s record and stop it being written again. The
flag is cleared under the same lock publish_instance_record holds, so
a rewrite racing a stop cannot bring the file back. Idempotent.
Sourcepub(super) async fn instance_record_loop(self: Arc<Self>, cancel: Receiver<()>)
pub(super) async fn instance_record_loop(self: Arc<Self>, cancel: Receiver<()>)
Rewrites the record every REWRITE_INTERVAL until cancelled by
shutdown(). Holds its own Arc, like the other loops start() spawns.
Sourcepub(super) fn sibling_entries(&self) -> Vec<Value>
pub(super) fn sibling_entries(&self) -> Vec<Value>
The siblings of a probe reply, as JSON, read fresh on each probe.
Source§impl LanDiscovery
impl LanDiscovery
Sourcepub(super) async fn udp_peer_loop(self: Arc<Self>, cancel: Receiver<()>)
pub(super) async fn udp_peer_loop(self: Arc<Self>, cancel: Receiver<()>)
Bind the discovery socket and run Self::udp_peer_loop_on over it. A
failed bind (most likely another local instance holds the port) is
logged at debug and ends the task; mDNS is unaffected.
Sourcepub(super) async fn udp_peer_loop_on(
self: Arc<Self>,
socket: UdpSocket,
targets: impl Fn() -> Vec<SocketAddr>,
every: Duration,
cancel: Receiver<()>,
)
pub(super) async fn udp_peer_loop_on( self: Arc<Self>, socket: UdpSocket, targets: impl Fn() -> Vec<SocketAddr>, every: Duration, cancel: Receiver<()>, )
The loop proper, over a socket and target list the caller supplies so a test can drive two instances on loopback without the production port.
async fn handle_datagram( &self, socket: &UdpSocket, own_port: u16, bytes: &[u8], src: SocketAddr, )
Source§impl LanDiscovery
impl LanDiscovery
Sourcepub fn start(
instance_id: String,
hostname: String,
version: String,
port: u16,
auth_key: String,
event_bus: Arc<EventBus>,
) -> Result<Arc<Self>, String>
pub fn start( instance_id: String, hostname: String, version: String, port: u16, auth_key: String, event_bus: Arc<EventBus>, ) -> Result<Arc<Self>, String>
Start LAN discovery: register this instance and browse for peers.
Sourceasync fn agent_names_refresh_loop(self: Arc<Self>, cancel: Receiver<()>)
async fn agent_names_refresh_loop(self: Arc<Self>, cancel: Receiver<()>)
Periodically ask every known peer which agents it hosts, so
LanInstance::agents reflects reality instead of staying the empty
vec it’s created with. Without this, a peer shows up with a correct
hostname/version/address but agents: [], so there is no way to
discover which agents live on another machine — you can only
address one blind by name and hope. See
handle_reactive_agent_names’ doc comment for the security scoping
of the endpoint this calls (names only, lan_key-readable).
Failures are deliberately non-destructive: a peer that’s briefly
unreachable keeps its last-known list rather than being blanked, since
staleness is already tracked by last_seen and a flapping list is
worse than a slightly-old one.
Sourceasync fn fetch_peer_agent_names(
http: &Client,
base_url: &str,
lan_key: &str,
) -> Option<PeerAgents>
async fn fetch_peer_agent_names( http: &Client, base_url: &str, lan_key: &str, ) -> Option<PeerAgents>
One peer’s contribution to agent_names_refresh_loop. Returns None
on any failure (unreachable, non-2xx, oversized, or malformed body) —
this list is display-only, so a bad peer response should leave the
last-known list in place rather than blank it (see the loop’s own
“non-destructive” framing).
Sourcepub(crate) async fn read_body_capped(
resp: Response,
max_bytes: usize,
) -> Result<Vec<u8>, String>
pub(crate) async fn read_body_capped( resp: Response, max_bytes: usize, ) -> Result<Vec<u8>, String>
Reads an HTTP response body up to max_bytes, erroring out as soon as
the cap is exceeded rather than buffering the full body first —
Response::bytes()/.json() have no size limit of their own, so a
peer that responds successfully but with an enormous body would
otherwise be read into memory in full before this code ever gets a
chance to reject it.
Sourcefn build_identity_response(&self) -> Value
fn build_identity_response(&self) -> Value
Build the JSON identity payload for this instance — see
probe_response_json for the pure field-assembly logic shared with
tests. Desktop peers get this (udp_peers); phones get
Self::build_probe_response.
Sourcefn identity_response_with(
&self,
install_id: Option<&str>,
channels_running: Option<u32>,
viewer_port: Option<u16>,
) -> Value
fn identity_response_with( &self, install_id: Option<&str>, channels_running: Option<u32>, viewer_port: Option<u16>, ) -> Value
Self::build_identity_response with the install id, channel count
and viewer port given, so a test needs no WAN identity or fleet feed.
Sourcefn build_probe_response(&self) -> Value
fn build_probe_response(&self) -> Value
The 47891 reply: the identity payload plus siblings, the other
LAN-enabled channels on this host (lan_instances), kept within one
datagram (lan_instances::MAX_REPLY_BYTES). Each sibling carries its
own lan_key, which it already broadcasts over mDNS.
fn probe_response_with( &self, install_id: Option<&str>, channels_running: Option<u32>, viewer_port: Option<u16>, ) -> Value
Sourceasync fn udp_responder_loop(self: Arc<Self>, cancel_rx: Receiver<()>)
async fn udp_responder_loop(self: Arc<Self>, cancel_rx: Receiver<()>)
UDP broadcast-probe responder loop (Layer 2 discovery fallback).
Binds 0.0.0.0:UDP_DISCOVERY_PORT and answers valid probes from
private/link-local source addresses (see is_lan_source) with a
unicast response back to the sender’s recv_from source address.
Deliberately does NOT set SO_REUSEADDR: this codebase supports
running multiple AgentMux instances on one host simultaneously, and
on Windows SO_REUSEADDR lets a second process silently steal a UDP
port already owned by another — an inappropriate risk for a socket
that hands out auth_key. If the bind fails (most likely because
another local instance already holds the port), this task retries
every UDP_RESPONDER_RETRY until it gets the port or is cancelled, so
the next LAN-enabled channel takes over when the holder stops. mDNS
discovery (already running via event_loop) is unaffected either way,
matching the “never fail start() over this” contract.
We do not call set_broadcast(true): that flag is only required to
send to a broadcast address, and this socket only receives (probes
arrive as broadcast/subnet-broadcast datagrams addressed to us, which
requires no special socket option on the receiving end) and replies
with a plain unicast send back to the probe’s source address.
Sourceasync fn udp_responder_loop_on(
self: Arc<Self>,
port: u16,
retry_every: Duration,
cancel_rx: Receiver<()>,
)
async fn udp_responder_loop_on( self: Arc<Self>, port: u16, retry_every: Duration, cancel_rx: Receiver<()>, )
Self::udp_responder_loop on a given port and retry interval, so a
test can drive the takeover without the production port.
fn event_loop(&self, receiver: Receiver<ServiceEvent>)
Sourcefn resolves_to_this_instance(&self, info: &ServiceInfo) -> bool
fn resolves_to_this_instance(&self, info: &ServiceInfo) -> bool
True when a resolved service is really this instance, reached over one of this host’s own addresses.
Both conditions are required. The port alone is far too weak (two hosts can land on the same ephemeral port), and an address alone is too weak while several AgentMux instances share a machine — but no remote peer can be listening on OUR port at an address that is OURS.
fn handle_event(&self, event: ServiceEvent)
fn broadcast_instances(&self)
Sourcepub fn health(&self) -> MdnsHealth
pub fn health(&self) -> MdnsHealth
Is this instance really announced on an IPv4 interface other machines
can reach? Pending until the start-up grace has passed.
Sourcepub fn get_instances(&self) -> Vec<LanInstance>
pub fn get_instances(&self) -> Vec<LanInstance>
Get current list of discovered LAN peers (excludes self).
Sourcepub fn peer_count(&self) -> usize
pub fn peer_count(&self) -> usize
Get peer count (excludes self).
Sourcepub fn shutdown(&self)
pub fn shutdown(&self)
Stop the mDNS daemon and the UDP broadcast-probe responder.
The mDNS side synchronously closes the daemon socket (UDP:5353),
causing the browse_receiver to return Err and the event-loop thread
spawned by start() to exit. The UDP responder is stopped by firing
its cancellation channel, which the tokio::select! in
udp_responder_loop is racing against recv_from — this is what
makes a live setting toggle actually stop responding to probes, not
just stop the mDNS advertisement.
Required for live-disable to actually stop discovery: both background
tasks hold their own Arc<Self> clone, so simply dropping the
controller’s Arc never reaches refcount zero and Drop does not
run. Callers must invoke shutdown() before clearing their Arc.
Idempotent — safe to call from both the explicit path and Drop.
Trait Implementations§
Source§impl Drop for LanDiscovery
impl Drop for LanDiscovery
Auto Trait Implementations§
impl !Freeze for LanDiscovery
impl !RefUnwindSafe for LanDiscovery
impl !UnwindSafe for LanDiscovery
impl Send for LanDiscovery
impl Sync for LanDiscovery
impl Unpin for LanDiscovery
impl UnsafeUnpin for LanDiscovery
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.§impl<T> DowncastSync for T
impl<T> DowncastSync for T
impl<A, B, T> HttpServerConnExec<A, B> for Twhere
B: Body,
§impl<T> Instrument for T
impl<T> Instrument for T
§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more