Skip to content

Commit

Permalink
feat(iroh-dns-server)!: add dht fallback option (#2188)
Browse files Browse the repository at this point in the history
## Description

feat(iroh-dns-server): add dht fallback option

if we don't have a record, ask the DHT as a last resort.

The DHT entries need to go into a separate ttl cache so they are
invalidated when they get too old.

## Breaking changes

`iroh_dns_server::config::Config` struct has a new field `mainline`.

## Notes & open questions

The issue with async in pkarr has been solved.

Note: caching will be integrated into pkarr in v2. Once that is out we
might be able to remove the cache again and just use pkarr with an
appropriate sized cache configured.

## Change checklist

- [x] Self-review.
- [x] Documentation updates if relevant.
- [ ] Tests if relevant.

---------

Co-authored-by: Franz Heinzmann <[email protected]>
  • Loading branch information
rklaehn and Frando authored Apr 29, 2024
1 parent ba7317d commit 0b0508b
Show file tree
Hide file tree
Showing 11 changed files with 257 additions and 19 deletions.
48 changes: 48 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion iroh-dns-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ http = "1.0.0"
iroh-metrics = { version = "0.14.0", path = "../iroh-metrics" }
lru = "0.12.3"
parking_lot = "0.12.1"
pkarr = { version = "1.1.2", features = [ "async", "relay"], default_features = false }
pkarr = { version = "1.1.4", features = [ "async", "relay", "dht"], default_features = false }
rcgen = "0.12.1"
redb = "2.0.0"
regex = "1.10.3"
Expand All @@ -46,10 +46,12 @@ tower-http = { version = "0.5.2", features = ["cors", "trace"] }
tower_governor = "0.3.2"
tracing = "0.1.40"
tracing-subscriber = "0.3.18"
ttl_cache = "0.5.1"
url = "2.5.0"
z32 = "1.1.1"

[dev-dependencies]
hickory-resolver = "0.24.0"
iroh-net = { version = "0.14.0", path = "../iroh-net" }
iroh-test = { path = "../iroh-test" }
mainline = "<1.5.0"
3 changes: 3 additions & 0 deletions iroh-dns-server/config.dev.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,6 @@ default_ttl = 900
origins = ["irohdns.example.", "."]
rr_a = "127.0.0.1"
rr_ns = "ns1.irohdns.example."

[mainline]
enabled = true
3 changes: 3 additions & 0 deletions iroh-dns-server/config.prod.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,6 @@ default_ttl = 30
origins = ["irohdns.example.org", "."]
rr_a = "203.0.10.10"
rr_ns = "ns1.irohdns.example.org."

[mainline]
enabled = false
51 changes: 51 additions & 0 deletions iroh-dns-server/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ pub struct Config {
/// The metrics server is started by default. To disable the metrics server, set to
/// `Some(MetricsConfig::disabled())`.
pub metrics: Option<MetricsConfig>,

/// Config for the mainline lookup.
pub mainline: Option<MainlineConfig>,
}

/// The config for the metrics server.
Expand All @@ -61,6 +64,39 @@ impl MetricsConfig {
}
}

/// The config for the metrics server.
#[derive(Debug, Serialize, Deserialize)]
pub struct MainlineConfig {
/// Set to true to enable the mainline lookup.
pub enabled: bool,
/// Set custom bootstrap nodes.
///
/// Addresses can either be `domain:port` or `ipv4:port`.
///
/// If empty this will use the default bittorrent mainline bootstrap nodes as defined by pkarr.
pub bootstrap: Option<Vec<String>>,
}

/// Configure the bootstrap servers for mainline DHT resolution.
#[derive(Debug, Serialize, Deserialize, Default)]
pub enum BootstrapOption {
/// Use the default bootstrap servers.
#[default]
Default,
/// Use custom bootstrap servers.
Custom(Vec<String>),
}

#[allow(clippy::derivable_impls)]
impl Default for MainlineConfig {
fn default() -> Self {
Self {
enabled: false,
bootstrap: None,
}
}
}

impl Config {
/// Load the config from a file.
pub async fn load(path: impl AsRef<Path>) -> Result<Config> {
Expand Down Expand Up @@ -103,6 +139,20 @@ impl Config {
},
}
}

pub(crate) fn mainline_enabled(&self) -> Option<BootstrapOption> {
match self.mainline.as_ref() {
None => None,
Some(MainlineConfig { enabled: false, .. }) => None,
Some(MainlineConfig {
bootstrap: Some(bootstrap),
..
}) => Some(BootstrapOption::Custom(bootstrap.clone())),
Some(MainlineConfig {
bootstrap: None, ..
}) => Some(BootstrapOption::Default),
}
}
}

