snowstorm/config/
mod.rs

1use crate::identity::Role;
2pub use geohash::Coord;
3use geohash::GeohashError;
4pub use libp2p::identity::{Keypair, PublicKey};
5use libp2p::multiaddr::Protocol;
6pub use libp2p::Multiaddr;
7use libp2p::PeerId;
8use std::sync::Arc;
9use std::{collections::HashSet, io::Error, net::Ipv4Addr};
10
11pub mod serializable;
12use crate::net::ProtectSocketFn;
13use serializable::SerializableConfig;
14
15#[derive(Debug, Clone)]
16pub struct AddrInfo {
17    pub peer_id: PeerId,
18    pub multiaddrs: Vec<Multiaddr>,
19}
20
21#[derive(Debug, Default, Clone)]
22pub struct Hop {
23    pub addr_info: Vec<AddrInfo>,
24    pub policy: String,
25    pub user: String,
26}
27
28#[derive(Debug, Clone)]
29pub struct RouteDescriptor {
30    pub id: String,
31    pub name: String,
32    pub path: Vec<Hop>,
33    pub ttl: i32,
34    pub reference: Option<u32>,
35}
36
37#[derive(Debug, Clone)]
38pub struct Config {
39    /// Peer privacy level
40    ///
41    /// The lower the level, the more privacy is granted
42    ///
43    /// This parameter affects geohash precision and internal behaviour
44    ///
45    /// From 0 to 12
46    /// If 0 is provided no location will be set
47    pub privacy_level: isize,
48
49    /// Peer geohashed location
50    pub location: Option<String>,
51
52    /// Peer behaviour roles
53    pub roles: HashSet<Role>,
54
55    /// First HOP peer multiaddresses
56    pub entry_peers: Vec<Multiaddr>,
57
58    /// Peer identity keypair
59    pub keypair: Keypair,
60
61    // List of multiaddresses to listen on
62    pub listen_on: Vec<Multiaddr>,
63
64    /// Discovery namespace
65    pub namespace: String,
66
67    /// Discovery TTL in seconds
68    pub discovery_ttl: Option<u64>,
69
70    /// Static Exits
71    /// deprecated
72    pub static_exits: Vec<Multiaddr>,
73
74    /// Static routes
75    pub static_routes: Vec<RouteDescriptor>,
76
77    /// Socks Listen Address
78    pub sserver_listen_addr: String,
79
80    /// Socks Client Listen Address
81    pub sclient_listen_addr: String,
82
83    /// modify fdlimit
84    pub fdlimit: bool,
85
86    /// External addresses
87    pub external_addrs: Vec<Multiaddr>,
88
89    /// Outbound addresses
90    pub outbound_addrs: Vec<Multiaddr>,
91
92    /// Fly.io mode.
93    pub is_fly_io: bool,
94
95    // Optional func to protect sockets
96    pub protect_socket_fn: Option<ProtectSocketFn>,
97
98    // A Yukigo token authenticating the user.
99    pub token: Option<String>,
100
101    // Device uuid
102    pub device_id: Option<String>,
103
104    // Auth checks for a given route id
105    // exit/volunteer mode only
106    pub route_auth_enabled: bool,
107}
108
109impl Default for Config {
110    fn default() -> Self {
111        let address_tcp = Multiaddr::from(Ipv4Addr::UNSPECIFIED).with(Protocol::Tcp(0));
112
113        let address_quic = Multiaddr::from(Ipv4Addr::UNSPECIFIED)
114            .with(Protocol::Udp(0))
115            .with(Protocol::QuicV1);
116
117        let address_webrtc = Multiaddr::from(Ipv4Addr::UNSPECIFIED)
118            .with(Protocol::Udp(0))
119            .with(Protocol::WebRTCDirect);
120
121        Config {
122            privacy_level: 0,
123            location: None,
124            roles: HashSet::new(),
125            keypair: Keypair::generate_ed25519(),
126            entry_peers: Vec::new(),
127            listen_on: vec![address_tcp, address_quic, address_webrtc],
128            namespace: "snowstorm".to_string(),
129            discovery_ttl: Some(60 * 60 * 12),
130            static_exits: Vec::new(),
131            static_routes: Vec::new(),
132            // Mac and iOS network extension is not happy with listening on 0.0.0.0
133            sserver_listen_addr: if cfg!(any(target_os = "macos", target_os = "ios")) { "127.0.0.1:0".to_string() } else  { "0.0.0.0:0".to_string() } ,
134            sclient_listen_addr: "127.0.0.1:0".to_string(),
135            fdlimit: true,
136            external_addrs: Vec::new(),
137            outbound_addrs: Vec::new(),
138            is_fly_io: false,
139            protect_socket_fn: None,
140            token: None,
141            device_id: None,
142            route_auth_enabled: false,
143        }
144    }
145}
146
147impl Config {
148    fn apply_privacy_level(&mut self) {
149        if self.privacy_level <= 0 {
150            self.location = None;
151            self.privacy_level = 0;
152            return;
153        }
154
155        if let Some(location) = &self.location {
156            self.location = Some(location[..self.privacy_level as usize].to_string());
157        }
158    }
159
160    pub fn has_entry(&self, id: &PeerId) -> bool {
161        self.entry_peers.iter().any(|multiaddr| {
162            multiaddr
163                .iter()
164                .any(|proto| matches!(proto, Protocol::P2p(peer_id) if peer_id == *id))
165        })
166    }
167
168    pub fn has_static_exit(&self, id: &PeerId) -> bool {
169        let has_exit = self.static_exits.iter().any(|multiaddr| {
170            multiaddr
171                .iter()
172                .any(|proto| matches!(proto, Protocol::P2p(peer_id) if peer_id == *id))
173        });
174
175        if has_exit {
176            return true;
177        }
178
179        let has_route = self.static_routes.iter().any(|route| {
180            route.path.iter().any(|hop| {
181                hop.addr_info
182                    .iter()
183                    .any(|addr_info| addr_info.peer_id == *id)
184            })
185        });
186
187        return has_route;
188    }
189
190    pub fn with_role(&mut self, role: Role) {
191        // TODO: maybe apply privacy level if mode is only client?
192        self.roles.insert(role);
193    }
194
195    pub fn with_privacy_level(mut self, privacy_level: isize) -> Result<Self, Error> {
196        if privacy_level > 12 {
197            return Err(Error::new(
198                std::io::ErrorKind::InvalidInput,
199                "Privacy level must be between 0 and 12",
200            ));
201        }
202
203        self.privacy_level = privacy_level;
204        self.apply_privacy_level();
205
206        Ok(self)
207    }
208
209    pub fn with_location(mut self, location: String) -> Self {
210        self.location = Some(location);
211        self.apply_privacy_level();
212        self
213    }
214
215    pub fn with_coordinates(mut self, coord: geohash::Coord) -> Result<Self, GeohashError> {
216        self.location = Some(geohash::encode(coord, self.privacy_level as usize)?);
217        Ok(self)
218    }
219
220    pub fn with_first_hops(mut self, first_hops: Vec<Multiaddr>) -> Self {
221        self.entry_peers = first_hops;
222        self
223    }
224
225    pub fn with_first_hop(mut self, first_hop: Multiaddr) -> Self {
226        self.entry_peers.push(first_hop);
227        self
228    }
229
230    pub fn with_roles(mut self, roles: HashSet<Role>) -> Self {
231        for role in roles {
232            self.with_role(role);
233        }
234
235        self
236    }
237
238    pub fn with_keypair(mut self, keypair: Keypair) -> Self {
239        self.keypair = keypair;
240        self
241    }
242
243    pub fn with_sserver_listen_addr(mut self, listen_addr: String) -> Self {
244        self.sserver_listen_addr = listen_addr;
245        self
246    }
247
248    pub fn with_sclient_listen_addr(mut self, listen_addr: String) -> Self {
249        self.sclient_listen_addr = listen_addr;
250        self
251    }
252
253    pub fn with_external_addrs(mut self, external_addrs: Vec<Multiaddr>) -> Self {
254        self.external_addrs = external_addrs;
255        self
256    }
257
258    pub fn with_outbound_addrs(mut self, outbound_addrs: Vec<Multiaddr>) -> Self {
259        self.outbound_addrs = outbound_addrs;
260        if self.outbound_addrs.len() == 0 {
261            return self;
262        }
263
264        // Fixup listen_on, because libp2p is stupid and only allows one
265        // QUIC listener per AF, so the default `INADDR_ANY:0` listener
266        // will take precidence, and it happens to be the address that
267        // gets used for outgoing connections.
268        let mut ln_addrs: Vec<Multiaddr> = Vec::new();
269        for out_addr in &self.outbound_addrs {
270            let is_ip4 = matches!(out_addr.iter().next(), Some(Protocol::Ip4(_)));
271
272            let tcp_addr = out_addr.clone().with(Protocol::Tcp(0));
273            let quic_addr = out_addr
274                .clone()
275                .with(Protocol::Udp(0))
276                .with(Protocol::QuicV1);
277            let webrtc_addr = out_addr
278                .clone()
279                .with(Protocol::Udp(0))
280                .with(Protocol::WebRTCDirect);
281            ln_addrs.push(tcp_addr);
282            ln_addrs.push(quic_addr);
283            if is_ip4 {
284                // @timur: webrtc-direct dialing wasn't working for me with an existing ipv6 listener
285                // we should probably look into why. In the meanwhile - just leaving ipv4 here.
286                ln_addrs.push(webrtc_addr);
287            }
288        }
289
290        self.listen_on = ln_addrs;
291        self
292    }
293
294    pub fn with_fly_io(mut self, is_fly_io: bool) -> Self {
295        self.is_fly_io = is_fly_io;
296        self
297    }
298
299    pub fn with_protect_socket_fn(
300        mut self,
301        protect_socket_fn: Arc<dyn Fn(i32) -> () + Send + Sync>,
302    ) -> Self {
303        self.protect_socket_fn = Some(ProtectSocketFn(protect_socket_fn));
304        self
305    }
306
307    pub fn with_token(mut self, token: String) -> Self {
308        self.token = Some(token);
309        self
310    }
311
312    pub fn with_route_auth_enabled(mut self, route_auth_enabled: bool) -> Self {
313        self.route_auth_enabled = route_auth_enabled;
314        self
315    }
316
317    pub fn seal(self) -> Result<Vec<u8>, Error> {
318        let config = SerializableConfig::from(self);
319        let data =
320            serde_cbor::to_vec(&config).map_err(|e| Error::new(std::io::ErrorKind::Other, e))?;
321
322        // TODO: encrypt data using passphrase
323
324        Ok(data)
325    }
326
327    pub fn unseal(data: Vec<u8>) -> Result<Self, Error> {
328        // TODO: decrypt data using passphrase
329
330        let sc: SerializableConfig =
331            serde_cbor::from_slice(&data).map_err(|e| Error::new(std::io::ErrorKind::Other, e))?;
332
333        Ok(Config::from(sc))
334    }
335}