iroh_base/endpoint_addr.rs
1//! Addressing for iroh endpoints.
2//!
3//! This module contains some common addressing types for iroh. An endpoint is uniquely
4//! identified by the [`EndpointId`] but that does not make it addressable on the network layer.
5//! For this the addition of a [`RelayUrl`] and/or direct addresses are required.
6//!
7//! The primary way of addressing an endpoint is by using the [`EndpointAddr`].
8
9use std::{collections::BTreeSet, net::SocketAddr};
10
11use serde::{Deserialize, Serialize};
12
13use crate::{EndpointId, PublicKey, RelayUrl};
14
15/// Network-level addressing information for an iroh endpoint.
16///
17/// This combines an endpoint's identifier with network-level addressing information of how to
18/// contact the endpoint.
19///
20/// To establish a network connection to an endpoint both the [`EndpointId`] and one or more network
21/// paths are needed. The network paths can come from various sources, current sources can come from
22///
23/// - A [discovery] service which can provide routing information for a given [`EndpointId`].
24///
25/// - A [`RelayUrl`] of the endpoint's [home relay], this allows establishing the connection via
26/// the Relay server and is very reliable.
27///
28/// - One or more *IP based addresses* on which the endpoint might be reachable. Depending on the
29/// network location of both endpoints it might not be possible to establish a direct
30/// connection without the help of a [Relay server].
31///
32/// This structure will always contain the required [`EndpointId`] and will contain an optional
33/// number of other addressing information. It is a generic addressing type used whenever a connection
34/// to other endpoints needs to be established.
35///
36/// [discovery]: https://docs.rs/iroh/*/iroh/index.html#endpoint-discovery
37/// [home relay]: https://docs.rs/iroh/*/iroh/relay/index.html
38/// [Relay server]: https://docs.rs/iroh/*/iroh/index.html#relay-servers
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
40pub struct EndpointAddr {
41 /// The endpoint's identifier.
42 pub id: EndpointId,
43 /// The endpoint's addresses
44 pub addrs: BTreeSet<TransportAddr>,
45}
46
47/// Available address types.
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
49#[non_exhaustive]
50pub enum TransportAddr {
51 /// Relays
52 Relay(RelayUrl),
53 /// IP based addresses
54 Ip(SocketAddr),
55}
56
57impl TransportAddr {
58 /// Whether this is a transport address via a relay server.
59 pub fn is_relay(&self) -> bool {
60 matches!(self, Self::Relay(_))
61 }
62
63 /// Whether this is an IP transport address.
64 pub fn is_ip(&self) -> bool {
65 matches!(self, Self::Ip(_))
66 }
67}
68
69impl EndpointAddr {
70 /// Creates a new [`EndpointAddr`] with no network level addresses.
71 ///
72 /// This still is usable with e.g. a discovery service to establish a connection,
73 /// depending on the situation.
74 pub fn new(id: PublicKey) -> Self {
75 EndpointAddr {
76 id,
77 addrs: Default::default(),
78 }
79 }
80
81 /// Creates a new [`EndpointAddr`] from its parts.
82 pub fn from_parts(id: PublicKey, addrs: impl IntoIterator<Item = TransportAddr>) -> Self {
83 Self {
84 id,
85 addrs: addrs.into_iter().collect(),
86 }
87 }
88
89 /// Adds a [`RelayUrl`] address.
90 pub fn with_relay_url(mut self, relay_url: RelayUrl) -> Self {
91 self.addrs.insert(TransportAddr::Relay(relay_url));
92 self
93 }
94
95 /// Adds an IP based address.
96 pub fn with_ip_addr(mut self, addr: SocketAddr) -> Self {
97 self.addrs.insert(TransportAddr::Ip(addr));
98 self
99 }
100
101 /// Adds a list of addresses.
102 pub fn with_addrs(mut self, addrs: impl IntoIterator<Item = TransportAddr>) -> Self {
103 for addr in addrs.into_iter() {
104 self.addrs.insert(addr);
105 }
106 self
107 }
108
109 /// Returns true, if only a [`EndpointId`] is present.
110 pub fn is_empty(&self) -> bool {
111 self.addrs.is_empty()
112 }
113
114 /// Returns a list of IP addresses of this peer.
115 pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr> {
116 self.addrs.iter().filter_map(|addr| match addr {
117 TransportAddr::Ip(addr) => Some(addr),
118 _ => None,
119 })
120 }
121
122 /// Returns a list of relay urls of this peer.
123 ///
124 /// In practice this is expected to be zero or one home relay for all known cases currently.
125 pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl> {
126 self.addrs.iter().filter_map(|addr| match addr {
127 TransportAddr::Relay(url) => Some(url),
128 _ => None,
129 })
130 }
131}
132
133impl From<EndpointId> for EndpointAddr {
134 fn from(endpoint_id: EndpointId) -> Self {
135 EndpointAddr::new(endpoint_id)
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
144 #[non_exhaustive]
145 enum NewAddrType {
146 /// Relays
147 Relay(RelayUrl),
148 /// IP based addresses
149 Ip(SocketAddr),
150 /// New addr type for testing
151 Cool(u16),
152 }
153
154 #[test]
155 fn test_roundtrip_new_addr_type() {
156 let old = vec![
157 TransportAddr::Ip("127.0.0.1:9".parse().unwrap()),
158 TransportAddr::Relay("https://example.com".parse().unwrap()),
159 ];
160 let old_ser = postcard::to_stdvec(&old).unwrap();
161 let old_back: Vec<TransportAddr> = postcard::from_bytes(&old_ser).unwrap();
162 assert_eq!(old, old_back);
163
164 let new = vec![
165 NewAddrType::Ip("127.0.0.1:9".parse().unwrap()),
166 NewAddrType::Relay("https://example.com".parse().unwrap()),
167 NewAddrType::Cool(4),
168 ];
169 let new_ser = postcard::to_stdvec(&new).unwrap();
170 let new_back: Vec<NewAddrType> = postcard::from_bytes(&new_ser).unwrap();
171
172 assert_eq!(new, new_back);
173
174 // serialize old into new
175 let old_new_back: Vec<NewAddrType> = postcard::from_bytes(&old_ser).unwrap();
176
177 assert_eq!(
178 old_new_back,
179 vec![
180 NewAddrType::Ip("127.0.0.1:9".parse().unwrap()),
181 NewAddrType::Relay("https://example.com".parse().unwrap()),
182 ]
183 );
184 }
185}