Skip to content

Commit

Permalink
feat(iroh-net)!: Add PkarrResolver and publish direct addresses in Pk…
Browse files Browse the repository at this point in the history
…arrPublisher when relay is disabled (#2417)

Tested it, works fine and fixes all my issues.

Closes #2412 and closes #2413 

### Features
- *(iroh-net)* `PkarrResolver` adds an alternative discovery mechanism
to the existing `DnsResolver`.

### Bug Fixes
- *(iroh-net)* Direct addresses are published when relay mode is set to
disabled.

### Breaking changes
- *(iroh-net)* pkarr_publish module was renamed to pkarr.
  • Loading branch information
dvc94ch committed Jul 3, 2024
1 parent ab2c7ea commit 5ba6855
Show file tree
Hide file tree
Showing 15 changed files with 492 additions and 278 deletions.
588 changes: 357 additions & 231 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 1 addition & 3 deletions iroh-cli/src/commands/doctor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ use iroh::{
docs::{Capability, DocTicket},
net::{
defaults::DEFAULT_STUN_PORT,
discovery::{
dns::DnsDiscovery, pkarr_publish::PkarrPublisher, ConcurrentDiscovery, Discovery,
},
discovery::{dns::DnsDiscovery, pkarr::PkarrPublisher, ConcurrentDiscovery, Discovery},
dns::default_resolver,
endpoint::{self, Connection, ConnectionTypeStream, RecvStream, SendStream},
key::{PublicKey, SecretKey},
Expand Down
5 changes: 3 additions & 2 deletions iroh-dns-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,9 @@ hickory-server = { version = "0.24.0", features = ["dns-over-rustls"] }
http = "1.0.0"
iroh-metrics = { version = "0.19.0", path = "../iroh-metrics" }
lru = "0.12.3"
mainline = "2.0.1"
parking_lot = "0.12.1"
pkarr = { version = "1.1.4", features = [ "async", "relay", "dht"], default-features = false }
pkarr = { version = "2.0.0", features = [ "async", "relay", "dht"], default-features = false }
rcgen = "0.12.1"
redb = "2.0.0"
regex = "1.10.3"
Expand All @@ -54,7 +55,7 @@ z32 = "1.1.1"
hickory-resolver = "0.24.0"
iroh-net = { version = "0.19.0", path = "../iroh-net" }
iroh-test = { path = "../iroh-test" }
mainline = "<1.5.0"
pkarr = { version = "2.0.0", features = ["rand"] }

[package.metadata.docs.rs]
all-features = true
2 changes: 1 addition & 1 deletion iroh-dns-server/examples/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ fn main() -> anyhow::Result<()> {
match args.command {
Command::NodeToPkarr { node_id } => {
let node_id = NodeId::from_str(&node_id)?;
let public_key = pkarr::PublicKey::try_from(*node_id.as_bytes())?;
let public_key = pkarr::PublicKey::try_from(node_id.as_bytes())?;
println!("{}", public_key.to_z32())
}
Command::PkarrToNode { z32_pubkey } => {
Expand Down
2 changes: 1 addition & 1 deletion iroh-dns-server/examples/publish.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use clap::{Parser, ValueEnum};
use iroh_net::{
discovery::{
dns::N0_DNS_NODE_ORIGIN_PROD,
pkarr_publish::{PkarrRelayClient, N0_DNS_PKARR_RELAY},
pkarr::{PkarrRelayClient, N0_DNS_PKARR_RELAY},
},
dns::node_info::{to_z32, NodeInfo, IROH_TXT_NAME},
key::SecretKey,
Expand Down
4 changes: 2 additions & 2 deletions iroh-dns-server/src/http/pkarr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub async fn put(
let key = pkarr::PublicKey::try_from(key.as_str())
.map_err(|e| AppError::new(StatusCode::BAD_REQUEST, Some(format!("invalid key: {e}"))))?;
let label = &key.to_z32()[..10];
let signed_packet = pkarr::SignedPacket::from_relay_response(key, body).map_err(|e| {
let signed_packet = pkarr::SignedPacket::from_relay_payload(&key, &body).map_err(|e| {
AppError::new(
StatusCode::BAD_REQUEST,
Some(format!("invalid body payload: {e}")),
Expand All @@ -46,7 +46,7 @@ pub async fn get(
.get_signed_packet(&pubkey)
.await?
.ok_or_else(|| AppError::with_status(StatusCode::NOT_FOUND))?;
let body = signed_packet.as_relay_request();
let body = signed_packet.to_relay_payload();
let headers = [(header::CONTENT_TYPE, "application/x-pkarr-signed-packet")];
Ok((headers, body))
}
24 changes: 15 additions & 9 deletions iroh-dns-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ mod tests {
AsyncResolver,
};
use iroh_net::{
discovery::pkarr_publish::PkarrRelayClient,
discovery::pkarr::PkarrRelayClient,
dns::{node_info::NodeInfo, DnsResolver, ResolverExt},
key::SecretKey,
};
Expand Down Expand Up @@ -93,10 +93,11 @@ mod tests {
));
SignedPacket::from_packet(&keypair, &packet)?
};
let pkarr_client = pkarr::PkarrClient::builder().build();
pkarr_client
.relay_put(&pkarr_relay_url, &signed_packet)
.await?;
let pkarr_client = pkarr::PkarrRelayClient::new(pkarr::RelaySettings {
relays: vec![pkarr_relay_url.to_string()],
..Default::default()
})?;
pkarr_client.as_async().publish(&signed_packet).await?;

use hickory_proto::rr::Name;
let pubkey = signed_packet.public_key().to_z32();
Expand Down Expand Up @@ -196,8 +197,13 @@ mod tests {
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?;
let pkarr = PkarrClient::builder()
.dht_settings(mainline::dht::DhtSettings {
bootstrap: Some(testnet.bootstrap),
..Default::default()
})
.build()?;
pkarr.publish(&signed_packet)?;

// resolve via DNS from our server, which will lookup from our DHT
let resolver = test_resolver(nameserver);
Expand All @@ -207,8 +213,8 @@ mod tests {
assert_eq!(res.info.relay_url.map(Url::from), Some(relay_url));

server.shutdown().await?;
for node in testnet.nodes {
node.shutdown();
for mut node in testnet.nodes {
node.shutdown()?;
}
Ok(())
}
Expand Down
21 changes: 13 additions & 8 deletions iroh-dns-server/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use anyhow::Result;
use hickory_proto::rr::{Name, RecordSet, RecordType, RrKey};
use iroh_metrics::inc;
use lru::LruCache;
use mainline::dht::DhtSettings;
use parking_lot::Mutex;
use pkarr::{PkarrClient, SignedPacket};
use tracing::{debug, trace};
Expand Down Expand Up @@ -64,10 +65,14 @@ impl ZoneStore {
/// mainline bootstrap nodes.
pub fn with_mainline_fallback(self, bootstrap: BootstrapOption) -> Self {
let pkarr_client = match bootstrap {
BootstrapOption::Default => PkarrClient::default(),
BootstrapOption::Custom(bootstrap) => {
PkarrClient::builder().bootstrap(&bootstrap).build()
}
BootstrapOption::Default => PkarrClient::builder().build().unwrap(),
BootstrapOption::Custom(bootstrap) => PkarrClient::builder()
.dht_settings(DhtSettings {
bootstrap: Some(bootstrap),
..Default::default()
})
.build()
.unwrap(),
};
Self {
pkarr: Some(Arc::new(pkarr_client)),
Expand Down Expand Up @@ -106,12 +111,12 @@ impl ZoneStore {
};

if let Some(pkarr) = self.pkarr.as_ref() {
let key = pkarr::PublicKey::try_from(*pubkey.as_bytes()).expect("valid public key");
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;
let packet_opt = pkarr.as_ref().clone().as_async().resolve(&key).await?;
if let Some(packet) = packet_opt {
debug!("DHT resolve successful {:?}", packet.packet());
return self
Expand Down Expand Up @@ -243,12 +248,12 @@ impl CachedZone {
signed_packet_to_hickory_records_without_origin(signed_packet, |_| true)?;
Ok(Self {
records,
timestamp: *signed_packet.timestamp(),
timestamp: signed_packet.timestamp(),
})
}

fn is_newer_than(&self, signed_packet: &SignedPacket) -> bool {
self.timestamp > *signed_packet.timestamp()
self.timestamp > signed_packet.timestamp()
}

fn resolve(&self, name: &Name, record_type: RecordType) -> Option<Arc<RecordSet>> {
Expand Down
2 changes: 1 addition & 1 deletion iroh-dns-server/src/store/signed_packets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,6 @@ fn get_packet(
let Some(row) = table.get(key.as_ref())? else {
return Ok(None);
};
let packet = SignedPacket::from_bytes(row.value().to_vec().into(), false)?;
let packet = SignedPacket::from_bytes(&row.value().to_vec().into())?;
Ok(Some(packet))
}
2 changes: 1 addition & 1 deletion iroh-dns-server/src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl From<pkarr::PublicKey> for PublicKeyBytes {
impl TryFrom<PublicKeyBytes> for pkarr::PublicKey {
type Error = anyhow::Error;
fn try_from(value: PublicKeyBytes) -> Result<Self, Self::Error> {
pkarr::PublicKey::try_from(value.0).map_err(anyhow::Error::from)
pkarr::PublicKey::try_from(&value.0).map_err(anyhow::Error::from)
}
}

Expand Down
2 changes: 1 addition & 1 deletion iroh-net/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ num_enum = "0.7"
once_cell = "1.18.0"
parking_lot = "0.12.1"
pin-project = "1"
pkarr = { version = "1.1.4", default-features = false, features = ["async", "relay"] }
pkarr = { version = "2.0.0", default-features = false, features = ["async", "relay"] }
postcard = { version = "1", default-features = false, features = ["alloc", "use-std", "experimental-derive"] }
quinn = { package = "iroh-quinn", version = "0.10.5" }
quinn-proto = { package = "iroh-quinn-proto", version = "0.10.8" }
Expand Down
4 changes: 2 additions & 2 deletions iroh-net/src/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use tracing::{debug, error_span, warn, Instrument};
use crate::{AddrInfo, Endpoint, NodeId};

pub mod dns;
pub mod pkarr_publish;
pub mod pkarr;

/// Name used for logging when new node addresses are added from discovery.
const SOURCE_NAME: &str = "discovery";
Expand Down Expand Up @@ -564,7 +564,7 @@ mod test_dns_pkarr {
use iroh_base::key::SecretKey;

use crate::{
discovery::pkarr_publish::PkarrPublisher,
discovery::pkarr::PkarrPublisher,
dns::{node_info::NodeInfo, ResolverExt},
relay::{RelayMap, RelayMode},
test_utils::{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
//! A discovery service which publishes node information to a [Pkarr] relay.
//!
//! This service only implements the [`Discovery::publish`] method and does not provide discovery.
//! It encodes the node information into a DNS packet in the format resolvable by the
//! [`super::dns::DnsDiscovery`].
//! A discovery service which publishes and resolves node information to a [pkarr] relay.
//!
//! [pkarr]: https://pkarr.org

use std::sync::Arc;

use anyhow::{anyhow, bail, Result};
use futures_util::stream::BoxStream;
use pkarr::SignedPacket;
use tokio::{
task::JoinHandle,
Expand All @@ -18,18 +15,29 @@ use tracing::{debug, error_span, info, warn, Instrument};
use url::Url;
use watchable::{Watchable, Watcher};

use crate::{discovery::Discovery, dns::node_info::NodeInfo, key::SecretKey, AddrInfo, NodeId};
use crate::{
discovery::{Discovery, DiscoveryItem},
dns::node_info::NodeInfo,
key::SecretKey,
AddrInfo, Endpoint, NodeId,
};

/// The pkarr relay run by n0.
pub const N0_DNS_PKARR_RELAY: &str = "https://dns.iroh.link/pkarr";

/// Default TTL for the records in the pkarr signed packet
/// Default TTL for the records in the pkarr signed packet. TTL tells DNS caches
/// how long to store a record. It is ignored by the iroh-dns-server as the home
/// server keeps the records for the domain. When using the pkarr relay no DNS is
/// involved and the setting is ignored.
pub const DEFAULT_PKARR_TTL: u32 = 30;

/// Interval in which we will republish our node info even if unchanged: 5 minutes.
pub const DEFAULT_REPUBLISH_INTERVAL: Duration = Duration::from_secs(60 * 5);

/// Publish node info to a pkarr relay.
///
/// Publishes either the relay url if the relay is enabled or the direct addresses
/// if the relay is disabled.
#[derive(derive_more::Debug, Clone)]
pub struct PkarrPublisher {
node_id: NodeId,
Expand Down Expand Up @@ -91,11 +99,12 @@ impl PkarrPublisher {
///
/// This is a nonblocking function, the actual update is performed in the background.
pub fn update_addr_info(&self, info: &AddrInfo) {
let info = NodeInfo::new(
self.node_id,
info.relay_url.clone().map(Into::into),
Default::default(),
);
let (relay_url, direct_addresses) = if let Some(relay_url) = info.relay_url.as_ref() {
(Some(relay_url.clone().into()), Default::default())
} else {
(None, info.direct_addresses.clone())
};
let info = NodeInfo::new(self.node_id, relay_url, direct_addresses);
self.watchable.update(Some(info)).ok();
}
}
Expand Down Expand Up @@ -174,6 +183,54 @@ impl PublisherService {
}
}

/// Resolve node info using a pkarr relay.
///
/// Pkarr stores signed DNS records in the mainline dht. These can be queried directly
/// via the pkarr relay HTTP api or alternatively via a dns server that provides the
/// pkarr records using `DnsDiscovery`. The main difference is that `DnsDiscovery` makes
/// use of the system dns resolver and caching which can return stale records, while the
/// `PkarrResolver` always gets recent data.
#[derive(derive_more::Debug, Clone)]
pub struct PkarrResolver {
pkarr_client: PkarrRelayClient,
}

impl PkarrResolver {
/// Create a new config with a pkarr relay URL.
pub fn new(pkarr_relay: Url) -> Self {
Self {
pkarr_client: PkarrRelayClient::new(pkarr_relay),
}
}

/// Create a config that resolves using the n0 dns server through [`N0_DNS_PKARR_RELAY`].
pub fn n0_dns() -> Self {
let pkarr_relay: Url = N0_DNS_PKARR_RELAY.parse().expect("url is valid");
Self::new(pkarr_relay)
}
}

impl Discovery for PkarrResolver {
fn resolve(
&self,
_ep: Endpoint,
node_id: NodeId,
) -> Option<BoxStream<'static, Result<DiscoveryItem>>> {
let pkarr_client = self.pkarr_client.clone();
let fut = async move {
let signed_packet = pkarr_client.resolve(node_id).await?;
let info = NodeInfo::from_pkarr_signed_packet(&signed_packet)?;
Ok(DiscoveryItem {
provenance: "pkarr",
last_updated: None,
addr_info: info.into(),
})
};
let stream = futures_lite::stream::once_future(fut);
Some(Box::pin(stream))
}
}

/// A pkarr client to publish [`pkarr::SignedPacket`]s to a pkarr relay.
#[derive(Debug, Clone)]
pub struct PkarrRelayClient {
Expand All @@ -190,6 +247,27 @@ impl PkarrRelayClient {
}
}

/// Resolve a [`SignedPacket`]
pub async fn resolve(&self, node_id: NodeId) -> anyhow::Result<SignedPacket> {
let public_key = pkarr::PublicKey::try_from(node_id.as_bytes())?;
let mut url = self.pkarr_relay_url.clone();
url.path_segments_mut()
.map_err(|_| anyhow!("Failed to resolve: Invalid relay URL"))?
.push(&public_key.to_z32());

let response = self.http_client.get(url).send().await?;

if !response.status().is_success() {
bail!(format!(
"Resolve request failed with status {}",
response.status()
))
}

let payload = response.bytes().await?;
Ok(SignedPacket::from_relay_payload(&public_key, &payload)?)
}

/// Publish a [`SignedPacket`]
pub async fn publish(&self, signed_packet: &SignedPacket) -> anyhow::Result<()> {
let mut url = self.pkarr_relay_url.clone();
Expand All @@ -200,7 +278,7 @@ impl PkarrRelayClient {
let response = self
.http_client
.put(url)
.body(signed_packet.as_relay_request())
.body(signed_packet.to_relay_payload())
.send()
.await?;

Expand Down
4 changes: 2 additions & 2 deletions iroh-net/src/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ pub(crate) mod dns_and_pkarr_servers {
use super::{create_dns_resolver, CleanupDropGuard};

use crate::{
discovery::{dns::DnsDiscovery, pkarr_publish::PkarrPublisher, ConcurrentDiscovery},
discovery::{dns::DnsDiscovery, pkarr::PkarrPublisher, ConcurrentDiscovery},
dns::DnsResolver,
test_utils::{
dns_server::run_dns_server, pkarr_dns_state::State, pkarr_relay::run_pkarr_relay,
Expand Down Expand Up @@ -309,7 +309,7 @@ pub(crate) mod pkarr_relay {
body: Bytes,
) -> Result<impl IntoResponse, AppError> {
let key = pkarr::PublicKey::try_from(key.as_str())?;
let signed_packet = pkarr::SignedPacket::from_relay_response(key, body)?;
let signed_packet = pkarr::SignedPacket::from_relay_payload(&key, &body)?;
let _updated = state.upsert(signed_packet)?;
Ok(http::StatusCode::NO_CONTENT)
}
Expand Down
Loading

0 comments on commit 5ba6855

Please sign in to comment.