Enable Iroh local network discovery

This commit is contained in:
Eric Wendland 2026-05-16 14:33:45 +02:00
commit bfc5df698c
12 changed files with 136 additions and 16 deletions

View file

@ -508,6 +508,14 @@ fn print_response(response: ControlResponse, json: bool) -> Result<()> {
status.endpoint_id.as_deref().unwrap_or("not started")
);
println!("iroh relay: {}", status.iroh_relay_mode);
println!(
"iroh discovery: {}",
if status.iroh_local_discovery {
"local-network enabled"
} else {
"local-network disabled"
}
);
println!("iroh: {}", status.iroh);
}
ControlResponse::NodeId(node) => {

View file

@ -8,6 +8,7 @@ pub const DEFAULT_CONFIG_TOML: &str = r#"# geth local node config
# Values: "default", "staging", "disabled", "custom".
[iroh]
relay_mode = "default"
local_discovery = true
# relay_map = "home"
#
# [iroh.relay_maps.home]
@ -101,6 +102,8 @@ pub enum ConfigError {
TomlParse(#[from] toml_edit::TomlError),
#[error("invalid iroh relay_mode: {0}")]
InvalidRelayMode(String),
#[error("invalid iroh local_discovery value: {0}")]
InvalidLocalDiscovery(String),
#[error("custom iroh relay_mode requires iroh.relay_map")]
MissingRelayMapSelection,
#[error("selected iroh relay map does not exist: {0}")]
@ -136,6 +139,7 @@ impl GethConfig {
pub fn parse(text: &str) -> Result<Self, ConfigError> {
let document = text.parse::<toml_edit::DocumentMut>()?;
let relay_maps = parse_relay_maps(&document)?;
let local_discovery = parse_local_discovery(&document)?;
let relay_mode = match document.get("iroh").and_then(|iroh| iroh.get("relay_mode")) {
Some(item) => {
let value = item
@ -150,15 +154,27 @@ impl GethConfig {
iroh: IrohConfig {
relay_mode,
relay_maps,
local_discovery,
},
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct IrohConfig {
pub relay_mode: RelayMode,
pub relay_maps: BTreeMap<String, RelayMapConfig>,
pub local_discovery: bool,
}
impl Default for IrohConfig {
fn default() -> Self {
Self {
relay_mode: RelayMode::Default,
relay_maps: BTreeMap::new(),
local_discovery: true,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
@ -269,6 +285,18 @@ fn validate_relay_url(map: &str, relay_url: &str) -> Result<(), ConfigError> {
}
}
fn parse_local_discovery(document: &toml_edit::DocumentMut) -> Result<bool, ConfigError> {
match document
.get("iroh")
.and_then(|iroh| iroh.get("local_discovery"))
{
Some(item) => item
.as_bool()
.ok_or_else(|| ConfigError::InvalidLocalDiscovery(item.to_string())),
None => Ok(true),
}
}
#[cfg(test)]
mod tests {
use super::*;
@ -277,6 +305,7 @@ mod tests {
fn default_config_uses_iroh_relays() {
let config = GethConfig::parse(GethConfig::default_toml()).expect("parse config");
assert_eq!(config.iroh.relay_mode, RelayMode::Default);
assert!(config.iroh.local_discovery);
}
#[test]
@ -345,4 +374,16 @@ mod tests {
.expect_err("error");
assert!(matches!(error, ConfigError::UnknownRelayMap(map) if map == "home"));
}
#[test]
fn config_parses_local_discovery_toggle() {
let config = GethConfig::parse("[iroh]\nlocal_discovery = false\n").expect("parse");
assert!(!config.iroh.local_discovery);
}
#[test]
fn config_rejects_non_bool_local_discovery() {
let error = GethConfig::parse("[iroh]\nlocal_discovery = \"yes\"\n").expect_err("error");
assert!(matches!(error, ConfigError::InvalidLocalDiscovery(_)));
}
}

View file

@ -149,6 +149,7 @@ pub struct StatusResponse {
pub iroh_enabled: bool,
pub endpoint_id: Option<String>,
pub iroh_relay_mode: String,
pub iroh_local_discovery: bool,
pub iroh: String,
}

View file

@ -16,6 +16,7 @@ pub const ALPN_SSH_PROXY: &[u8] = b"/geth/ssh-proxy/1";
pub struct GethIrohConfig {
pub secret_key_path: PathBuf,
pub relay_mode: GethRelayMode,
pub local_discovery: bool,
pub bind_ipv4: Option<SocketAddrV4>,
pub bind_ipv6: Option<SocketAddrV6>,
pub alpns: Vec<Vec<u8>>,
@ -35,6 +36,7 @@ impl GethIrohConfig {
Self {
secret_key_path: secret_key_path.into(),
relay_mode,
local_discovery: true,
bind_ipv4: None,
bind_ipv6: None,
alpns: default_protocol_router().alpns(),
@ -229,6 +231,10 @@ pub async fn start_endpoint(config: &GethIrohConfig) -> Result<GethIrohEndpoint,
.relay_mode(config.relay_mode.to_iroh()?)
.alpns(config.alpns.clone());
if config.local_discovery {
builder = builder.discovery_local_network();
}
if let Some(bind_ipv4) = config.bind_ipv4 {
builder = builder.bind_addr_v4(bind_ipv4);
}
@ -241,9 +247,10 @@ pub async fn start_endpoint(config: &GethIrohConfig) -> Result<GethIrohEndpoint,
enabled: true,
endpoint_id: Some(endpoint.node_id().to_string()),
relay_mode: config.relay_mode.label(),
local_discovery: config.local_discovery,
note: format!(
"Iroh endpoint started with iroh 0.90.0; relay mode: {:?}",
config.relay_mode
"Iroh endpoint started with iroh 0.90.0; relay mode: {:?}; local discovery: {}",
config.relay_mode, config.local_discovery
),
};
Ok(GethIrohEndpoint { endpoint, status })
@ -283,6 +290,7 @@ pub struct EndpointStatus {
pub enabled: bool,
pub endpoint_id: Option<String>,
pub relay_mode: String,
pub local_discovery: bool,
pub note: String,
}
@ -293,6 +301,7 @@ impl EndpointStatus {
enabled: false,
endpoint_id: None,
relay_mode: "not-started".to_owned(),
local_discovery: false,
note: "Iroh endpoint is not running outside daemon mode".to_owned(),
}
}
@ -416,16 +425,18 @@ mod tests {
#[tokio::test]
async fn endpoint_status_tracks_node_id_when_bind_is_available() {
let dir = tempfile::tempdir().expect("tempdir");
let config = GethIrohConfig::local_with_relay(
let mut config = GethIrohConfig::local_with_relay(
dir.path().join("iroh.ed25519"),
GethRelayMode::Disabled,
);
config.local_discovery = false;
match start_endpoint(&config).await {
Ok(endpoint) => {
let status = endpoint.status();
assert!(status.enabled);
assert_eq!(status.endpoint_id, Some(endpoint.node_id()));
assert_eq!(status.relay_mode, "disabled");
assert!(!status.local_discovery);
endpoint.shutdown().await;
}
Err(IrohError::Bind(error)) => {

View file

@ -161,6 +161,7 @@ pub fn handle_request(
iroh_enabled: node.iroh_status.enabled,
endpoint_id: node.iroh_status.endpoint_id.clone(),
iroh_relay_mode: node.iroh_status.relay_mode.clone(),
iroh_local_discovery: node.iroh_status.local_discovery,
iroh: node.iroh_status.note.clone(),
})),
ControlRequest::NodeId => Ok(ControlResponse::NodeId(NodeIdResponse {
@ -456,8 +457,10 @@ async fn start_daemon_iroh_endpoint(
let node_config = GethConfig::load(&node.paths.config_file())?;
let relay_mode = node_config.iroh.relay_mode.clone();
let relay_mode_label = relay_mode.label();
let local_discovery = node_config.iroh.local_discovery;
let iroh_relay_mode = config_relay_mode_to_iroh(&relay_mode, &node_config.iroh.relay_maps);
let config = GethIrohConfig::local_with_relay(node.paths.iroh_key(), iroh_relay_mode);
let mut config = GethIrohConfig::local_with_relay(node.paths.iroh_key(), iroh_relay_mode);
config.local_discovery = local_discovery;
match geth_iroh::start_endpoint(&config).await {
Ok(endpoint) => {
let status = endpoint.status();
@ -473,6 +476,7 @@ async fn start_daemon_iroh_endpoint(
enabled: false,
endpoint_id: None,
relay_mode: relay_mode_label,
local_discovery,
note: format!("Iroh endpoint failed to start: {error}"),
};
Ok(None)

View file

@ -64,6 +64,11 @@ fn geth_init_in_temp_home() {
.expect("read config")
.contains("relay_mode = \"default\"")
);
assert!(
std::fs::read_to_string(home.path().join("config.toml"))
.expect("read config")
.contains("local_discovery = true")
);
}
#[test]
@ -75,7 +80,7 @@ fn geth_status_against_running_daemon() {
assert!(run_geth(home.path(), &["init"]).status.success());
std::fs::write(
home.path().join("config.toml"),
"[iroh]\nrelay_mode = \"disabled\"\n",
"[iroh]\nrelay_mode = \"disabled\"\nlocal_discovery = false\n",
)
.expect("write config");
let mut daemon = spawn_daemon(home.path());
@ -95,6 +100,7 @@ fn geth_status_against_running_daemon() {
assert!(stdout.contains("agent:"));
assert!(stdout.contains("endpoint:"));
assert!(stdout.contains("iroh relay: disabled"));
assert!(stdout.contains("iroh discovery: local-network disabled"));
}
#[test]