Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 16 commits into from
Apr 29, 2024
Merged
47 changes: 47 additions & 0 deletions Cargo.lock

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

3 changes: 2 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,6 +46,7 @@ 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"

Expand Down
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
25 changes: 25 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,20 @@ 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,
}

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

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

pub(crate) fn mainline_enabled(&self) -> bool {
self.mainline
.as_ref()
.map(|x| x.enabled)
.unwrap_or_default()
}
}

impl Default for Config {
Expand Down Expand Up @@ -134,6 +158,7 @@ impl Default for Config {
rr_ns: Some("ns1.irohdns.example.".to_string()),
},
metrics: None,
mainline: None,
}
}
}
2 changes: 2 additions & 0 deletions iroh-dns-server/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,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
7 changes: 5 additions & 2 deletions iroh-dns-server/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//! The main server which combines the DNS and HTTP(S) servers.

use anyhow::Result;
use iroh_metrics::metrics::start_metrics_server;
use tracing::info;
Expand All @@ -14,7 +13,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 config.mainline_enabled() {
info!("mainline fallback enabled");
store = store.with_pkarr(Default::default());
};
let server = Server::spawn(config, store).await?;
tokio::signal::ctrl_c().await?;
info!("shutdown");
Expand Down
76 changes: 66 additions & 10 deletions iroh-dns-server/src/store.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
//! Pkarr packet store used to resolve DNS queries.

use std::{collections::BTreeMap, num::NonZeroUsize, path::Path, sync::Arc};
use std::{collections::BTreeMap, num::NonZeroUsize, path::Path, sync::Arc, time::Duration};

use anyhow::Result;
use hickory_proto::rr::{Name, RecordSet, RecordType, RrKey};
use iroh_metrics::inc;
use lru::LruCache;
use parking_lot::Mutex;
use pkarr::SignedPacket;
use pkarr::{PkarrClient, SignedPacket};
use tracing::{debug, trace};
use ttl_cache::TtlCache;

