iroh_relay/
relay_map.rs

1//! based on tailscale/tailcfg/derpmap.go
2
3use std::{
4    collections::BTreeMap,
5    fmt,
6    sync::{Arc, RwLock},
7};
8
9use iroh_base::{RelayUrl, RelayUrlParseError};
10use serde::{Deserialize, Serialize};
11
12use crate::defaults::DEFAULT_RELAY_QUIC_PORT;
13
14/// List of relay server configurations to be used in an iroh endpoint.
15///
16/// A [`RelayMap`] can be constructed from an iterator of [`RelayConfig`] or [`RelayUrl]`,
17/// or by creating an empty relay map with [`RelayMap::empty`] and then adding entries with
18/// [`RelayMap::insert`].
19///
20/// Example:
21/// ```
22/// # use std::str::FromStr;
23/// # use iroh_base::RelayUrl;
24/// # use iroh_relay::RelayMap;
25/// let relay1 = RelayUrl::from_str("https://relay1.example.org").unwrap();
26/// let relay2 = RelayUrl::from_str("https://relay2.example.org").unwrap();
27/// let map = RelayMap::from_iter(vec![relay1, relay2]);
28/// ```
29#[derive(Debug, Clone)]
30pub struct RelayMap {
31    /// A map of the different relay IDs to the [`RelayConfig`] information
32    relays: Arc<RwLock<BTreeMap<RelayUrl, Arc<RelayConfig>>>>,
33}
34
35impl PartialEq for RelayMap {
36    fn eq(&self, other: &Self) -> bool {
37        let this = self.relays.read().expect("poisoned");
38        let that = other.relays.read().expect("poisoned");
39        this.eq(&*that)
40    }
41}
42
43impl Eq for RelayMap {}
44
45impl RelayMap {
46    /// Creates an empty relay map.
47    pub fn empty() -> Self {
48        Self {
49            relays: Default::default(),
50        }
51    }
52
53    /// Creates a [`RelayMap`] from an iterator.
54    ///
55    /// The conversion from a URL to a [`RelayConfig`] is done the same as when parsing it directly,
56    /// which means it is assumed to run QUIC on default settings as defined in [`RelayQuicConfig::default`].
57    ///
58    /// # Example
59    /// ```rust
60    /// # use iroh_relay::RelayMap;
61    /// let map =
62    ///     RelayMap::try_from_iter(["https://relay_0.cool.com", "https://relay_1.cool.com"]).unwrap();
63    /// ```
64    pub fn try_from_iter<'a, T: IntoIterator<Item = &'a str>>(
65        urls: T,
66    ) -> Result<Self, RelayUrlParseError> {
67        let relays: BTreeMap<RelayUrl, Arc<RelayConfig>> = urls
68            .into_iter()
69            .map(|t| {
70                t.parse()
71                    .map(|url: RelayUrl| (url.clone(), Arc::new(RelayConfig::from(url))))
72            })
73            .collect::<Result<_, _>>()?;
74        Ok(Self {
75            relays: Arc::new(RwLock::new(relays)),
76        })
77    }
78
79    /// Returns the URLs of all servers in this relay map.
80    ///
81    /// This function is generic over the container to collect into. If you simply want a list
82    /// of URLs, call this with `map.urls::<Vec<_>>()` to get a `Vec<RelayUrl>`.
83    pub fn urls<T>(&self) -> T
84    where
85        T: FromIterator<RelayUrl>,
86    {
87        self.relays
88            .read()
89            .expect("poisoned")
90            .keys()
91            .cloned()
92            .collect::<T>()
93    }
94
95    /// Returns a list with the [`RelayConfig`] for each relay in this relay map.
96    ///
97    /// This function is generic over the container to collect into. If you simply want a list
98    /// of URLs, call this with `map.relays::<Vec<_>>()` to get a `Vec<RelayConfig>`.
99    pub fn relays<T>(&self) -> T
100    where
101        T: FromIterator<Arc<RelayConfig>>,
102    {
103        self.relays
104            .read()
105            .expect("poisoned")
106            .values()
107            .cloned()
108            .collect::<T>()
109    }
110
111    /// Returns `true` if a relay with `url` is contained in this this relay map.
112    pub fn contains(&self, url: &RelayUrl) -> bool {
113        self.relays.read().expect("poisoned").contains_key(url)
114    }
115
116    /// Returns the config for a relay.
117    pub fn get(&self, url: &RelayUrl) -> Option<Arc<RelayConfig>> {
118        self.relays.read().expect("poisoned").get(url).cloned()
119    }
120
121    /// Returns the number of relays in this relay map.
122    pub fn len(&self) -> usize {
123        self.relays.read().expect("poisoned").len()
124    }
125
126    /// Returns `true` if this relay map is empty.
127    pub fn is_empty(&self) -> bool {
128        self.relays.read().expect("poisoned").is_empty()
129    }
130
131    /// Inserts a new relay into the relay map.
132    pub fn insert(&self, url: RelayUrl, endpoint: Arc<RelayConfig>) -> Option<Arc<RelayConfig>> {
133        self.relays.write().expect("poisoned").insert(url, endpoint)
134    }
135
136    /// Removes an existing relay by its URL.
137    pub fn remove(&self, url: &RelayUrl) -> Option<Arc<RelayConfig>> {
138        self.relays.write().expect("poisoned").remove(url)
139    }
140
141    /// Extends this `RelayMap` with another one.
142    pub fn extend(&self, other: &RelayMap) {
143        let mut a = self.relays.write().expect("poisoned");
144        let b = other.relays.read().expect("poisoned");
145        a.extend(b.iter().map(|(a, b)| (a.clone(), b.clone())));
146    }
147}
148
149impl FromIterator<RelayConfig> for RelayMap {
150    fn from_iter<T: IntoIterator<Item = RelayConfig>>(iter: T) -> Self {
151        Self::from_iter(iter.into_iter().map(Arc::new))
152    }
153}
154
155impl FromIterator<Arc<RelayConfig>> for RelayMap {
156    fn from_iter<T: IntoIterator<Item = Arc<RelayConfig>>>(iter: T) -> Self {
157        Self {
158            relays: Arc::new(RwLock::new(
159                iter.into_iter()
160                    .map(|config| (config.url.clone(), config))
161                    .collect(),
162            )),
163        }
164    }
165}
166
167impl From<RelayUrl> for RelayMap {
168    /// Creates a [`RelayMap`] from a [`RelayUrl`].
169    ///
170    /// The [`RelayConfig`]s in the [`RelayMap`] will have the default QUIC address
171    /// discovery ports.
172    fn from(value: RelayUrl) -> Self {
173        Self {
174            relays: Arc::new(RwLock::new(
175                [(value.clone(), Arc::new(value.into()))].into(),
176            )),
177        }
178    }
179}
180
181impl From<RelayConfig> for RelayMap {
182    fn from(value: RelayConfig) -> Self {
183        Self {
184            relays: Arc::new(RwLock::new([(value.url.clone(), Arc::new(value))].into())),
185        }
186    }
187}
188
189impl FromIterator<RelayUrl> for RelayMap {
190    /// Creates a [`RelayMap`] from an iterator of [`RelayUrl`].
191    ///
192    /// The [`RelayConfig`]s in the [`RelayMap`] will have the default QUIC address
193    /// discovery ports.
194    fn from_iter<T: IntoIterator<Item = RelayUrl>>(iter: T) -> Self {
195        Self {
196            relays: Arc::new(RwLock::new(
197                iter.into_iter()
198                    .map(|url| (url.clone(), Arc::new(url.into())))
199                    .collect(),
200            )),
201        }
202    }
203}
204
205impl fmt::Display for RelayMap {
206    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207        fmt::Debug::fmt(&self, f)
208    }
209}
210
211/// Information on a specific relay server.
212///
213/// Includes the Url where it can be dialed.
214// Please note that this is documented in the `iroh.computer` repository under
215// `src/app/docs/reference/config/page.mdx`.  Any changes to this need to be updated there.
216#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
217pub struct RelayConfig {
218    /// The [`RelayUrl`] where this relay server can be dialed.
219    pub url: RelayUrl,
220    /// Configuration to speak to the QUIC endpoint on the relay server.
221    ///
222    /// When `None`, we will not attempt to do QUIC address discovery
223    /// with this relay server.
224    #[serde(default = "quic_config")]
225    pub quic: Option<RelayQuicConfig>,
226}
227
228impl From<RelayUrl> for RelayConfig {
229    fn from(value: RelayUrl) -> Self {
230        Self {
231            url: value,
232            quic: quic_config(),
233        }
234    }
235}
236
237fn quic_config() -> Option<RelayQuicConfig> {
238    Some(RelayQuicConfig::default())
239}
240
241/// Configuration for speaking to the QUIC endpoint on the relay
242/// server to do QUIC address discovery.
243///
244/// Defaults to using [`DEFAULT_RELAY_QUIC_PORT`].
245#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
246pub struct RelayQuicConfig {
247    /// The port on which the connection should be bound to.
248    pub port: u16,
249}
250
251impl Default for RelayQuicConfig {
252    fn default() -> Self {
253        Self {
254            port: DEFAULT_RELAY_QUIC_PORT,
255        }
256    }
257}
258
259impl fmt::Display for RelayConfig {
260    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
261        write!(f, "{}", self.url)
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use std::str::FromStr;
268
269    use super::*;
270
271    #[test]
272    fn relay_map_extend() {
273        let urls1 = vec![
274            RelayUrl::from_str("https://hello-a-01.com").unwrap(),
275            RelayUrl::from_str("https://hello-b-01.com").unwrap(),
276            RelayUrl::from_str("https://hello-c-01-.com").unwrap(),
277        ];
278
279        let urls2 = vec![
280            RelayUrl::from_str("https://hello-a-02.com").unwrap(),
281            RelayUrl::from_str("https://hello-b-02.com").unwrap(),
282            RelayUrl::from_str("https://hello-c-02-.com").unwrap(),
283        ];
284
285        let map1 = RelayMap::from_iter(urls1.clone().into_iter().map(RelayConfig::from));
286        let map2 = RelayMap::from_iter(urls2.clone().into_iter().map(RelayConfig::from));
287
288        assert_ne!(map1, map2);
289
290        // combine
291
292        let map3 = RelayMap::from_iter(
293            map1.relays::<Vec<_>>()
294                .into_iter()
295                .chain(map2.relays::<Vec<_>>()),
296        );
297
298        assert_eq!(map3.len(), 6);
299
300        map1.extend(&map2);
301        assert_eq!(map3, map1);
302    }
303}