Skip to content

Commit df51601

Browse files
committed
feat: publish pkarr self-announces through the derper
1 parent 180a651 commit df51601

File tree

18 files changed

+337
-44
lines changed

18 files changed

+337
-44
lines changed

iroh-base/src/key.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,12 @@ impl From<VerifyingKey> for PublicKey {
229229
}
230230
}
231231

232+
impl From<PublicKey> for VerifyingKey {
233+
fn from(value: PublicKey) -> Self {
234+
value.public()
235+
}
236+
}
237+
232238
impl Debug for PublicKey {
233239
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234240
write!(f, "PublicKey({})", base32::fmt_short(self.as_bytes()))
@@ -391,6 +397,12 @@ impl From<SigningKey> for SecretKey {
391397
}
392398
}
393399

400+
impl From<SecretKey> for SigningKey {
401+
fn from(secret: SecretKey) -> Self {
402+
secret.secret
403+
}
404+
}
405+
394406
impl From<[u8; 32]> for SecretKey {
395407
fn from(value: [u8; 32]) -> Self {
396408
Self::from_bytes(&value)

iroh-net/src/bin/iroh-relay.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use tracing::{debug, debug_span, error, info, info_span, trace, warn, Instrument
3232
use tracing_subscriber::{prelude::*, EnvFilter};
3333

3434
use metrics::StunMetrics;
35+
use url::Url;
3536

3637
type BytesBody = http_body_util::Full<hyper::body::Bytes>;
3738
type HyperError = Box<dyn std::error::Error + Send + Sync>;
@@ -199,6 +200,8 @@ struct Config {
199200
#[cfg(feature = "metrics")]
200201
/// Metrics serve address. If not set, metrics are not served.
201202
metrics_addr: Option<SocketAddr>,
203+
/// Pkarr relay to publish node announces to
204+
pkarr_relay: Option<Url>,
202205
}
203206

204207
#[derive(Serialize, Deserialize)]
@@ -249,6 +252,7 @@ impl Default for Config {
249252
limits: None,
250253
#[cfg(feature = "metrics")]
251254
metrics_addr: None,
255+
pkarr_relay: None,
252256
}
253257
}
254258
}
@@ -451,6 +455,10 @@ async fn run(
451455
Box::new(serve_no_content_handler),
452456
);
453457
}
458+
459+
if let Some(pkarr_relay) = cfg.pkarr_relay {
460+
builder = builder.pkarr_relay(pkarr_relay);
461+
}
454462
let relay_server = builder.spawn().await?;
455463

456464
// captive portal detections must be served over HTTP

iroh-net/src/discovery/dns.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ pub const N0_TESTDNS_NODE_ORIGIN: &str = "testdns.iroh.link";
1818
/// Node information is resolved via an _iroh_node.z32encodednodeid TXT record.
1919
///
2020
/// The content of this record is expected to be a DNS attribute string, with a required
21-
/// `node=` attribute containing the base32 encoded node id and a derp_url attribute containing the
22-
/// node's home Derp server.
21+
/// `node` attribute containing the base32 encoded node id and a `relay` attribute containing the
22+
/// node's home relay server.
2323
///
2424
/// The discovery has to be configured with a `node_origin`, which is the domain name under which
2525
/// lookups for nodes will be made.

