Skip to content

Commit c5a623c

Browse files
Le-Mazramfox
andauthored
feat(iroh)!: add passive mode for mdns discovery (#3401) (#3403)
## Description This pull request adds support for passive mDNS discovery, enabling nodes to listen for peer advertisements without broadcasting their own presence on the local network. ### Summary of changes: - Added `advertise(bool)` setter to `MdnsDiscoveryBuilder` (default `true`). - Modified `MdnsDiscovery::new` and `spawn_discoverer` to respect the `advertise` flag. ## Breaking Changes - `MdnsDiscovery::new(node_id: NodeId)` → `MdnsDiscovery::new(node_id: NodeId, advertise: bool)` Code using `MdnsDiscovery::new` must now provide the `advertise` argument explicitly. ## Notes & Open Questions ## Change Checklist - [x] Self-review. - [x] Documentation updates following the [style guide](https://rust-lang.github.io/rfcs/1574-more-api-documentation-conventions.html#appendix-a-full-conventions-text). - [x] Tests if relevant. - [x] All breaking changes documented. --------- Co-authored-by: ramfox <kasey@n0.computer>
1 parent 048001d commit c5a623c

File tree

1 file changed

+89
-17
lines changed

1 file changed

+89
-17
lines changed

iroh/src/discovery/mdns.rs

Lines changed: 89 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ pub struct MdnsDiscovery {
7373
#[allow(dead_code)]
7474
handle: AbortOnDropHandle<()>,
7575
sender: mpsc::Sender<Message>,
76+
advertise: bool,
7677
/// When `local_addrs` changes, we re-publish our info.
7778
local_addrs: Watchable<Option<NodeData>>,
7879
}
@@ -127,39 +128,73 @@ impl Subscribers {
127128

128129
/// Builder for [`MdnsDiscovery`].
129130
#[derive(Debug)]
130-
pub struct MdnsDiscoveryBuilder;
131+
pub struct MdnsDiscoveryBuilder {
132+
advertise: bool,
133+
}
134+
135+
impl MdnsDiscoveryBuilder {
136+
/// Creates a new [`MdnsDiscoveryBuilder`] with default settings.
137+
pub fn new() -> Self {
138+
Self { advertise: true }
139+
}
140+
141+
/// Sets whether this node should advertise its presence.
142+
///
143+
/// Default is true.
144+
pub fn advertise(mut self, advertise: bool) -> Self {
145+
self.advertise = advertise;
146+
self
147+
}
148+
149+
/// Builds an [`MdnsDiscovery`] instance with the configured settings.
150+
pub fn build(self, node_id: NodeId) -> Result<MdnsDiscovery, IntoDiscoveryError> {
151+
MdnsDiscovery::new(node_id, self.advertise)
152+
}
153+
}
154+
155+
impl Default for MdnsDiscoveryBuilder {
156+
fn default() -> Self {
157+
Self::new()
158+
}
159+
}
131160

132161
impl IntoDiscovery for MdnsDiscoveryBuilder {
133162
fn into_discovery(
134163
self,
135164
context: &DiscoveryContext,
136165
) -> Result<impl Discovery, IntoDiscoveryError> {
137-
MdnsDiscovery::new(context.node_id())
166+
self.build(context.node_id())
138167
}
139168
}
140169

141170
impl MdnsDiscovery {
142171
/// Returns a [`MdnsDiscoveryBuilder`] that implements [`IntoDiscovery`].
143172
pub fn builder() -> MdnsDiscoveryBuilder {
144-
MdnsDiscoveryBuilder
173+
MdnsDiscoveryBuilder::new()
145174
}
146175

147176
/// Create a new [`MdnsDiscovery`] Service.
148177
///
149-
/// This starts a [`Discoverer`] that broadcasts your addresses and receives addresses from other nodes in your local network.
178+
/// This starts a [`Discoverer`] that broadcasts your addresses (if advertise is set to true)
179+
/// and receives addresses from other nodes in your local network.
150180
///
151181
/// # Errors
152182
/// Returns an error if the network does not allow ipv4 OR ipv6.
153183
///
154184
/// # Panics
155185
/// This relies on [`tokio::runtime::Handle::current`] and will panic if called outside of the context of a tokio runtime.
156-
pub fn new(node_id: NodeId) -> Result<Self, IntoDiscoveryError> {
186+
pub fn new(node_id: NodeId, advertise: bool) -> Result<Self, IntoDiscoveryError> {
157187
debug!("Creating new MdnsDiscovery service");
158188
let (send, mut recv) = mpsc::channel(64);
159189
let task_sender = send.clone();
160190
let rt = tokio::runtime::Handle::current();
161-
let discovery =
162-
MdnsDiscovery::spawn_discoverer(node_id, task_sender.clone(), BTreeSet::new(), &rt)?;
191+
let discovery = MdnsDiscovery::spawn_discoverer(
192+
node_id,
193+
advertise,
194+
task_sender.clone(),
195+
BTreeSet::new(),
196+
&rt,
197+
)?;
163198

164199
let local_addrs: Watchable<Option<NodeData>> = Watchable::default();
165200
let mut addrs_change = local_addrs.watch();
@@ -311,13 +346,15 @@ impl MdnsDiscovery {
311346
let handle = task::spawn(discovery_fut.instrument(info_span!("swarm-discovery.actor")));
312347
Ok(Self {
313348
handle: AbortOnDropHandle::new(handle),
349+
advertise,
314350
sender: send,
315351
local_addrs,
316352
})
317353
}
318354

319355
fn spawn_discoverer(
320356
node_id: PublicKey,
357+
advertise: bool,
321358
sender: mpsc::Sender<Message>,
322359
socketaddrs: BTreeSet<SocketAddr>,
323360
rt: &tokio::runtime::Handle,
@@ -337,15 +374,17 @@ impl MdnsDiscovery {
337374
sender.send(Message::Discovery(node_id, peer)).await.ok();
338375
});
339376
};
340-
let addrs = MdnsDiscovery::socketaddrs_to_addrs(&socketaddrs);
341377
let node_id_str = data_encoding::BASE32_NOPAD
342378
.encode(node_id.as_bytes())
343379
.to_ascii_lowercase();
344380
let mut discoverer = Discoverer::new_interactive(N0_LOCAL_SWARM.to_string(), node_id_str)
345381
.with_callback(callback)
346382
.with_ip_class(IpClass::Auto);
347-
for addr in addrs {
348-
discoverer = discoverer.with_addrs(addr.0, addr.1);
383+
if advertise {
384+
let addrs = MdnsDiscovery::socketaddrs_to_addrs(&socketaddrs);
385+
for addr in addrs {
386+
discoverer = discoverer.with_addrs(addr.0, addr.1);
387+
}
349388
}
350389
discoverer
351390
.spawn(rt)
@@ -406,7 +445,9 @@ impl Discovery for MdnsDiscovery {
406445
}
407446

408447
fn publish(&self, data: &NodeData) {
409-
self.local_addrs.set(Some(data.clone())).ok();
448+
if self.advertise {
449+
self.local_addrs.set(Some(data.clone())).ok();
450+
}
410451
}
411452

412453
fn subscribe(&self) -> Option<BoxStream<DiscoveryItem>> {
@@ -440,8 +481,10 @@ mod tests {
440481
#[tokio::test]
441482
#[traced_test]
442483
async fn mdns_publish_resolve() -> Result {
443-
let (_, discovery_a) = make_discoverer()?;
444-
let (node_id_b, discovery_b) = make_discoverer()?;
484+
// Create discoverer A with advertise=false (only listens)
485+
let (_, discovery_a) = make_discoverer(false)?;
486+
// Create discoverer B with advertise=true (will broadcast)
487+
let (node_id_b, discovery_b) = make_discoverer(true)?;
445488

446489
// make addr info for discoverer b
447490
let user_data: UserData = "foobar".parse()?;
@@ -477,11 +520,11 @@ mod tests {
477520
let mut node_ids = BTreeSet::new();
478521
let mut discoverers = vec![];
479522

480-
let (_, discovery) = make_discoverer()?;
523+
let (_, discovery) = make_discoverer(false)?;
481524
let node_data = NodeData::new(None, BTreeSet::from(["0.0.0.0:11111".parse().unwrap()]));
482525

483526
for i in 0..num_nodes {
484-
let (node_id, discovery) = make_discoverer()?;
527+
let (node_id, discovery) = make_discoverer(true)?;
485528
let user_data: UserData = format!("node{i}").parse()?;
486529
let node_data = node_data.clone().with_user_data(Some(user_data.clone()));
487530
node_ids.insert((node_id, Some(user_data)));
@@ -513,9 +556,38 @@ mod tests {
513556
.context("timeout")?
514557
}
515558

516-
fn make_discoverer() -> Result<(PublicKey, MdnsDiscovery)> {
559+
#[tokio::test]
560+
#[traced_test]
561+
async fn non_advertising_node_not_discovered() -> Result {
562+
let (_, discovery_a) = make_discoverer(false)?;
563+
let (node_id_b, discovery_b) = make_discoverer(false)?;
564+
565+
let (node_id_c, discovery_c) = make_discoverer(true)?;
566+
let node_data_c =
567+
NodeData::new(None, BTreeSet::from(["0.0.0.0:22222".parse().unwrap()]));
568+
discovery_c.publish(&node_data_c);
569+
570+
let node_data_b =
571+
NodeData::new(None, BTreeSet::from(["0.0.0.0:11111".parse().unwrap()]));
572+
discovery_b.publish(&node_data_b);
573+
574+
let mut stream_c = discovery_a.resolve(node_id_c).unwrap();
575+
let result_c = tokio::time::timeout(Duration::from_secs(2), stream_c.next()).await;
576+
assert!(result_c.is_ok(), "Advertising node should be discoverable");
577+
578+
let mut stream_b = discovery_a.resolve(node_id_b).unwrap();
579+
let result_b = tokio::time::timeout(Duration::from_secs(2), stream_b.next()).await;
580+
assert!(
581+
result_b.is_err(),
582+
"Expected timeout since node b isn't advertising"
583+
);
584+
585+
Ok(())
586+
}
587+
588+
fn make_discoverer(advertise: bool) -> Result<(PublicKey, MdnsDiscovery)> {
517589
let node_id = SecretKey::generate(rand::thread_rng()).public();
518-
Ok((node_id, MdnsDiscovery::new(node_id)?))
590+
Ok((node_id, MdnsDiscovery::new(node_id, advertise)?))
519591
}
520592
}
521593
}

0 commit comments

Comments
 (0)