use crate::{
metrics::Metrics,
Expand All @@ -20,6 +22,8 @@ mod signed_packets;

/// Cache up to 1 million pkarr zones by default
pub const DEFAULT_CACHE_CAPACITY: usize = 1024 * 1024;
/// Default TTL for DHT cache entries
pub const DHT_CACHE_TTL: Duration = Duration::from_secs(300);

/// Where a new pkarr packet comes from
pub enum PacketSource {
Expand All @@ -35,6 +39,7 @@ pub enum PacketSource {
pub struct ZoneStore {
cache: Arc<Mutex<ZoneCache>>,
store: Arc<SignedPacketStore>,
pkarr: Option<Arc<PkarrClient>>,
}

impl ZoneStore {
Expand All @@ -50,24 +55,30 @@ impl ZoneStore {
Ok(Self::new(packet_store))
}

/// Set pkarr client for fallback resolution.
rklaehn marked this conversation as resolved.
Show resolved Hide resolved
pub fn with_pkarr(self, pkarr: Option<Arc<PkarrClient>>) -> Self {
Self { pkarr, ..self }
}

/// Create a new zone store.
pub fn new(store: SignedPacketStore) -> Self {
let zone_cache = ZoneCache::new(DEFAULT_CACHE_CAPACITY);
Self {
store: Arc::new(store),
cache: Arc::new(Mutex::new(zone_cache)),
pkarr: None,
}
}

/// Resolve a DNS query.
// allow unused async: this will be async soon.
#[allow(clippy::unused_async)]
pub async fn resolve(
&self,
pubkey: &PublicKeyBytes,
name: &Name,
record_type: RecordType,
) -> Result<Option<Arc<RecordSet>>> {
tracing::info!("{} {}", name, record_type);
if let Some(rset) = self.cache.lock().resolve(pubkey, name, record_type) {
return Ok(Some(rset));
}
Expand All @@ -79,8 +90,23 @@ impl ZoneStore {
.insert_and_resolve(&packet, name, record_type);
};

// This would be where mainline discovery could be added.

if let Some(pkarr) = self.pkarr.as_ref() {
let key = pkarr::PublicKey::try_from(*pubkey.as_bytes()).expect("valid public key");
// use the more expensive `resolve_most_recent` here.
//
// it will be cached for some time.
debug!("DHT resolve {}", key.to_z32());
let packet_opt = pkarr.as_ref().resolve_most_recent(key).await;
if let Some(packet) = packet_opt {
debug!("DHT resolve successful {:?}", packet.packet());
return self
.cache
.lock()
.insert_and_resolve_dht(&packet, name, record_type);
} else {
debug!("DHT resolve failed");
}
}
Ok(None)
}

Expand Down Expand Up @@ -110,15 +136,21 @@ impl ZoneStore {
}
}

#[derive(Debug)]
#[derive(derive_more::Debug)]
struct ZoneCache {
/// Cache for explicitly added entries
cache: LruCache<PublicKeyBytes, CachedZone>,
/// Cache for DHT entries, this must have a finite TTL
/// so we don't cache stale entries indefinitely.
#[debug("dht_cache")]
dht_cache: TtlCache<PublicKeyBytes, CachedZone>,
}

impl ZoneCache {
fn new(cap: usize) -> Self {
let cache = LruCache::new(NonZeroUsize::new(cap).expect("capacity must be larger than 0"));
Self { cache }
let dht_cache = TtlCache::new(cap);
Self { cache, dht_cache }
}

fn resolve(
Expand All @@ -127,9 +159,16 @@ impl ZoneCache {
name: &Name,
record_type: RecordType,
) -> Option<Arc<RecordSet>> {
self.cache
.get(pubkey)
.and_then(|zone| zone.resolve(name, record_type))
let zone = if let Some(zone) = self.cache.get(pubkey) {
trace!("cache hit {}", pubkey.to_z32());
zone
} else if let Some(zone) = self.dht_cache.get(pubkey) {
trace!("dht cache hit {}", pubkey.to_z32());
zone
} else {
return None;
};
zone.resolve(name, record_type)
}

fn insert_and_resolve(
Expand All @@ -143,6 +182,19 @@ impl ZoneCache {
Ok(self.resolve(&pubkey, name, record_type))
}

fn insert_and_resolve_dht(
&mut self,
signed_packet: &SignedPacket,
name: &Name,
record_type: RecordType,
) -> Result<Option<Arc<RecordSet>>> {
let pubkey = PublicKeyBytes::from_signed_packet(signed_packet);
let zone = CachedZone::from_signed_packet(signed_packet)?;
let res = zone.resolve(name, record_type);
self.dht_cache.insert(pubkey, zone, DHT_CACHE_TTL);
Ok(res)
}

fn insert(&mut self, signed_packet: &SignedPacket) -> Result<()> {
let pubkey = PublicKeyBytes::from_signed_packet(signed_packet);
if self
Expand All @@ -160,6 +212,7 @@ impl ZoneCache {

fn remove(&mut self, pubkey: &PublicKeyBytes) {
self.cache.pop(pubkey);
self.dht_cache.remove(pubkey);
}
}

Expand All @@ -185,6 +238,9 @@ impl CachedZone {

fn resolve(&self, name: &Name, record_type: RecordType) -> Option<Arc<RecordSet>> {
let key = RrKey::new(name.into(), record_type);
for record in self.records.keys() {
tracing::info!("record {:?}", record);
}
self.records.get(&key).cloned()
}
}
8 changes: 5 additions & 3 deletions iroh-dns-server/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use hickory_proto::{
};
use pkarr::SignedPacket;

#[derive(derive_more::From, derive_more::Into, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[derive(
derive_more::From, derive_more::Into, Eq, PartialEq, Ord, PartialOrd, Hash, Clone, Copy,
)]
pub struct PublicKeyBytes([u8; 32]);

impl PublicKeyBytes {
Expand All @@ -26,11 +28,11 @@ impl PublicKeyBytes {
Ok(Self(bytes))
}

pub fn to_z32(&self) -> String {
pub fn to_z32(self) -> String {
z32::encode(&self.0)
}

pub fn to_bytes(&self) -> [u8; 32] {
pub fn to_bytes(self) -> [u8; 32] {
self.0
}

Expand Down
Loading
Loading