iroh-net/src/discovery/pkarr_publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use crate::{discovery::Discovery, dns::node_info::NodeInfo, key::SecretKey, Addr
2424
pub const N0_TESTDNS_PKARR_RELAY: &str = "https://testdns.iroh.link/pkarr";
2525

2626
/// Default TTL for the _iroh_node TXT record in the pkarr signed packet
27-
const DEFAULT_PKARR_TTL: u32 = 30;
27+
pub const DEFAULT_PKARR_TTL: u32 = 30;
2828

2929
/// Publish node info to a pkarr relay.
3030
#[derive(derive_more::Debug, Clone)]

iroh-net/src/dns/node_info.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub(crate) async fn lookup_node_info(
4242
) -> Result<NodeInfo> {
4343
let name = ensure_iroh_node_txt_label(name)?;
4444
let lookup = resolver.txt_lookup(name).await?;
45-
NodeInfo::from_hickory_lookup(lookup.as_lookup())
45+
NodeInfo::from_hickory_records(lookup.as_lookup().records())
4646
}
4747

4848
fn ensure_iroh_node_txt_label(name: Name) -> Result<Name, ProtoError> {
@@ -100,8 +100,11 @@ impl From<NodeInfo> for AddrInfo {
100100

101101
impl NodeInfo {
102102
/// Create a new [`NodeInfo`] from its parts.
103-
pub fn new(node_id: NodeId, relay_url: Option<Url>) -> Self {
104-
Self { node_id, relay_url }
103+
pub fn new(node_id: NodeId, relay_url: Option<impl Into<Url>>) -> Self {
104+
Self {
105+
node_id,
106+
relay_url: relay_url.map(Into::into),
107+
}
105108
}
106109

107110
/// Convert this node info into a DNS attribute string.
@@ -117,11 +120,6 @@ impl NodeInfo {
117120
attrs.join(" ")
118121
}
119122

120-
/// Try to parse a [`NodeInfo`] from the lookup result of our DNS resolver.
121-
pub fn from_hickory_lookup(lookup: &hickory_resolver::lookup::Lookup) -> Result<Self> {
122-
Self::from_hickory_records(lookup.records())
123-
}
124-
125123
/// Try to parse a [`NodeInfo`] from a set of DNS records.
126124
pub fn from_hickory_records(records: &[hickory_proto::rr::Record]) -> Result<Self> {
127125
use hickory_proto::rr;

iroh-net/src/magic_endpoint.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub struct MagicEndpointBuilder {
4141
/// Path for known peers. See [`MagicEndpointBuilder::peers_data_path`].
4242
peers_path: Option<PathBuf>,
4343
dns_resolver: Option<DnsResolver>,
44+
pkarr_announce: bool,
4445
}
4546

4647
impl Default for MagicEndpointBuilder {
@@ -55,6 +56,7 @@ impl Default for MagicEndpointBuilder {
5556
discovery: Default::default(),
5657
peers_path: None,
5758
dns_resolver: None,
59+
pkarr_announce: false,
5860
}
5961
}
6062
}
@@ -156,6 +158,21 @@ impl MagicEndpointBuilder {
156158
self
157159
}
158160

161+
/// Announce the endpoint through the home relay.
162+
///
163+
/// If enabled, and connected to a relay server, the node will publish its basic node
164+
/// information to the relay server as a [`pkarr::SignedPacket`]. The node information contains
165+
/// only our [`NodeId`] and the URL of our home relay. This is the minimal information needed
166+
/// for other nodes to be able to connect to us if they only know our [`NodeId`].
167+
///
168+
/// The default relay servers run by number0 will republish this information as a resolvable TXT record
169+
/// in the Domain Name System (DNS), which makes the assocation of a [`NodeId`] to its home
170+
/// relay globally resolvable.
171+
pub fn pkarr_announce(mut self) -> Self {
172+
self.pkarr_announce = true;
173+
self
174+
}
175+
159176
/// Bind the magic endpoint on the specified socket address.
160177
///
161178
/// The *bind_port* is the port that should be bound locally.
@@ -192,6 +209,7 @@ impl MagicEndpointBuilder {
192209
nodes_path: self.peers_path,
193210
discovery: self.discovery,
194211
dns_resolver,
212+
pkarr_announce: self.pkarr_announce,
195213
};
196214
MagicEndpoint::bind(Some(server_config), msock_opts, self.keylog).await
197215
}

iroh-net/src/magicsock.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,11 @@ pub struct Options {
119119
/// You can use [`crate::dns::default_resolver`] for a resolver that uses the system's DNS
120120
/// configuration.
121121
pub dns_resolver: DnsResolver,
122+
123+
/// Whether to announce ourselves to the relay with a pkarr signed packet.
124+
///
125+
/// If set to false no self-announces will be published.
126+
pub pkarr_announce: bool,
122127
}
123128

124129
impl Default for Options {
@@ -130,6 +135,7 @@ impl Default for Options {
130135
nodes_path: None,
131136
discovery: None,
132137
dns_resolver: crate::dns::default_resolver().clone(),
138+
pkarr_announce: false,
133139
}
134140
}
135141
}
@@ -220,6 +226,9 @@ struct Inner {
220226

221227
/// Indicates the update endpoint state.
222228
endpoints_update_state: EndpointUpdateState,
229+
230+
/// Whether to announce ourselves to the relay with a pkarr signed packet.
231+
pkarr_announce: bool,
223232
}
224233

225234
impl Inner {
@@ -1147,6 +1156,7 @@ impl MagicSock {
11471156
discovery,
11481157
nodes_path,
11491158
dns_resolver,
1159+
pkarr_announce,
11501160
} = opts;
11511161

11521162
let nodes_path = match nodes_path {
@@ -1227,6 +1237,7 @@ impl MagicSock {
12271237
pending_call_me_maybes: Default::default(),
12281238
endpoints_update_state: EndpointUpdateState::new(),
12291239
dns_resolver,
1240+
pkarr_announce,
12301241
});
12311242

12321243
let mut actor_tasks = JoinSet::default();
@@ -2203,10 +2214,10 @@ impl Actor {
22032214
info!("home is now relay {}", relay_url);
22042215
self.inner.publish_my_addr();
22052216

2206-
self.send_relay_actor(RelayActorMessage::NotePreferred(relay_url.clone()));
2207-
self.send_relay_actor(RelayActorMessage::Connect {
2217+
// This will also send a NotePreferred message to the relay,
2218+
// and, if configured, a [`pkarr::SignedPacket`] with info about ourselves.
2219+
self.send_relay_actor(RelayActorMessage::ConnectAsHomeRelay {
22082220
url: relay_url.clone(),
2209-
peer: None,
22102221
});
22112222
}
22122223

iroh-net/src/magicsock/relay_actor.rs

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use tokio_util::sync::CancellationToken;
1919
use tracing::{debug, info, info_span, trace, warn, Instrument};
2020

2121
use crate::{
22+
discovery::pkarr_relay_publish::DEFAULT_PKARR_TTL,
23+
dns::node_info::NodeInfo,
2224
key::{PublicKey, PUBLIC_KEY_LENGTH},
2325
relay::{self, http::ClientError, ReceivedMessage, RelayUrl, MAX_PACKET_SIZE},
2426
};
@@ -38,11 +40,9 @@ pub(super) enum RelayActorMessage {
3840
contents: RelayContents,
3941
peer: PublicKey,
4042
},
41-
Connect {
43+
ConnectAsHomeRelay {
4244
url: RelayUrl,
43-
peer: Option<PublicKey>,
4445
},
45-
NotePreferred(RelayUrl),
4646
MaybeCloseRelaysOnRebind(Vec<IpAddr>),
4747
}
4848

@@ -79,6 +79,7 @@ enum ActiveRelayMessage {
7979
GetPeerRoute(PublicKey, oneshot::Sender<Option<relay::http::Client>>),
8080
GetClient(oneshot::Sender<relay::http::Client>),
8181
NotePreferred(bool),
82+
PkarrPublish(pkarr::SignedPacket),
8283
Shutdown,
8384
}
8485

@@ -133,6 +134,9 @@ impl ActiveRelay {
133134
ActiveRelayMessage::NotePreferred(is_preferred) => {
134135
self.relay_client.note_preferred(is_preferred).await;
135136
}
137+
ActiveRelayMessage::PkarrPublish(packet) => {
138+
self.relay_client.pkarr_publish(packet).await;
139+
}
136140
ActiveRelayMessage::GetPeerRoute(peer, r) => {
137141
let res = if self.relay_routes.contains(&peer) {
138142
Some(self.relay_client.clone())
@@ -349,11 +353,8 @@ impl RelayActor {
349353
} => {
350354
self.send_relay(&url, contents, peer).await;
351355
}
352-
RelayActorMessage::Connect { url, peer } => {
353-
self.connect_relay(&url, peer.as_ref()).await;
354-
}
355-
RelayActorMessage::NotePreferred(my_relay) => {
356-
self.note_preferred(&my_relay).await;
356+
RelayActorMessage::ConnectAsHomeRelay { url } => {
357+
self.connect_relay_as_home(&url).await;
357358
}
358359
RelayActorMessage::MaybeCloseRelaysOnRebind(ifs) => {
359360
self.maybe_close_relays_on_rebind(&ifs).await;
@@ -420,6 +421,29 @@ impl RelayActor {
420421
}
421422

422423
/// Connect to the given relay node.
424+
async fn connect_relay_as_home(&mut self, url: &RelayUrl) {
425+
self.connect_relay(url, None).await;
426+
self.note_preferred(url).await;
427+
if let Err(err) = self.pkarr_announce_to_relay(url).await {
428+
warn!(?err, %url, "failed to send pkarr self-announce to home derper");
429+
}
430+
}
431+
432+
async fn pkarr_announce_to_relay(&self, my_relay: &RelayUrl) -> anyhow::Result<()> {
433+
if self.conn.pkarr_announce {
434+
let s = self
435+
.active_relay
436+
.iter()
437+
.find_map(|(relay_url, (s, _))| (relay_url == my_relay).then_some(s))
438+
.context("home derp not in list of active derps")?;
439+
let info = NodeInfo::new(self.conn.secret_key.public(), Some(my_relay.clone()));
440+
let packet = info.to_pkarr_signed_packet(&self.conn.secret_key, DEFAULT_PKARR_TTL)?;
441+
s.send(ActiveRelayMessage::PkarrPublish(packet)).await?;
442+
}
443+
Ok(())
444+
}
445+
446+
/// Connect to the given derp node.
423447
async fn connect_relay(
424448
&mut self,
425449
url: &RelayUrl,
@@ -583,7 +607,7 @@ impl RelayActor {
583607
async fn close_or_reconnect_relay(&mut self, url: &RelayUrl, why: &'static str) {
584608
self.close_relay(url, why).await;
585609
if self.conn.my_relay().as_ref() == Some(url) {
586-
self.connect_relay(url, None).await;
610+
self.connect_relay_as_home(url).await;
587611
}
588612
}
589613

iroh-net/src/relay/client.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ use super::{
2222
};
2323

2424
use crate::key::{PublicKey, SecretKey};
25+
use crate::relay::codec::PkarrWirePacket;
2526
use crate::util::AbortingJoinHandle;
2627

2728
const CLIENT_RECV_TIMEOUT: Duration = Duration::from_secs(120);
@@ -122,6 +123,17 @@ impl Client {
122123
Ok(())
123124
}
124125

126+
/// Send a pkarr packet to the derper to publish for us.
127+
///
128+
/// Must be signed by our secret key, otherwise the derper will reject it.
129+
pub async fn pkarr_publish_packet(&self, packet: pkarr::SignedPacket) -> Result<()> {
130+
self.inner
131+
.writer_channel
132+
.send(ClientWriterMessage::PkarrPublish(packet))
133+
.await?;
134+
Ok(())
135+
}
136+
125137
/// The local address that the [`Client`] is listening on.
126138
pub fn local_addr(&self) -> Result<SocketAddr> {
127139
Ok(self.inner.local_addr)
@@ -205,6 +217,8 @@ enum ClientWriterMessage {
205217
Ping([u8; 8]),
206218
/// Tell the server whether or not this client is the user's preferred client
207219
NotePreferred(bool),
220+
/// Publish a pkarr signed packet about ourselves
221+
PkarrPublish(pkarr::SignedPacket),
208222
/// Shutdown the writer
209223
Shutdown,
210224
}
@@ -239,6 +253,11 @@ impl<W: AsyncWrite + Unpin + Send + 'static> ClientWriter<W> {
239253
write_frame(&mut self.writer, Frame::NotePreferred { preferred }, None).await?;
240254
self.writer.flush().await?;
241255
}
256+
ClientWriterMessage::PkarrPublish(packet) => {
257+
let packet = PkarrWirePacket::V0(packet.as_bytes());
258+
write_frame(&mut self.writer, Frame::PkarrPublish { packet }, None).await?;
259+
self.writer.flush().await?;
260+
}
242261
ClientWriterMessage::Shutdown => {
243262
return Ok(());
244263
}
@@ -331,6 +350,7 @@ impl ClientBuilder {
331350
};
332351
let mut buf = encrypted_message.to_vec();
333352
shared_secret.open(&mut buf)?;
353+
334354
let info: ServerInfo = postcard::from_bytes(&buf)?;
335355
if info.version != PROTOCOL_VERSION {
336356
bail!(

0 commit comments

Comments
 (0)