Skip to main content

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, fmt, net::SocketAddr};
10
11use data_encoding::HEXLOWER;
12use n0_error::stack_error;
13use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
14
15use crate::{EndpointId, PublicKey, RelayUrl};
16
17/// Network-level addressing information for an iroh endpoint.
18///
19/// This combines an endpoint's identifier with network-level addressing information of how to
20/// contact the endpoint.
21///
22/// To establish a network connection to an endpoint both the [`EndpointId`] and one or more network
23/// paths are needed.  The network paths can come from various sources:
24///
25/// - An [Address Lookup] service which can provide routing information for a given [`EndpointId`].
26///
27/// - A [`RelayUrl`] of the endpoint's [home relay], this allows establishing the connection via
28///   the Relay server and is very reliable.
29///
30/// - One or more *IP based addresses* on which the endpoint might be reachable.  Depending on the
31///   network location of both endpoints it might not be possible to establish a direct
32///   connection without the help of a [Relay server].
33///
34/// This structure will always contain the required [`EndpointId`] and will contain an optional
35/// number of other addressing information.  It is a generic addressing type used whenever a connection
36/// to other endpoints needs to be established.
37///
38/// [Address Lookup]: https://docs.rs/iroh/*/iroh/index.html#address-lookup
39/// [home relay]: https://docs.rs/iroh/*/iroh/relay/index.html
40/// [Relay server]: https://docs.rs/iroh/*/iroh/index.html#relay-servers
41#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
42pub struct EndpointAddr {
43    /// The endpoint's identifier.
44    pub id: EndpointId,
45    /// The endpoint's addresses.
46    pub addrs: BTreeSet<TransportAddr>,
47}
48
49/// Available address types.
50#[derive(
51    derive_more::Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash,
52)]
53#[non_exhaustive]
54pub enum TransportAddr {
55    /// A relay server address.
56    #[debug("Relay({_0})")]
57    Relay(RelayUrl),
58    /// An IP based address.
59    Ip(SocketAddr),
60    /// Custom transport address
61    Custom(CustomAddr),
62}
63
64impl TransportAddr {
65    /// Whether this is a transport address via a relay server.
66    pub fn is_relay(&self) -> bool {
67        matches!(self, Self::Relay(_))
68    }
69
70    /// Whether this is an IP transport address.
71    pub fn is_ip(&self) -> bool {
72        matches!(self, Self::Ip(_))
73    }
74
75    /// Whether this is a custom transport address.
76    pub fn is_custom(&self) -> bool {
77        matches!(self, Self::Custom(_))
78    }
79}
80
81impl fmt::Display for TransportAddr {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        match self {
84            Self::Relay(url) => write!(f, "relay:{url}"),
85            Self::Ip(addr) => write!(f, "ip:{addr}"),
86            Self::Custom(addr) => write!(f, "custom:{addr}"),
87        }
88    }
89}
90
91impl EndpointAddr {
92    /// Creates a new [`EndpointAddr`] with no network level addresses.
93    ///
94    /// This still is usable with e.g. an address lookup service to establish a connection,
95    /// depending on the situation.
96    pub fn new(id: PublicKey) -> Self {
97        EndpointAddr {
98            id,
99            addrs: Default::default(),
100        }
101    }
102
103    /// Creates a new [`EndpointAddr`] from its parts.
104    pub fn from_parts(id: PublicKey, addrs: impl IntoIterator<Item = TransportAddr>) -> Self {
105        Self {
106            id,
107            addrs: addrs.into_iter().collect(),
108        }
109    }
110
111    /// Adds a [`RelayUrl`] address.
112    pub fn with_relay_url(mut self, relay_url: RelayUrl) -> Self {
113        self.addrs.insert(TransportAddr::Relay(relay_url));
114        self
115    }
116
117    /// Adds an IP based address.
118    pub fn with_ip_addr(mut self, addr: SocketAddr) -> Self {
119        self.addrs.insert(TransportAddr::Ip(addr));
120        self
121    }
122
123    /// Adds a list of addresses.
124    pub fn with_addrs(mut self, addrs: impl IntoIterator<Item = TransportAddr>) -> Self {
125        for addr in addrs.into_iter() {
126            self.addrs.insert(addr);
127        }
128        self
129    }
130
131    /// Returns true if only an [`EndpointId`] is present.
132    pub fn is_empty(&self) -> bool {
133        self.addrs.is_empty()
134    }
135
136    /// Returns an iterator over the IP addresses of this endpoint address.
137    pub fn ip_addrs(&self) -> impl Iterator<Item = &SocketAddr> {
138        self.addrs.iter().filter_map(|addr| match addr {
139            TransportAddr::Ip(addr) => Some(addr),
140            _ => None,
141        })
142    }
143
144    /// Returns an iterator over the relay URLs of this endpoint address.
145    ///
146    ///  In practice this is expected to be zero or one home relay for all known cases currently.
147    pub fn relay_urls(&self) -> impl Iterator<Item = &RelayUrl> {
148        self.addrs.iter().filter_map(|addr| match addr {
149            TransportAddr::Relay(url) => Some(url),
150            _ => None,
151        })
152    }
153}
154
155impl From<EndpointId> for EndpointAddr {
156    fn from(endpoint_id: EndpointId) -> Self {
157        EndpointAddr::new(endpoint_id)
158    }
159}
160
161/// A custom transport address consisting of a transport id and opaque address data.
162///
163/// This is a generic address type that allows external crates to implement custom
164/// transports for iroh.
165///
166/// Transport ids are freely chosen u64 numbers. A registry for well-known transport ids
167/// is maintained at <https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md>.
168/// The opaque address data is not validated or size-limited in any way.
169///
170/// # String encoding
171///
172/// Used by [`Display`] and [`FromStr`] implementations.
173/// Format: `<id>_<data>` where `<id>` is the transport id as lowercase hex (no `0x`
174/// prefix, no leading zeros) and `<data>` is the address bytes as lowercase hex,
175/// separated by `_`.
176///
177/// # Binary encoding
178///
179/// Used by [`Self::to_vec`] and [`Self::from_bytes`].
180/// Format: 8-byte little-endian `u64` transport id, followed by raw address data bytes.
181/// The minimum valid length is 8 bytes (id only with empty data).
182///
183/// [`Display`]: std::fmt::Display
184/// [`FromStr`]: std::str::FromStr
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
186pub struct CustomAddr {
187    /// The transport id.
188    id: u64,
189    /// Opaque address data for this transport.
190    data: CustomAddrBytes,
191}
192
193impl fmt::Display for CustomAddr {
194    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
195        write!(f, "{:x}_{}", self.id, HEXLOWER.encode(self.data.as_bytes()))
196    }
197}
198
199impl std::str::FromStr for CustomAddr {
200    type Err = CustomAddrParseError;
201
202    fn from_str(s: &str) -> Result<Self, Self::Err> {
203        let Some((id_str, data_str)) = s.split_once('_') else {
204            return Err(CustomAddrParseError::MissingSeparator);
205        };
206        let Ok(id) = u64::from_str_radix(id_str, 16) else {
207            return Err(CustomAddrParseError::InvalidId);
208        };
209        let Ok(data) = HEXLOWER.decode(data_str.as_bytes()) else {
210            return Err(CustomAddrParseError::InvalidData);
211        };
212        Ok(Self::from_parts(id, &data))
213    }
214}
215
216/// Error returned when parsing a [`CustomAddr`] from its string encoding fails.
217///
218/// Parsing a string into a [`CustomAddr`] represents just the first part of
219/// validation. Even if the string is well-formed, the resulting [`CustomAddr`] might
220/// still have an invalid data size or format for the transport type.
221#[stack_error(derive)]
222#[allow(missing_docs)]
223pub enum CustomAddrParseError {
224    /// Missing `_` separator between id and data.
225    #[error("missing '_' separator")]
226    MissingSeparator,
227    /// Invalid hex-encoded id.
228    #[error("invalid id")]
229    InvalidId,
230    /// Invalid hex-encoded data.
231    #[error("invalid data")]
232    InvalidData,
233}
234
235/// Inline or heap storage, chosen by length. Serializes as plain bytes: the cutoff is an
236/// implementation detail, and `copy_from_slice` must stay the only constructor.
237#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
238enum CustomAddrBytes {
239    Inline { size: u8, data: [u8; 30] },
240    Heap(Box<[u8]>),
241}
242
243impl Serialize for CustomAddrBytes {
244    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
245        serializer.serialize_bytes(self.as_bytes())
246    }
247}
248
249impl<'de> Deserialize<'de> for CustomAddrBytes {
250    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
251        struct BytesVisitor;
252
253        impl<'de> de::Visitor<'de> for BytesVisitor {
254            type Value = CustomAddrBytes;
255
256            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
257                f.write_str("custom transport address bytes")
258            }
259
260            fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
261                Ok(CustomAddrBytes::copy_from_slice(v))
262            }
263
264            fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
265                Ok(CustomAddrBytes::copy_from_slice(&v))
266            }
267
268            /// Needed for json, which has no byte type and encodes bytes as a number
269            /// array, so `deserialize_bytes` comes back as a sequence.
270            fn visit_seq<A: de::SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
271                // Does not reserve from the attacker-controlled `size_hint`.
272                let mut data = Vec::new();
273                while let Some(byte) = seq.next_element::<u8>()? {
274                    data.push(byte);
275                }
276                Ok(CustomAddrBytes::copy_from_slice(&data))
277            }
278        }
279
280        deserializer.deserialize_bytes(BytesVisitor)
281    }
282}
283
284impl fmt::Debug for CustomAddrBytes {
285    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
286        if !f.alternate() {
287            write!(f, "[{}]", HEXLOWER.encode(self.as_bytes()))
288        } else {
289            let bytes = self.as_bytes();
290            match self {
291                Self::Inline { .. } => write!(f, "Inline[{}]", HEXLOWER.encode(bytes)),
292                Self::Heap(_) => write!(f, "Heap[{}]", HEXLOWER.encode(bytes)),
293            }
294        }
295    }
296}
297
298impl From<(u64, &[u8])> for CustomAddr {
299    fn from((id, data): (u64, &[u8])) -> Self {
300        Self::from_parts(id, data)
301    }
302}
303
304impl CustomAddrBytes {
305    fn len(&self) -> usize {
306        match self {
307            Self::Inline { size, .. } => *size as usize,
308            Self::Heap(data) => data.len(),
309        }
310    }
311
312    fn as_bytes(&self) -> &[u8] {
313        match self {
314            Self::Inline { size, data } => &data[..*size as usize],
315            Self::Heap(data) => data,
316        }
317    }
318
319    fn copy_from_slice(data: &[u8]) -> Self {
320        if data.len() <= 30 {
321            let mut inline = [0u8; 30];
322            inline[..data.len()].copy_from_slice(data);
323            Self::Inline {
324                size: data.len() as u8,
325                data: inline,
326            }
327        } else {
328            Self::Heap(data.to_vec().into_boxed_slice())
329        }
330    }
331}
332
333impl CustomAddr {
334    /// Creates a new [`CustomAddr`] from a transport id and raw address data.
335    pub fn from_parts(id: u64, data: &[u8]) -> Self {
336        Self {
337            id,
338            data: CustomAddrBytes::copy_from_slice(data),
339        }
340    }
341
342    /// Returns the transport id.
343    ///
344    /// You can freely choose this. There is a table of reserved custom transport ids in
345    /// <https://github.com/n0-computer/iroh/blob/main/TRANSPORTS.md>, where you could
346    /// submit your transport for registration to get a reserved id.
347    ///
348    /// But this is only relevant if you care for interop.
349    pub fn id(&self) -> u64 {
350        self.id
351    }
352
353    /// Returns the opaque address data for this transport.
354    ///
355    /// Below a certain size (currently 30 bytes) this is stored inline, otherwise on the heap.
356    ///
357    /// Note that there are no guarantees about the size of this data. When parsing custom
358    /// addresses you must be prepared to handle unexpected sizes here.
359    pub fn data(&self) -> &[u8] {
360        self.data.as_bytes()
361    }
362
363    /// Serializes to the binary encoding.
364    ///
365    /// See [`CustomAddr`] docs for details on the encoding.
366    pub fn to_vec(&self) -> Vec<u8> {
367        let mut out = vec![0u8; 8 + self.data.len()];
368        out[..8].copy_from_slice(&self.id().to_le_bytes());
369        out[8..].copy_from_slice(self.data());
370        out
371    }
372
373    /// Parses from the binary encoding.
374    ///
375    /// See [`CustomAddr`] docs for details on the encoding.
376    pub fn from_bytes(data: &[u8]) -> Result<Self, &'static str> {
377        if data.len() < 8 {
378            return Err("data too short");
379        }
380        let id = u64::from_le_bytes(data[..8].try_into().expect("data length checked above"));
381        let data = &data[8..];
382        Ok(Self::from_parts(id, data))
383    }
384}
385
386#[cfg(test)]
387mod tests {
388    use super::*;
389
390    #[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
391    #[non_exhaustive]
392    enum NewAddrType {
393        /// Relays
394        Relay(RelayUrl),
395        /// IP based addresses
396        Ip(SocketAddr),
397        /// New addr type for testing
398        Cool(u16),
399    }
400
401    #[test]
402    fn test_roundtrip_new_addr_type() {
403        let old = vec![
404            TransportAddr::Ip("127.0.0.1:9".parse().unwrap()),
405            TransportAddr::Relay("https://example.com".parse().unwrap()),
406        ];
407        let old_ser = postcard::to_stdvec(&old).unwrap();
408        let old_back: Vec<TransportAddr> = postcard::from_bytes(&old_ser).unwrap();
409        assert_eq!(old, old_back);
410
411        let new = vec![
412            NewAddrType::Ip("127.0.0.1:9".parse().unwrap()),
413            NewAddrType::Relay("https://example.com".parse().unwrap()),
414            NewAddrType::Cool(4),
415        ];
416        let new_ser = postcard::to_stdvec(&new).unwrap();
417        let new_back: Vec<NewAddrType> = postcard::from_bytes(&new_ser).unwrap();
418
419        assert_eq!(new, new_back);
420
421        // serialize old into new
422        let old_new_back: Vec<NewAddrType> = postcard::from_bytes(&old_ser).unwrap();
423
424        assert_eq!(
425            old_new_back,
426            vec![
427                NewAddrType::Ip("127.0.0.1:9".parse().unwrap()),
428                NewAddrType::Relay("https://example.com".parse().unwrap()),
429            ]
430        );
431    }
432
433    #[test]
434    fn test_custom_addr_roundtrip() {
435        // Small id, small data (e.g., Bluetooth MAC)
436        let addr = CustomAddr::from_parts(1, &[0xa1, 0xb2, 0xc3, 0xd4, 0xe5, 0xf6]);
437        let s = addr.to_string();
438        assert_eq!(s, "1_a1b2c3d4e5f6");
439        let parsed: CustomAddr = s.parse().unwrap();
440        assert_eq!(addr, parsed);
441
442        // Larger id, 32-byte data (e.g., Tor pubkey)
443        let addr = CustomAddr::from_parts(42, &[0xab; 32]);
444        let s = addr.to_string();
445        assert_eq!(
446            s,
447            "2a_abababababababababababababababababababababababababababababababab"
448        );
449        let parsed: CustomAddr = s.parse().unwrap();
450        assert_eq!(addr, parsed);
451
452        // Zero id, empty data
453        let addr = CustomAddr::from_parts(0, &[]);
454        let s = addr.to_string();
455        assert_eq!(s, "0_");
456        let parsed: CustomAddr = s.parse().unwrap();
457        assert_eq!(addr, parsed);
458
459        // Large id
460        let addr = CustomAddr::from_parts(0xdeadbeef, &[0x01, 0x02]);
461        let s = addr.to_string();
462        assert_eq!(s, "deadbeef_0102");
463        let parsed: CustomAddr = s.parse().unwrap();
464        assert_eq!(addr, parsed);
465    }
466
467    /// The serialized form is just the bytes, so no encoding can disagree with itself.
468    #[test]
469    fn test_custom_addr_serde_roundtrip() {
470        for len in [0usize, 1, 29, 30, 31, 32, 255] {
471            let data: Vec<u8> = (0..len).map(|i| i as u8).collect();
472            let addr = CustomAddr::from_parts(0x544f52, &data);
473            let ser = postcard::to_stdvec(&addr).unwrap();
474            let back: CustomAddr = postcard::from_bytes(&ser).unwrap();
475            assert_eq!(addr, back);
476            assert_eq!(back.data(), &data[..]);
477
478            // The bytes are those of a plain `Vec<u8>`, with no trace of the cutoff.
479            let expected = postcard::to_stdvec(&(0x544f52u64, &data)).unwrap();
480            assert_eq!(ser, expected);
481
482            // Self-describing formats go through `visit_seq`.
483            let json = serde_json::to_string(&addr).unwrap();
484            let back: CustomAddr = serde_json::from_str(&json).unwrap();
485            assert_eq!(addr, back);
486        }
487    }
488
489    /// Equal addresses must be equal whatever route they arrive by.
490    #[test]
491    fn test_custom_addr_deserialize_is_canonical() {
492        for len in [5usize, 30, 31] {
493            let data = vec![9u8; len];
494            let addr = CustomAddr::from_parts(0x544f52, &data);
495            let ser = postcard::to_stdvec(&addr).unwrap();
496            let back: CustomAddr = postcard::from_bytes(&ser).unwrap();
497
498            assert_eq!(back.data(), addr.data());
499            assert_eq!(back, addr);
500            let set: BTreeSet<CustomAddr> = [back, addr].into_iter().collect();
501            assert_eq!(set.len(), 1);
502        }
503    }
504
505    /// The reported crash: an inline `size` larger than the 30-byte buffer, which the
506    /// derived `Deserialize` used to accept and every reader then panicked on.
507    #[test]
508    fn test_custom_addr_deserialize_oversized_inline() {
509        // The old encoding, with the inline size byte set to 255.
510        let mut crafted = postcard::to_stdvec(&(0x544f52u64, 0u32, 30u8, [9u8; 30])).unwrap();
511        let idx = crafted.len() - 31;
512        assert_eq!(crafted[idx], 30);
513        crafted[idx] = 255;
514
515        if let Ok(addr) = postcard::from_bytes::<CustomAddr>(&crafted) {
516            assert_eq!(addr.data().len(), addr.to_vec().len() - 8);
517            let _ = format!("{addr:?}");
518            let _ = format!("{addr:#?}");
519            let _ = addr.to_string();
520        }
521    }
522
523    /// The same, reached the way an attacker would: an [`EndpointAddr`] from a ticket.
524    #[test]
525    fn test_endpoint_addr_deserialize_arbitrary_bytes() {
526        let id = PublicKey::from_bytes(&[0; 32]).unwrap();
527        let addr = EndpointAddr {
528            id,
529            addrs: [TransportAddr::Custom(CustomAddr::from_parts(
530                0x544f52, &[9u8; 30],
531            ))]
532            .into_iter()
533            .collect(),
534        };
535        let ser = postcard::to_stdvec(&addr).unwrap();
536
537        // Flipping any single byte must not make a value that panics when read.
538        for idx in 0..ser.len() {
539            for bit in 0..8 {
540                let mut crafted = ser.clone();
541                crafted[idx] ^= 1 << bit;
542                if let Ok(evil) = postcard::from_bytes::<EndpointAddr>(&crafted) {
543                    let _ = format!("{evil:?}");
544                    for addr in &evil.addrs {
545                        if let TransportAddr::Custom(custom) = addr {
546                            let _ = custom.data();
547                            let _ = custom.to_vec();
548                            let _ = custom.to_string();
549                        }
550                    }
551                }
552            }
553        }
554    }
555
556    #[test]
557    fn test_custom_addr_parse_errors() {
558        // Missing separator
559        assert!("abc123".parse::<CustomAddr>().is_err());
560
561        // Invalid id (not hex)
562        assert!("xyz_0102".parse::<CustomAddr>().is_err());
563
564        // Invalid data (not hex)
565        assert!("1_ghij".parse::<CustomAddr>().is_err());
566
567        // Odd-length hex data
568        assert!("1_abc".parse::<CustomAddr>().is_err());
569    }
570}