Skip to content

Commit 2306ca6

Browse files
committed
Close #742: Allow limitting of peer discovery to specific interfaces
Signed-off-by: Lee Smet <lee.smet@hotmail.com>
1 parent 20a3038 commit 2306ca6

File tree

5 files changed

+131
-37
lines changed

5 files changed

+131
-37
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
- You can now limit peer discovery to specific interfaces by specifying the `--peer-discovery-interface <interface_name>`.
13+
The flag can be specified multiple times to allow multiple interfaces.
14+
1015
### Changed
1116

1217
- No longer block the router when a subnet is being queried until the query resolves

mycelium/src/lib.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use message::{
1717
MessageId, MessageInfo, MessagePushResponse, MessageStack, PushMessageError, ReceivedMessage,
1818
};
1919
use metrics::Metrics;
20-
use peer_manager::{PeerExists, PeerNotFound, PeerStats, PrivateNetworkKey};
20+
use peer_manager::{PeerDiscoveryMode, PeerExists, PeerNotFound, PeerStats, PrivateNetworkKey};
2121
use routing_table::{NoRouteSubnet, QueriedSubnet, RouteEntry};
2222
use subnet::Subnet;
2323
use tokio::net::TcpListener;
@@ -70,7 +70,9 @@ pub struct Config<M> {
7070
/// Listen port for Quic connections.
7171
pub quic_listen_port: Option<u16>,
7272
/// Udp port for peer discovery.
73-
pub peer_discovery_port: Option<u16>,
73+
pub peer_discovery_port: u16,
74+
/// Mode for peer discovery (All, Disabled, or Filtered).
75+
pub peer_discovery_mode: PeerDiscoveryMode,
7476
/// Name for the TUN device.
7577
#[cfg(any(
7678
target_os = "linux",
@@ -200,8 +202,8 @@ where
200202
config.peers,
201203
config.tcp_listen_port,
202204
config.quic_listen_port,
203-
config.peer_discovery_port.unwrap_or_default(),
204-
config.peer_discovery_port.is_none(),
205+
config.peer_discovery_port,
206+
config.peer_discovery_mode,
205207
config.private_network_config,
206208
config.metrics,
207209
config.firewall_mark,

mycelium/src/peer_manager.rs

Lines changed: 54 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,19 @@ pub enum PeerType {
7676
Inbound,
7777
}
7878

79+
/// Defines how peer discovery operates regarding network interfaces.
80+
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
81+
#[serde(rename_all = "camelCase")]
82+
pub enum PeerDiscoveryMode {
83+
/// Discover peers on all qualifying interfaces (default behavior).
84+
#[default]
85+
All,
86+
/// Peer discovery is completely disabled.
87+
Disabled,
88+
/// Only discover peers on interfaces whose names match the provided list.
89+
Filtered(Vec<String>),
90+
}
91+
7992
/// Local info about a peer.
8093
struct PeerInfo {
8194
/// Details how we found out about this peer.
@@ -186,7 +199,7 @@ where
186199
tcp_listen_port: u16,
187200
quic_listen_port: Option<u16>,
188201
peer_discovery_port: u16,
189-
disable_peer_discovery: bool,
202+
peer_discovery_mode: PeerDiscoveryMode,
190203
private_network_config: Option<(String, PrivateNetworkKey)>,
191204
metrics: M,
192205
firewall_mark: Option<u32>,
@@ -266,16 +279,30 @@ where
266279
let handle = tokio::spawn(peer_manager.inner.clone().connect_to_peers());
267280
peer_manager.abort_handles.push(handle.abort_handle());
268281

269-
// Discover local peers, this does not actually connect to them. That is handle by the
282+
// Discover local peers, this does not actually connect to them. That is handled by the
270283
// connect_to_peers task.
271-
if !disable_peer_discovery {
272-
let handle = tokio::spawn(
273-
peer_manager
274-
.inner
275-
.clone()
276-
.local_discovery(peer_discovery_port),
277-
);
278-
peer_manager.abort_handles.push(handle.abort_handle());
284+
match peer_discovery_mode {
285+
PeerDiscoveryMode::Disabled => {
286+
// No discovery task spawned
287+
}
288+
PeerDiscoveryMode::All => {
289+
let handle = tokio::spawn(
290+
peer_manager
291+
.inner
292+
.clone()
293+
.local_discovery(peer_discovery_port, None),
294+
);
295+
peer_manager.abort_handles.push(handle.abort_handle());
296+
}
297+
PeerDiscoveryMode::Filtered(interfaces) => {
298+
let handle = tokio::spawn(
299+
peer_manager
300+
.inner
301+
.clone()
302+
.local_discovery(peer_discovery_port, Some(interfaces)),
303+
);
304+
peer_manager.abort_handles.push(handle.abort_handle());
305+
}
279306
}
280307

281308
Ok(peer_manager)
@@ -1046,7 +1073,12 @@ where
10461073
}
10471074

10481075
/// Use multicast discovery to find local peers.
1049-
async fn local_discovery(self: Arc<Self>, peer_discovery_port: u16) {
1076+
/// When `allowed_interfaces` is provided, only join multicast groups on those interfaces.
1077+
async fn local_discovery(
1078+
self: Arc<Self>,
1079+
peer_discovery_port: u16,
1080+
allowed_interfaces: Option<Vec<String>>,
1081+
) {
10501082
let rid = self.router.lock().unwrap().router_id();
10511083

10521084
let multicast_destination = LL_PEER_DISCOVERY_GROUP
@@ -1077,7 +1109,7 @@ where
10771109
let mut joined_interfaces = HashSet::new();
10781110
// Join the multicast discovery group on newly detected interfaces.
10791111
let join_new_interfaces = |joined_interfaces: &mut HashSet<_>| {
1080-
let ipv6_nics = list_ipv6_interface_ids()?;
1112+
let ipv6_nics = list_ipv6_interface_ids(allowed_interfaces.as_deref())?;
10811113
// Keep the existing interfaces, removing interface ids we previously joined but are no
10821114
// longer found when listing ids. We simply discard unknown ids, and assume if the
10831115
// interface is gone (or it's IPv6), that we also implicitly left the group (i.e. no
@@ -1356,7 +1388,10 @@ impl rustls::client::danger::ServerCertVerifier for SkipServerVerification {
13561388
}
13571389

13581390
/// Get a list of the interface identifiers of every network interface with a local IPv6 IP.
1359-
fn list_ipv6_interface_ids() -> Result<HashSet<u32>, Box<dyn std::error::Error>> {
1391+
/// When `allowed_interfaces` is provided, only interfaces with matching names are returned.
1392+
fn list_ipv6_interface_ids(
1393+
allowed_interfaces: Option<&[String]>,
1394+
) -> Result<HashSet<u32>, Box<dyn std::error::Error>> {
13601395
let mut nics = HashSet::new();
13611396
for nic in netdev::get_interfaces()
13621397
.into_iter()
@@ -1368,6 +1403,12 @@ fn list_ipv6_interface_ids() -> Result<HashSet<u32>, Box<dyn std::error::Error>>
13681403
&& nic.is_multicast()
13691404
&& nic.is_up()
13701405
})
1406+
// Apply name filter if provided
1407+
.filter(|nic| {
1408+
allowed_interfaces
1409+
.map(|names| names.iter().any(|name| name == &nic.name))
1410+
.unwrap_or(true)
1411+
})
13711412
{
13721413
for addr in nic.ipv6 {
13731414
if addr.addr().segments()[..4] == [0xfe80, 0, 0, 0] {

myceliumd-private/src/main.rs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use tracing::{debug, error, info, warn};
2121

2222
use crypto::PublicKey;
2323
use mycelium::endpoint::Endpoint;
24+
use mycelium::peer_manager::PeerDiscoveryMode;
2425
use mycelium::{crypto, Node};
2526
use tracing_subscriber::layer::SubscriberExt;
2627
use tracing_subscriber::util::SubscriberInitExt;
@@ -308,6 +309,15 @@ pub struct NodeArguments {
308309
#[arg(long = "disable-peer-discovery", default_value_t = false)]
309310
disable_peer_discovery: bool,
310311

312+
/// Limit peer discovery to specific network interfaces.
313+
///
314+
/// When specified, peer discovery will only operate on the named interfaces.
315+
/// Can be specified multiple times. If not specified, discovery runs on all
316+
/// qualifying interfaces (unless --disable-peer-discovery is set).
317+
/// This flag is ignored if --disable-peer-discovery is set.
318+
#[arg(long = "peer-discovery-interface")]
319+
peer_discovery_interfaces: Vec<String>,
320+
311321
/// Address of the HTTP API server.
312322
#[arg(long = "api-addr", default_value_t = DEFAULT_HTTP_API_SERVER_ADDRESS)]
313323
api_addr: SocketAddr,
@@ -406,6 +416,7 @@ pub struct MergedNodeConfig {
406416
quic_listen_port: u16,
407417
peer_discovery_port: u16,
408418
disable_peer_discovery: bool,
419+
peer_discovery_interfaces: Vec<String>,
409420
api_addr: SocketAddr,
410421
jsonrpc_addr: SocketAddr,
411422
no_tun: bool,
@@ -431,6 +442,7 @@ struct MyceliumConfig {
431442
tun_name: Option<String>,
432443
disable_peer_discovery: Option<bool>,
433444
peer_discovery_port: Option<u16>,
445+
peer_discovery_interfaces: Option<Vec<String>>,
434446
api_addr: Option<SocketAddr>,
435447
jsonrpc_addr: Option<SocketAddr>,
436448
metrics_api_address: Option<SocketAddr>,
@@ -586,6 +598,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
586598
info!(path = ?merged_config.topic_config, "Loaded topic cofig");
587599
}
588600

601+
// Compute peer discovery mode from flags
602+
let peer_discovery_mode = if merged_config.disable_peer_discovery {
603+
if !merged_config.peer_discovery_interfaces.is_empty() {
604+
warn!("--disable-peer-discovery is set, ignoring --peer-discovery-interface values");
605+
}
606+
PeerDiscoveryMode::Disabled
607+
} else if !merged_config.peer_discovery_interfaces.is_empty() {
608+
PeerDiscoveryMode::Filtered(merged_config.peer_discovery_interfaces.clone())
609+
} else {
610+
PeerDiscoveryMode::All
611+
};
612+
589613
let private_network_config =
590614
match (merged_config.network_name, merged_config.network_key_file) {
591615
(Some(network_name), Some(network_key_file)) => {
@@ -618,11 +642,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
618642
} else {
619643
Some(merged_config.quic_listen_port)
620644
},
621-
peer_discovery_port: if merged_config.disable_peer_discovery {
622-
None
623-
} else {
624-
Some(merged_config.peer_discovery_port)
625-
},
645+
peer_discovery_port: merged_config.peer_discovery_port,
646+
peer_discovery_mode: peer_discovery_mode.clone(),
626647
tun_name: merged_config.tun_name,
627648
private_network_config,
628649
metrics: metrics.clone(),
@@ -653,11 +674,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
653674
} else {
654675
Some(merged_config.quic_listen_port)
655676
},
656-
peer_discovery_port: if merged_config.disable_peer_discovery {
657-
None
658-
} else {
659-
Some(merged_config.peer_discovery_port)
660-
},
677+
peer_discovery_port: merged_config.peer_discovery_port,
678+
peer_discovery_mode,
661679
tun_name: merged_config.tun_name,
662680
private_network_config,
663681
metrics: mycelium_metrics::NoMetrics,
@@ -914,6 +932,11 @@ fn merge_config(cli_args: NodeArguments, file_config: MyceliumConfig) -> MergedN
914932
},
915933
disable_peer_discovery: cli_args.disable_peer_discovery
916934
|| file_config.disable_peer_discovery.unwrap_or(false),
935+
peer_discovery_interfaces: if !cli_args.peer_discovery_interfaces.is_empty() {
936+
cli_args.peer_discovery_interfaces
937+
} else {
938+
file_config.peer_discovery_interfaces.unwrap_or_default()
939+
},
917940
api_addr: if cli_args.api_addr != DEFAULT_HTTP_API_SERVER_ADDRESS {
918941
cli_args.api_addr
919942
} else {

myceliumd/src/main.rs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ use tracing::{debug, error, info, warn};
2121

2222
use crypto::PublicKey;
2323
use mycelium::endpoint::Endpoint;
24+
use mycelium::peer_manager::PeerDiscoveryMode;
2425
use mycelium::{crypto, Node};
2526
use tracing_subscriber::layer::SubscriberExt;
2627
use tracing_subscriber::util::SubscriberInitExt;
@@ -308,6 +309,15 @@ pub struct NodeArguments {
308309
#[arg(long = "disable-peer-discovery", default_value_t = false)]
309310
disable_peer_discovery: bool,
310311

312+
/// Limit peer discovery to specific network interfaces.
313+
///
314+
/// When specified, peer discovery will only operate on the named interfaces.
315+
/// Can be specified multiple times. If not specified, discovery runs on all
316+
/// qualifying interfaces (unless --disable-peer-discovery is set).
317+
/// This flag is ignored if --disable-peer-discovery is set.
318+
#[arg(long = "peer-discovery-interface")]
319+
peer_discovery_interfaces: Vec<String>,
320+
311321
/// Address of the HTTP API server.
312322
#[arg(long = "api-addr", default_value_t = DEFAULT_HTTP_API_SERVER_ADDRESS)]
313323
api_addr: SocketAddr,
@@ -389,6 +399,7 @@ pub struct MergedNodeConfig {
389399
quic_listen_port: u16,
390400
peer_discovery_port: u16,
391401
disable_peer_discovery: bool,
402+
peer_discovery_interfaces: Vec<String>,
392403
api_addr: SocketAddr,
393404
jsonrpc_addr: SocketAddr,
394405
no_tun: bool,
@@ -412,6 +423,7 @@ struct MyceliumConfig {
412423
tun_name: Option<String>,
413424
disable_peer_discovery: Option<bool>,
414425
peer_discovery_port: Option<u16>,
426+
peer_discovery_interfaces: Option<Vec<String>>,
415427
api_addr: Option<SocketAddr>,
416428
jsonrpc_addr: Option<SocketAddr>,
417429
metrics_api_address: Option<SocketAddr>,
@@ -565,6 +577,18 @@ async fn main() -> Result<(), Box<dyn Error>> {
565577
info!(path = ?merged_config.topic_config, "Loaded topic cofig");
566578
}
567579

580+
// Compute peer discovery mode from flags
581+
let peer_discovery_mode = if merged_config.disable_peer_discovery {
582+
if !merged_config.peer_discovery_interfaces.is_empty() {
583+
warn!("--disable-peer-discovery is set, ignoring --peer-discovery-interface values");
584+
}
585+
PeerDiscoveryMode::Disabled
586+
} else if !merged_config.peer_discovery_interfaces.is_empty() {
587+
PeerDiscoveryMode::Filtered(merged_config.peer_discovery_interfaces.clone())
588+
} else {
589+
PeerDiscoveryMode::All
590+
};
591+
568592
let node_keys = get_node_keys(&key_path).await?;
569593
let node_secret_key = if let Some((node_secret_key, _)) = node_keys {
570594
node_secret_key
@@ -587,11 +611,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
587611
} else {
588612
Some(merged_config.quic_listen_port)
589613
},
590-
peer_discovery_port: if merged_config.disable_peer_discovery {
591-
None
592-
} else {
593-
Some(merged_config.peer_discovery_port)
594-
},
614+
peer_discovery_port: merged_config.peer_discovery_port,
615+
peer_discovery_mode: peer_discovery_mode.clone(),
595616
tun_name: merged_config.tun_name,
596617
private_network_config: None,
597618
metrics: metrics.clone(),
@@ -621,11 +642,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
621642
} else {
622643
Some(merged_config.quic_listen_port)
623644
},
624-
peer_discovery_port: if merged_config.disable_peer_discovery {
625-
None
626-
} else {
627-
Some(merged_config.peer_discovery_port)
628-
},
645+
peer_discovery_port: merged_config.peer_discovery_port,
646+
peer_discovery_mode,
629647
tun_name: merged_config.tun_name,
630648
private_network_config: None,
631649
metrics: mycelium_metrics::NoMetrics,
@@ -881,6 +899,11 @@ fn merge_config(cli_args: NodeArguments, file_config: MyceliumConfig) -> MergedN
881899
},
882900
disable_peer_discovery: cli_args.disable_peer_discovery
883901
|| file_config.disable_peer_discovery.unwrap_or(false),
902+
peer_discovery_interfaces: if !cli_args.peer_discovery_interfaces.is_empty() {
903+
cli_args.peer_discovery_interfaces
904+
} else {
905+
file_config.peer_discovery_interfaces.unwrap_or_default()
906+
},
884907
api_addr: if cli_args.api_addr != DEFAULT_HTTP_API_SERVER_ADDRESS {
885908
cli_args.api_addr
886909
} else {

0 commit comments

Comments
 (0)