impl Default for Config {
Expand Down Expand Up @@ -134,6 +184,7 @@ impl Default for Config {
rr_ns: Some("ns1.irohdns.example.".to_string()),
},
metrics: None,
mainline: None,
}
}
}
43 changes: 41 additions & 2 deletions iroh-dns-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ mod tests {
},
key::SecretKey,
};
use pkarr::SignedPacket;
use pkarr::{PkarrClient, SignedPacket};
use url::Url;

use crate::server::Server;
use crate::{config::BootstrapOption, server::Server};

#[tokio::test]
async fn pkarr_publish_dns_resolve() -> Result<()> {
Expand Down Expand Up @@ -177,6 +177,45 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn integration_mainline() -> Result<()> {
iroh_test::logging::setup_multithreaded();

// run a mainline testnet
let testnet = mainline::dht::Testnet::new(5);
let bootstrap = testnet.bootstrap.clone();

// spawn our server with mainline support
let (server, nameserver, _http_url) =
Server::spawn_for_tests_with_mainline(Some(BootstrapOption::Custom(bootstrap))).await?;

let origin = "irohdns.example.";

// create a signed packet
let secret_key = SecretKey::generate();
let node_id = secret_key.public();
let relay_url: Url = "https://relay.example.".parse()?;
let node_info = NodeInfo::new(node_id, Some(relay_url.clone()), Default::default());
let signed_packet = node_info.to_pkarr_signed_packet(&secret_key, 30)?;

// publish the signed packet to our DHT
let pkarr = PkarrClient::builder().bootstrap(&testnet.bootstrap).build();
pkarr.publish(&signed_packet).await?;

// resolve via DNS from our server, which will lookup from our DHT
let resolver = test_resolver(nameserver);
let res = lookup_by_id(&resolver, &node_id, origin).await?;

assert_eq!(res.node_id, node_id);
assert_eq!(res.info.relay_url.map(Url::from), Some(relay_url));

server.shutdown().await?;
for node in testnet.nodes {
node.shutdown();
}
Ok(())
}

fn test_resolver(nameserver: SocketAddr) -> DnsResolver {
let mut config = ResolverConfig::new();
let nameserver_config = NameServerConfig::new(nameserver, Protocol::Udp);
Expand Down
2 changes: 2 additions & 0 deletions iroh-dns-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,10 @@ async fn main() -> Result<()> {
let args = Cli::parse();

let config = if let Some(path) = args.config {
debug!("loading config from {:?}", path);
Config::load(path).await?
} else {
debug!("using default config");
Config::default()
};

Expand Down
21 changes: 19 additions & 2 deletions iroh-dns-server/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ use crate::{

/// Spawn the server and run until the `Ctrl-C` signal is received, then shutdown.
pub async fn run_with_config_until_ctrl_c(config: Config) -> Result<()> {
let store = ZoneStore::persistent(Config::signed_packet_store_path()?)?;
let mut store = ZoneStore::persistent(Config::signed_packet_store_path()?)?;
if let Some(bootstrap) = config.mainline_enabled() {
info!("mainline fallback enabled");
store = store.with_mainline_fallback(bootstrap);
};
let server = Server::spawn(config, store).await?;
tokio::signal::ctrl_c().await?;
info!("shutdown");
Expand Down Expand Up @@ -86,6 +90,15 @@ impl Server {
/// HTTP server.
#[cfg(test)]
pub async fn spawn_for_tests() -> Result<(Self, std::net::SocketAddr, url::Url)> {
Self::spawn_for_tests_with_mainline(None).await
}

/// Spawn a server suitable for testing, while optionally enabling mainline with custom
/// bootstrap addresses.
#[cfg(test)]
pub async fn spawn_for_tests_with_mainline(
mainline: Option<crate::config::BootstrapOption>,
) -> Result<(Self, std::net::SocketAddr, url::Url)> {
use crate::config::MetricsConfig;
use std::net::{IpAddr, Ipv4Addr};

Expand All @@ -97,7 +110,11 @@ impl Server {
config.https = None;
config.metrics = Some(MetricsConfig::disabled());

let store = ZoneStore::in_memory()?;
let mut store = ZoneStore::in_memory()?;
if let Some(bootstrap) = mainline {
info!("mainline fallback enabled");
store = store.with_mainline_fallback(bootstrap);
}
let server = Self::spawn(config, store).await?;
let dns_addr = server.dns_server.local_addr();
let http_addr = server.http_server.http_addr().expect("http is set");
Expand Down
Loading

0 comments on commit 0b0508b

Please sign in to comment.