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;
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    /// Returns the URLs of all servers in this relay map.
54    ///
55    /// This function is generic over the container to collect into. If you simply want a list
56    /// of URLs, call this with `map.urls::<Vec<_>>()` to get a `Vec<RelayUrl>`.
57    pub fn urls<T>(&self) -> T
58    where
59        T: FromIterator<RelayUrl>,
60    {
61        self.relays
62            .read()
63            .expect("poisoned")
64            .keys()
65            .cloned()
66            .collect::<T>()
67    }
68
69    /// Returns a list with the [`RelayConfig`] for each relay in this relay map.
70    ///
71    /// This function is generic over the container to collect into. If you simply want a list
72    /// of URLs, call this with `map.relays::<Vec<_>>()` to get a `Vec<RelayConfig>`.
73    pub fn relays<T>(&self) -> T
74    where
75        T: FromIterator<Arc<RelayConfig>>,
76    {
77        self.relays
78            .read()
79            .expect("poisoned")
80            .values()
81            .cloned()
82            .collect::<T>()
83    }
84
85    /// Returns `true` if a relay with `url` is contained in this this relay map.
86    pub fn contains(&self, url: &RelayUrl) -> bool {
87        self.relays.read().expect("poisoned").contains_key(url)
88    }
89
90    /// Returns the config for a relay.
91    pub fn get(&self, url: &RelayUrl) -> Option<Arc<RelayConfig>> {
92        self.relays.read().expect("poisoned").get(url).cloned()
93    }
94
95    /// Returns the number of relays in this relay map.
96    pub fn len(&self) -> usize {
97        self.relays.read().expect("poisoned").len()
98    }
99
100    /// Returns `true` if this relay map is empty.
101    pub fn is_empty(&self) -> bool {
102        self.relays.read().expect("poisoned").is_empty()
103    }
104
105    /// Inserts a new relay into the relay map.
106    pub fn insert(&self, url: RelayUrl, endpoint: Arc<RelayConfig>) -> Option<Arc<RelayConfig>> {
107        self.relays.write().expect("poisoned").insert(url, endpoint)
108    }
109
110    /// Removes an existing relay by its URL.
111    pub fn remove(&self, url: &RelayUrl) -> Option<Arc<RelayConfig>> {
112        self.relays.write().expect("poisoned").remove(url)
113    }
114
115    /// Extends this `RelayMap` with another one.
116    pub fn extend(&self, other: &RelayMap) {
117        let mut a = self.relays.write().expect("poisoned");
118        let b = other.relays.read().expect("poisoned");
119        a.extend(b.iter().map(|(a, b)| (a.clone(), b.clone())));
120    }
121}
122
123impl FromIterator<RelayConfig> for RelayMap {
124    fn from_iter<T: IntoIterator<Item = RelayConfig>>(iter: T) -> Self {
125        Self::from_iter(iter.into_iter().map(Arc::new))
126    }
127}
128
129impl FromIterator<Arc<RelayConfig>> for RelayMap {
130    fn from_iter<T: IntoIterator<Item = Arc<RelayConfig>>>(iter: T) -> Self {
131        Self {
132            relays: Arc::new(RwLock::new(
133                iter.into_iter()
134                    .map(|config| (config.url.clone(), config))
135                    .collect(),
136            )),
137        }
138    }
139}
140
141impl From<RelayUrl> for RelayMap {
142    /// Creates a [`RelayMap`] from a [`RelayUrl`].
143    ///
144    /// The [`RelayConfig`]s in the [`RelayMap`] will have the default QUIC address
145    /// discovery ports.
146    fn from(value: RelayUrl) -> Self {
147        Self {
148            relays: Arc::new(RwLock::new(
149                [(value.clone(), Arc::new(value.into()))].into(),
150            )),
151        }
152    }
153}
154
155impl From<RelayConfig> for RelayMap {
156    fn from(value: RelayConfig) -> Self {
157        Self {
158            relays: Arc::new(RwLock::new([(value.url.clone(), Arc::new(value))].into())),
159        }
160    }
161}
162
163impl FromIterator<RelayUrl> for RelayMap {
164    /// Creates a [`RelayMap`] from an iterator of [`RelayUrl`].
165    ///
166    /// The [`RelayConfig`]s in the [`RelayMap`] will have the default QUIC address
167    /// discovery ports.
168    fn from_iter<T: IntoIterator<Item = RelayUrl>>(iter: T) -> Self {
169        Self {
170            relays: Arc::new(RwLock::new(
171                iter.into_iter()
172                    .map(|url| (url.clone(), Arc::new(url.into())))
173                    .collect(),
174            )),
175        }
176    }
177}
178
179impl fmt::Display for RelayMap {
180    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181        fmt::Debug::fmt(&self, f)
182    }
183}
184
185/// Information on a specific relay server.
186///
187/// Includes the Url where it can be dialed.
188// Please note that this is documented in the `iroh.computer` repository under
189// `src/app/docs/reference/config/page.mdx`.  Any changes to this need to be updated there.
190#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, PartialOrd, Ord)]
191pub struct RelayConfig {
192    /// The [`RelayUrl`] where this relay server can be dialed.
193    pub url: RelayUrl,
194    /// Configuration to speak to the QUIC endpoint on the relay server.
195    ///
196    /// When `None`, we will not attempt to do QUIC address discovery
197    /// with this relay server.
198    #[serde(default = "quic_config")]
199    pub quic: Option<RelayQuicConfig>,
200}
201
202impl From<RelayUrl> for RelayConfig {
203    fn from(value: RelayUrl) -> Self {
204        Self {
205            url: value,
206            quic: quic_config(),
207        }
208    }
209}
210
211fn quic_config() -> Option<RelayQuicConfig> {
212    Some(RelayQuicConfig::default())
213}
214
215/// Configuration for speaking to the QUIC endpoint on the relay
216/// server to do QUIC address discovery.
217#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq, PartialOrd, Ord)]
218pub struct RelayQuicConfig {
219    /// The port on which the connection should be bound to.
220    pub port: u16,
221}
222
223impl Default for RelayQuicConfig {
224    fn default() -> Self {
225        Self {
226            port: DEFAULT_RELAY_QUIC_PORT,
227        }
228    }
229}
230
231impl fmt::Display for RelayConfig {
232    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
233        write!(f, "{}", self.url)
234    }
235}
236
237#[cfg(test)]
238mod tests {
239    use std::str::FromStr;
240
241    use super::*;
242
243    #[test]
244    fn relay_map_extend() {
245        let urls1 = vec![
246            RelayUrl::from_str("https://hello-a-01.com").unwrap(),
247            RelayUrl::from_str("https://hello-b-01.com").unwrap(),
248            RelayUrl::from_str("https://hello-c-01-.com").unwrap(),
249        ];
250
251        let urls2 = vec![
252            RelayUrl::from_str("https://hello-a-02.com").unwrap(),
253            RelayUrl::from_str("https://hello-b-02.com").unwrap(),
254            RelayUrl::from_str("https://hello-c-02-.com").unwrap(),
255        ];
256
257        let map1 = RelayMap::from_iter(urls1.clone().into_iter().map(RelayConfig::from));
258        let map2 = RelayMap::from_iter(urls2.clone().into_iter().map(RelayConfig::from));
259
260        assert_ne!(map1, map2);
261
262        // combine
263
264        let map3 = RelayMap::from_iter(
265            map1.relays::<Vec<_>>()
266                .into_iter()
267                .chain(map2.relays::<Vec<_>>()),
268        );
269
270        assert_eq!(map3.len(), 6);
271
272        map1.extend(&map2);
273        assert_eq!(map3, map1);
274    }
275}