Skip to main content

LanDiscovery

Struct LanDiscovery 

Source
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: bool

Whether the monitor channel was obtained. Without it announced_v4 never fills, so health() has no basis for a verdict and stays Pending.

§started_at: Instant

When this daemon was built, for the start-up grace in health().

Implementations§

Source§

impl LanDiscovery

Source

fn own_record(&self) -> InstanceRecord

Source

pub(super) fn publish_instance_record(&self)

Write (or refresh) this instance’s record, unless it has been withdrawn.

Source

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.

Source

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.

Source

pub(super) fn sibling_entries(&self) -> Vec<Value>

The siblings of a probe reply, as JSON, read fresh on each probe.

Source§

impl LanDiscovery

Source

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.

Source

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.

Source

async fn handle_datagram( &self, socket: &UdpSocket, own_port: u16, bytes: &[u8], src: SocketAddr, )

Source§

impl LanDiscovery

Source

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.

Source

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.

Source

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).

Source

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.

Source

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.

Source

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.

Source

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.

Source

fn probe_response_with( &self, install_id: Option<&str>, channels_running: Option<u32>, viewer_port: Option<u16>, ) -> Value

Source

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.

Source

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.

Source

fn event_loop(&self, receiver: Receiver<ServiceEvent>)

Source

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.

Source

fn handle_event(&self, event: ServiceEvent)

Source

fn broadcast_instances(&self)

Source

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.

Source

pub fn get_instances(&self) -> Vec<LanInstance>

Get current list of discovered LAN peers (excludes self).

Source

pub fn peer_count(&self) -> usize

Get peer count (excludes self).

Source

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

Source§

fn drop(&mut self)

Executes the destructor for this type. Read more
Source§

fn pin_drop(self: Pin<&mut Self>)

🔬This is a nightly-only experimental API. (pin_ergonomics)
Execute the destructor for this type, but different to Drop::drop, it requires self to be pinned. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
§

impl<T> Downcast for T
where T: Any,

§

fn into_any(self: Box<T>) -> Box<dyn Any>

Convert 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>

Convert 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)

Convert &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)

Convert &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
where T: Any + Send + Sync,

§

fn into_any_arc(self: Arc<T>) -> Arc<dyn Any + Send + Sync> ⓘ

Convert Arc<Trait> (where Trait: Downcast) to Arc<Any>. Arc<Any> can then be further downcast into Arc<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

§

impl<T> Instrument for T

§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided [Span], returning an Instrumented wrapper. Read more
§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts 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 more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts 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
§

impl<T> PolicyExt for T
where T: ?Sized,

§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] only if self and other return Action::Follow. Read more
§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns [Action::Follow] if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<T> WithSubscriber for T

§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a [WithDispatch] wrapper. Read more
§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a [WithDispatch] wrapper. Read more