Skip to main content

innernet_shared/
types.rs

1use crate::{wg::PeerInfoExt, DEFAULT_HOSTS_PATH};
2use anyhow::{anyhow, Error};
3use clap::{
4    builder::{PossibleValuesParser, TypedValueParser},
5    Args,
6};
7use ipnet::{IpNet, PrefixLenError};
8use once_cell::sync::Lazy;
9use regex::Regex;
10use serde::{Deserialize, Serialize};
11use std::{
12    fmt::{self, Display, Formatter},
13    io,
14    net::{IpAddr, SocketAddr, ToSocketAddrs},
15    ops::{Deref, DerefMut},
16    path::{Path, PathBuf},
17    str::FromStr,
18    time::{Duration, SystemTime},
19    vec,
20};
21use url::Host;
22use wireguard_control::{
23    AllowedIp, Backend, InterfaceName, InvalidInterfaceName, Key, PeerConfig, PeerConfigBuilder,
24    PeerInfo,
25};
26
27#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct Interface {
29    name: InterfaceName,
30}
31
32impl FromStr for Interface {
33    type Err = InvalidInterfaceName;
34
35    fn from_str(name: &str) -> Result<Self, Self::Err> {
36        if !Hostname::is_valid(name) {
37            Err(InvalidInterfaceName::InvalidChars)
38        } else {
39            Ok(Self {
40                name: name.parse()?,
41            })
42        }
43    }
44}
45
46impl Deref for Interface {
47    type Target = InterfaceName;
48
49    fn deref(&self) -> &Self::Target {
50        &self.name
51    }
52}
53
54impl Display for Interface {
55    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
56        f.write_str(&self.name.to_string())
57    }
58}
59
60#[derive(Clone, Debug, PartialEq, Eq)]
61/// An external endpoint that supports both IP and domain name hosts.
62pub struct Endpoint {
63    host: Host,
64    port: u16,
65}
66
67impl From<SocketAddr> for Endpoint {
68    fn from(addr: SocketAddr) -> Self {
69        match addr {
70            SocketAddr::V4(v4addr) => Self {
71                host: Host::Ipv4(*v4addr.ip()),
72                port: v4addr.port(),
73            },
74            SocketAddr::V6(v6addr) => Self {
75                host: Host::Ipv6(*v6addr.ip()),
76                port: v6addr.port(),
77            },
78        }
79    }
80}
81
82impl FromStr for Endpoint {
83    type Err = &'static str;
84
85    fn from_str(s: &str) -> Result<Self, Self::Err> {
86        match s.rsplitn(2, ':').collect::<Vec<&str>>().as_slice() {
87            [port, host] => {
88                let port = port.parse().map_err(|_| "couldn't parse port")?;
89                let host = Host::parse(host).map_err(|_| "couldn't parse host")?;
90                Ok(Endpoint { host, port })
91            },
92            _ => Err("couldn't parse in form of 'host:port'"),
93        }
94    }
95}
96
97impl Serialize for Endpoint {
98    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
99    where
100        S: serde::Serializer,
101    {
102        serializer.serialize_str(&self.to_string())
103    }
104}
105
106impl<'de> Deserialize<'de> for Endpoint {
107    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
108    where
109        D: serde::Deserializer<'de>,
110    {
111        struct EndpointVisitor;
112        impl serde::de::Visitor<'_> for EndpointVisitor {
113            type Value = Endpoint;
114
115            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
116                formatter.write_str("a valid host:port endpoint")
117            }
118
119            fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
120            where
121                E: serde::de::Error,
122            {
123                s.parse().map_err(serde::de::Error::custom)
124            }
125        }
126        deserializer.deserialize_str(EndpointVisitor)
127    }
128}
129
130impl Display for Endpoint {
131    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
132        self.host.fmt(f)?;
133        f.write_str(":")?;
134        self.port.fmt(f)
135    }
136}
137
138impl Endpoint {
139    pub fn resolve(&self) -> Result<SocketAddr, io::Error> {
140        let mut addrs = self.to_string().to_socket_addrs()?;
141        addrs.next().ok_or_else(|| {
142            io::Error::new(
143                io::ErrorKind::AddrNotAvailable,
144                "failed to resolve address".to_string(),
145            )
146        })
147    }
148
149    /// Returns true if the endpoint host is unspecified e.g. 0.0.0.0
150    pub fn is_host_unspecified(&self) -> bool {
151        match self.host {
152            Host::Ipv4(ip) => ip.is_unspecified(),
153            Host::Ipv6(ip) => ip.is_unspecified(),
154            Host::Domain(_) => false,
155        }
156    }
157
158    pub fn port(&self) -> u16 {
159        self.port
160    }
161}
162
163#[derive(Deserialize, Serialize, Debug)]
164#[serde(tag = "option", content = "content")]
165pub enum EndpointContents {
166    Set(Endpoint),
167    Unset,
168}
169
170impl From<EndpointContents> for Option<Endpoint> {
171    fn from(endpoint: EndpointContents) -> Self {
172        match endpoint {
173            EndpointContents::Set(addr) => Some(addr),
174            EndpointContents::Unset => None,
175        }
176    }
177}
178
179impl From<Option<Endpoint>> for EndpointContents {
180    fn from(option: Option<Endpoint>) -> Self {
181        match option {
182            Some(addr) => Self::Set(addr),
183            None => Self::Unset,
184        }
185    }
186}
187
188#[derive(Deserialize, Serialize, Debug)]
189pub struct AssociationContents {
190    pub cidr_id_1: i64,
191    pub cidr_id_2: i64,
192}
193
194#[derive(Deserialize, Serialize, Debug)]
195pub struct Association {
196    pub id: i64,
197
198    #[serde(flatten)]
199    pub contents: AssociationContents,
200}
201
202impl Deref for Association {
203    type Target = AssociationContents;
204
205    fn deref(&self) -> &Self::Target {
206        &self.contents
207    }
208}
209
210#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord)]
211pub struct CidrContents {
212    pub name: String,
213    pub cidr: IpNet,
214    pub parent: Option<i64>,
215}
216
217impl Deref for CidrContents {
218    type Target = IpNet;
219
220    fn deref(&self) -> &Self::Target {
221        &self.cidr
222    }
223}
224
225#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, PartialOrd, Eq, Ord)]
226pub struct Cidr {
227    pub id: i64,
228
229    #[serde(flatten)]
230    pub contents: CidrContents,
231}
232
233impl Display for Cidr {
234    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
235        write!(f, "{} ({})", self.name, self.cidr)
236    }
237}
238
239impl Deref for Cidr {
240    type Target = CidrContents;
241
242    fn deref(&self) -> &Self::Target {
243        &self.contents
244    }
245}
246
247#[derive(Clone, PartialEq, PartialOrd, Eq, Ord)]
248pub struct CidrTree<'a> {
249    cidrs: &'a [Cidr],
250    contents: &'a Cidr,
251}
252
253impl std::ops::Deref for CidrTree<'_> {
254    type Target = Cidr;
255
256    fn deref(&self) -> &Self::Target {
257        self.contents
258    }
259}
260
261impl<'a> CidrTree<'a> {
262    pub fn new(cidrs: &'a [Cidr]) -> Self {
263        let root = cidrs
264            .iter()
265            .min_by_key(|c| c.cidr.prefix_len())
266            .expect("failed to find root CIDR");
267        Self::with_root(cidrs, root)
268    }
269
270    pub fn with_root(cidrs: &'a [Cidr], root: &'a Cidr) -> Self {
271        Self {
272            cidrs,
273            contents: root,
274        }
275    }
276
277    pub fn children(&self) -> impl Iterator<Item = CidrTree<'_>> {
278        self.cidrs
279            .iter()
280            .filter(move |c| c.parent == Some(self.contents.id))
281            .map(move |c| CidrTree {
282                cidrs: self.cidrs,
283                contents: c,
284            })
285    }
286
287    pub fn leaves(&self) -> Vec<Cidr> {
288        if !self.cidrs.iter().any(|cidr| cidr.parent == Some(self.id)) {
289            vec![self.contents.clone()]
290        } else {
291            self.children().flat_map(|child| child.leaves()).collect()
292        }
293    }
294
295    pub fn ip_net_for(&self, ip: IpAddr) -> Result<IpNet, PrefixLenError> {
296        IpNet::new(ip, self.contents.prefix_len())
297    }
298}
299
300#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
301pub struct RedeemContents {
302    pub public_key: String,
303}
304
305#[derive(Debug, Clone, PartialEq, Eq, Args)]
306pub struct InstallOpts {
307    /// Set a specific interface name
308    #[clap(long, conflicts_with = "default_name")]
309    pub name: Option<String>,
310
311    /// Use the network name inside the invitation as the interface name
312    #[clap(long = "default-name")]
313    pub default_name: bool,
314
315    /// Delete the invitation after a successful install
316    #[clap(short, long)]
317    pub delete_invite: bool,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq, Args)]
321pub struct AddPeerOpts {
322    /// Name of new peer
323    #[clap(long)]
324    pub name: Option<Hostname>,
325
326    /// Specify desired IP of new peer (within parent CIDR)
327    #[clap(long, conflicts_with = "auto_ip")]
328    pub ip: Option<IpAddr>,
329
330    /// Auto-assign the peer the first available IP within the CIDR
331    #[clap(long = "auto-ip")]
332    pub auto_ip: bool,
333
334    /// Name of CIDR to add new peer under
335    #[clap(long)]
336    pub cidr: Option<String>,
337
338    /// Make new peer an admin?
339    #[clap(long)]
340    pub admin: Option<bool>,
341
342    /// Bypass confirmation
343    #[clap(long)]
344    pub yes: bool,
345
346    /// Save the config to the given location
347    #[clap(long)]
348    pub save_config: Option<String>,
349
350    /// Invite expiration period (eg. '30d', '7w', '2h', '60m', '1000s')
351    #[clap(long)]
352    pub invite_expires: Option<Timestring>,
353}
354
355#[derive(Debug, Clone, PartialEq, Eq, Args)]
356pub struct RenamePeerOpts {
357    /// Name of peer to rename
358    #[clap(long)]
359    pub name: Option<Hostname>,
360
361    /// The new name of the peer
362    #[clap(long)]
363    pub new_name: Option<Hostname>,
364
365    /// Bypass confirmation
366    #[clap(long)]
367    pub yes: bool,
368}
369
370#[derive(Debug, Clone, PartialEq, Eq, Args)]
371pub struct EnableDisablePeerOpts {
372    /// Name of peer to enable/disable
373    #[clap(long)]
374    pub name: Option<Hostname>,
375
376    /// Bypass confirmation
377    #[clap(long, requires("name"))]
378    pub yes: bool,
379}
380
381#[derive(Debug, Clone, PartialEq, Eq, Args)]
382pub struct AddCidrOpts {
383    /// The CIDR name (eg. 'engineers')
384    #[clap(long)]
385    pub name: Option<Hostname>,
386
387    /// The CIDR network (eg. '10.42.5.0/24')
388    #[clap(long)]
389    pub cidr: Option<IpNet>,
390
391    /// The CIDR parent name
392    #[clap(long)]
393    pub parent: Option<String>,
394
395    /// Bypass confirmation
396    #[clap(long)]
397    pub yes: bool,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq, Args)]
401pub struct RenameCidrOpts {
402    /// Name of CIDR to rename
403    #[clap(long)]
404    pub name: Option<String>,
405
406    /// The new name of the CIDR
407    #[clap(long)]
408    pub new_name: Option<String>,
409
410    /// Bypass confirmation
411    #[clap(long)]
412    pub yes: bool,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Args)]
416pub struct DeleteCidrOpts {
417    /// The CIDR name (eg. 'engineers')
418    #[clap(long)]
419    pub name: Option<String>,
420
421    /// Bypass confirmation
422    #[clap(long)]
423    pub yes: bool,
424}
425
426#[derive(Debug, Clone, PartialEq, Eq, Args)]
427pub struct AddDeleteAssociationOpts {
428    /// The first cidr to associate
429    pub cidr1: Option<String>,
430
431    /// The second cidr to associate
432    pub cidr2: Option<String>,
433
434    /// Bypass confirmation
435    #[clap(long)]
436    pub yes: bool,
437}
438
439#[derive(Debug, Clone, PartialEq, Eq, Args)]
440pub struct ListenPortOpts {
441    /// The listen port you'd like to set for the interface
442    #[clap(short, long)]
443    pub listen_port: Option<u16>,
444
445    /// Unset the local listen port to use a randomized port
446    #[clap(short, long, conflicts_with = "listen_port")]
447    pub unset: bool,
448
449    /// Bypass confirmation
450    #[clap(long)]
451    pub yes: bool,
452}
453
454#[derive(Debug, Clone, PartialEq, Eq, Args)]
455pub struct OverrideEndpointOpts {
456    /// The external endpoint that you'd like the innernet server to broadcast to other peers. The
457    /// IP address may be unspecified (all zeros), in which case the server will try to resolve it
458    /// based on its most recent connection. The port will still be used, even if you decide to use
459    /// an unspecified IP address.
460    #[clap(short, long)]
461    pub endpoint: Option<Endpoint>,
462
463    /// Unset an existing override to use the automatic endpoint discovery
464    #[clap(short, long, conflicts_with = "endpoint")]
465    pub unset: bool,
466
467    /// Bypass confirmation
468    #[clap(long)]
469    pub yes: bool,
470}
471
472#[derive(Debug, Clone, PartialEq, Eq, Args)]
473pub struct OverridePeerEndpointOpts {
474    /// Name of peer whose endpoint you want to override
475    #[clap(long)]
476    pub name: Option<Hostname>,
477
478    /// The external endpoint that you'd like to use for a given peer
479    #[clap(short, long)]
480    pub endpoint: Option<Endpoint>,
481
482    /// Unset an existing local endpoint override for this peer
483    #[clap(short, long, conflicts_with = "endpoint")]
484    pub unset: bool,
485
486    /// Bypass confirmation
487    #[clap(long)]
488    pub yes: bool,
489}
490
491#[derive(Debug, Clone, Args)]
492pub struct NatOpts {
493    #[clap(long)]
494    /// Don't attempt NAT traversal. Note that this still will report candidates
495    /// unless you also specify to exclude all NAT candidates.
496    pub no_nat_traversal: bool,
497
498    #[clap(long)]
499    /// Exclude one or more CIDRs from NAT candidate reporting.
500    /// ex. --exclude-nat-candidates '0.0.0.0/0' would report no candidates.
501    pub exclude_nat_candidates: Vec<IpNet>,
502
503    #[clap(long, conflicts_with = "exclude_nat_candidates")]
504    /// Don't report any candidates to coordinating server.
505    /// Shorthand for --exclude-nat-candidates '0.0.0.0/0'.
506    pub no_nat_candidates: bool,
507}
508
509impl NatOpts {
510    pub fn all_disabled() -> Self {
511        Self {
512            no_nat_traversal: true,
513            exclude_nat_candidates: vec![],
514            no_nat_candidates: true,
515        }
516    }
517
518    /// Check if an IP is allowed to be reported as a candidate.
519    pub fn is_excluded(&self, ip: IpAddr) -> bool {
520        self.no_nat_candidates
521            || self
522                .exclude_nat_candidates
523                .iter()
524                .any(|network| network.contains(&ip))
525    }
526}
527
528#[derive(Debug, Clone, Copy, Args)]
529pub struct NetworkOpts {
530    #[clap(long)]
531    /// Whether the routing should be done by innernet or is done by an
532    /// external tool like e.g. babeld.
533    pub no_routing: bool,
534
535    #[clap(long, default_value_t, value_parser = PossibleValuesParser::new(Backend::variants()).map(|s| s.parse::<Backend>().unwrap()))]
536    /// Specify a WireGuard backend to use.
537    /// If not set, innernet will auto-select based on availability.
538    pub backend: Backend,
539
540    #[clap(long)]
541    /// Specify the desired MTU for your interface (default: 1280).
542    pub mtu: Option<u32>,
543}
544
545#[derive(Clone, Debug, Args)]
546pub struct HostsOpts {
547    /// The path to write hosts to
548    #[clap(long = "hosts-path", default_value = DEFAULT_HOSTS_PATH)]
549    pub hosts_path: PathBuf,
550
551    /// Don't write to any hosts files
552    #[clap(long = "no-write-hosts", conflicts_with = "hosts_path")]
553    pub no_write_hosts: bool,
554
555    /// Use a different suffix for hosts, than 'INTERFACE.wg' , ex.
556    /// --host-suffix 'evilnet' names peers: PEER.evilnet, and
557    /// --host-suffix '' gives peers no suffix, just: PEER
558    #[clap(long = "host-suffix")]
559    pub host_suffix: Option<String>,
560}
561
562#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
563pub struct PeerContents {
564    pub name: Hostname,
565    pub ip: IpAddr,
566    pub cidr_id: i64,
567    pub public_key: String,
568    pub endpoint: Option<Endpoint>,
569    pub persistent_keepalive_interval: Option<u16>,
570    pub is_admin: bool,
571    pub is_disabled: bool,
572    pub is_redeemed: bool,
573    pub invite_expires: Option<SystemTime>,
574    #[serde(default)]
575    pub candidates: Vec<Endpoint>,
576}
577
578#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
579pub struct Peer {
580    pub id: i64,
581
582    #[serde(flatten)]
583    pub contents: PeerContents,
584}
585
586impl AsRef<Peer> for Peer {
587    fn as_ref(&self) -> &Peer {
588        self
589    }
590}
591
592impl Deref for Peer {
593    type Target = PeerContents;
594
595    fn deref(&self) -> &Self::Target {
596        &self.contents
597    }
598}
599
600impl DerefMut for Peer {
601    fn deref_mut(&mut self) -> &mut Self::Target {
602        &mut self.contents
603    }
604}
605
606impl Display for Peer {
607    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
608        write!(f, "{} ({})", &self.name, &self.public_key)
609    }
610}
611
612#[derive(Debug, Clone, PartialEq, Eq)]
613pub enum PeerChange {
614    AllowedIPs {
615        old: Vec<AllowedIp>,
616        new: Vec<AllowedIp>,
617    },
618    PersistentKeepalive {
619        old: Option<u16>,
620        new: Option<u16>,
621    },
622    Endpoint {
623        old: Option<SocketAddr>,
624        new: Option<SocketAddr>,
625    },
626    NatTraverseReattempt,
627}
628
629impl Display for PeerChange {
630    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
631        match self {
632            Self::AllowedIPs { old, new } => write!(f, "Allowed IPs: {old:?} => {new:?}"),
633            Self::PersistentKeepalive { old, new } => write!(
634                f,
635                "Persistent Keepalive: {} => {}",
636                old.display_string(),
637                new.display_string()
638            ),
639            Self::Endpoint { old, new } => write!(
640                f,
641                "Endpoint: {} => {}",
642                old.display_string(),
643                new.display_string()
644            ),
645            Self::NatTraverseReattempt => write!(f, "NAT Traversal Reattempt"),
646        }
647    }
648}
649
650trait OptionExt {
651    fn display_string(&self) -> String;
652}
653
654impl<T: std::fmt::Debug> OptionExt for Option<T> {
655    fn display_string(&self) -> String {
656        match self {
657            Some(x) => {
658                format!("{x:?}")
659            },
660            None => "[none]".to_string(),
661        }
662    }
663}
664
665/// Encompasses the logic for comparing the peer configuration currently on the WireGuard interface
666/// to a (potentially) more current peer configuration from the innernet server.
667#[derive(Clone, Debug, PartialEq, Eq)]
668pub struct PeerDiff<'a> {
669    pub old: Option<&'a PeerConfig>,
670    pub new: Option<&'a Peer>,
671    builder: PeerConfigBuilder,
672    changes: Vec<PeerChange>,
673}
674
675impl<'a> PeerDiff<'a> {
676    pub fn new(
677        old_info: Option<&'a PeerInfo>,
678        new: Option<&'a Peer>,
679    ) -> Result<Option<Self>, Error> {
680        let old = old_info.map(|p| &p.config);
681        match (old_info, new) {
682            (Some(old), Some(new)) if old.config.public_key.to_base64() != new.public_key => Err(
683                anyhow!("old and new peer configs have different public keys"),
684            ),
685            (None, None) => Ok(None),
686            _ => Ok(
687                Self::peer_config_builder(old_info, new).map(|(builder, changes)| Self {
688                    old,
689                    new,
690                    builder,
691                    changes,
692                }),
693            ),
694        }
695    }
696
697    pub fn public_key(&self) -> &Key {
698        self.builder.public_key()
699    }
700
701    pub fn changes(&self) -> &[PeerChange] {
702        &self.changes
703    }
704
705    fn peer_config_builder(
706        old_info: Option<&PeerInfo>,
707        new: Option<&Peer>,
708    ) -> Option<(PeerConfigBuilder, Vec<PeerChange>)> {
709        let old = old_info.map(|p| &p.config);
710        let public_key = match (old, new) {
711            (Some(old), _) => old.public_key.clone(),
712            (_, Some(new)) => Key::from_base64(&new.public_key).unwrap(),
713            _ => return None,
714        };
715        let mut builder = PeerConfigBuilder::new(&public_key);
716        let mut changes = vec![];
717
718        // Remove peer from interface if they're deleted or disabled, and we can return early.
719        if new.is_none() || matches!(new, Some(new) if new.is_disabled) {
720            return Some((builder.remove(), changes));
721        }
722        // diff.new is now guaranteed to be a Some(_) variant.
723        let new = new.unwrap();
724
725        let new_allowed_ips = &[AllowedIp {
726            address: new.ip,
727            cidr: if new.ip.is_ipv4() { 32 } else { 128 },
728        }];
729        if old.is_none() || matches!(old, Some(old) if old.allowed_ips != new_allowed_ips) {
730            builder = builder
731                .replace_allowed_ips()
732                .add_allowed_ips(new_allowed_ips);
733            changes.push(PeerChange::AllowedIPs {
734                old: old.map(|o| o.allowed_ips.clone()).unwrap_or_default(),
735                new: new_allowed_ips.to_vec(),
736            });
737        }
738
739        if old.is_none()
740            || matches!(old, Some(old) if old.persistent_keepalive_interval != new.persistent_keepalive_interval)
741        {
742            builder = match new.persistent_keepalive_interval {
743                Some(interval) => builder.set_persistent_keepalive_interval(interval),
744                None => builder.unset_persistent_keepalive(),
745            };
746            changes.push(PeerChange::PersistentKeepalive {
747                old: old.and_then(|p| p.persistent_keepalive_interval),
748                new: new.persistent_keepalive_interval,
749            });
750        }
751
752        // We won't update the endpoint if there's already a stable connection.
753        if !old_info
754            .map(|info| info.is_recently_connected())
755            .unwrap_or_default()
756        {
757            let mut endpoint_changed = false;
758            let resolved = new.endpoint.as_ref().and_then(|e| e.resolve().ok());
759            if let Some(addr) = resolved {
760                if old.is_none() || matches!(old, Some(old) if old.endpoint != resolved) {
761                    builder = builder.set_endpoint(addr);
762                    changes.push(PeerChange::Endpoint {
763                        old: old.and_then(|p| p.endpoint),
764                        new: Some(addr),
765                    });
766                    endpoint_changed = true;
767                }
768            }
769            if !endpoint_changed && !new.candidates.is_empty() {
770                changes.push(PeerChange::NatTraverseReattempt)
771            }
772        }
773
774        if !changes.is_empty() {
775            Some((builder, changes))
776        } else {
777            None
778        }
779    }
780}
781
782impl From<&Peer> for PeerConfigBuilder {
783    fn from(peer: &Peer) -> Self {
784        PeerDiff::new(None, Some(peer))
785            .expect("No Err on explicitly set peer data")
786            .expect("None -> Some(peer) will always create a PeerDiff")
787            .into()
788    }
789}
790
791impl From<PeerDiff<'_>> for PeerConfigBuilder {
792    /// Turn a PeerDiff into a minimal set of instructions to update the WireGuard interface,
793    /// hopefully minimizing dropped packets and other interruptions.
794    fn from(diff: PeerDiff) -> Self {
795        diff.builder
796    }
797}
798
799/// This model is sent as a response to the /state endpoint, and is meant
800/// to include all the data a client needs to update its WireGuard interface.
801#[derive(Debug, Clone, Deserialize, Serialize)]
802pub struct State {
803    /// This list will be only the peers visible to the user requesting this
804    /// information, not including disabled peers or peers from other CIDRs
805    /// that the user's CIDR is not authorized to communicate with.
806    pub peers: Vec<Peer>,
807
808    /// At the moment, this is all CIDRs, regardless of whether the peer is
809    /// eligible to communicate with them or not.
810    pub cidrs: Vec<Cidr>,
811}
812
813/// This model is sent as a response to the /capabilities endpoint.
814#[derive(Debug, Default, Clone, Deserialize, Serialize)]
815pub struct ServerCapabilities {
816    #[serde(default)]
817    pub unspecified_ip_in_override_endpoint: bool,
818}
819
820#[derive(Clone, Debug, PartialEq, Eq)]
821pub struct Timestring {
822    timestring: String,
823    seconds: u64,
824}
825
826impl Display for Timestring {
827    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
828        f.write_str(&self.timestring)
829    }
830}
831
832impl FromStr for Timestring {
833    type Err = &'static str;
834
835    fn from_str(timestring: &str) -> Result<Self, Self::Err> {
836        if timestring.len() < 2 {
837            Err("timestring isn't long enough!")
838        } else {
839            let (n, suffix) = timestring.split_at(timestring.len() - 1);
840            let n: u64 = n.parse().map_err(|_| {
841                "invalid timestring (a number followed by a time unit character, eg. '15m')"
842            })?;
843            let multiplier = match suffix {
844                "s" => Ok(1),
845                "m" => Ok(60),
846                "h" => Ok(60 * 60),
847                "d" => Ok(60 * 60 * 24),
848                "w" => Ok(60 * 60 * 24 * 7),
849                _ => Err("invalid timestring suffix (must be one of 's', 'm', 'h', 'd', or 'w')"),
850            }?;
851
852            Ok(Self {
853                timestring: timestring.to_string(),
854                seconds: n * multiplier,
855            })
856        }
857    }
858}
859
860impl From<Timestring> for Duration {
861    fn from(timestring: Timestring) -> Self {
862        Duration::from_secs(timestring.seconds)
863    }
864}
865
866#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
867pub struct Hostname(String);
868
869/// Regex to match the requirements of hostname(7), needed to have peers also be reachable hostnames.
870/// Note that the full length also must be maximum 63 characters, which this regex does not check.
871static HOSTNAME_REGEX: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([a-z0-9]-?)*[a-z0-9]$").unwrap());
872
873impl Hostname {
874    pub fn is_valid(name: &str) -> bool {
875        name.len() < 64 && HOSTNAME_REGEX.is_match(name)
876    }
877}
878
879impl FromStr for Hostname {
880    type Err = &'static str;
881
882    fn from_str(name: &str) -> Result<Self, Self::Err> {
883        if Self::is_valid(name) {
884            Ok(Self(name.to_string()))
885        } else {
886            Err("invalid hostname string (only alphanumeric with dashes)")
887        }
888    }
889}
890
891impl Deref for Hostname {
892    type Target = str;
893
894    fn deref(&self) -> &Self::Target {
895        &self.0
896    }
897}
898
899impl Display for Hostname {
900    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
901        f.write_str(&self.0)
902    }
903}
904
905pub trait IoErrorContext<T> {
906    fn with_path<P: AsRef<Path>>(self, path: P) -> Result<T, WrappedIoError>;
907    fn with_str<S: Into<String>>(self, context: S) -> Result<T, WrappedIoError>;
908}
909
910impl<T> IoErrorContext<T> for Result<T, std::io::Error> {
911    fn with_path<P: AsRef<Path>>(self, path: P) -> Result<T, WrappedIoError> {
912        self.with_str(path.as_ref().to_string_lossy())
913    }
914
915    fn with_str<S: Into<String>>(self, context: S) -> Result<T, WrappedIoError> {
916        self.map_err(|e| WrappedIoError {
917            io_error: e,
918            context: context.into(),
919        })
920    }
921}
922
923#[derive(Debug)]
924pub struct WrappedIoError {
925    io_error: std::io::Error,
926    context: String,
927}
928
929impl Display for WrappedIoError {
930    fn fmt(&self, f: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
931        write!(f, "{} - {}", self.context, self.io_error)
932    }
933}
934
935impl Deref for WrappedIoError {
936    type Target = std::io::Error;
937
938    fn deref(&self) -> &Self::Target {
939        &self.io_error
940    }
941}
942
943impl std::error::Error for WrappedIoError {}
944
945#[cfg(test)]
946mod tests {
947    use super::*;
948    use std::net::IpAddr;
949    use wireguard_control::{Key, PeerConfigBuilder, PeerStats};
950
951    #[test]
952    fn test_peer_no_diff() {
953        const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE=";
954        let ip: IpAddr = "10.0.0.1".parse().unwrap();
955        let peer = Peer {
956            id: 1,
957            contents: PeerContents {
958                name: "peer1".parse().unwrap(),
959                ip,
960                cidr_id: 1,
961                public_key: PUBKEY.to_owned(),
962                endpoint: None,
963                persistent_keepalive_interval: None,
964                is_admin: false,
965                is_disabled: false,
966                is_redeemed: true,
967                invite_expires: None,
968                candidates: vec![],
969            },
970        };
971        let builder =
972            PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32);
973
974        let config = builder.into_peer_config();
975        let info = PeerInfo {
976            config,
977            stats: Default::default(),
978        };
979
980        let diff = PeerDiff::new(Some(&info), Some(&peer)).unwrap();
981
982        println!("{diff:?}");
983        assert_eq!(diff, None);
984    }
985
986    #[test]
987    fn test_peer_diff() {
988        const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE=";
989        let ip: IpAddr = "10.0.0.1".parse().unwrap();
990        let peer = Peer {
991            id: 1,
992            contents: PeerContents {
993                name: "peer1".parse().unwrap(),
994                ip,
995                cidr_id: 1,
996                public_key: PUBKEY.to_owned(),
997                endpoint: None,
998                persistent_keepalive_interval: Some(15),
999                is_admin: false,
1000                is_disabled: false,
1001                is_redeemed: true,
1002                invite_expires: None,
1003                candidates: vec![],
1004            },
1005        };
1006        let builder =
1007            PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32);
1008
1009        let config = builder.into_peer_config();
1010        let info = PeerInfo {
1011            config,
1012            stats: Default::default(),
1013        };
1014        let diff = PeerDiff::new(Some(&info), Some(&peer)).unwrap();
1015
1016        println!("{peer:?}");
1017        println!("{:?}", info.config);
1018        assert!(diff.is_some());
1019    }
1020
1021    #[test]
1022    fn test_peer_diff_handshake_time() {
1023        const PUBKEY: &str = "4CNZorWVtohO64n6AAaH/JyFjIIgBFrfJK2SGtKjzEE=";
1024        let ip: IpAddr = "10.0.0.1".parse().unwrap();
1025        let peer = Peer {
1026            id: 1,
1027            contents: PeerContents {
1028                name: "peer1".parse().unwrap(),
1029                ip,
1030                cidr_id: 1,
1031                public_key: PUBKEY.to_owned(),
1032                endpoint: Some("1.1.1.1:1111".parse().unwrap()),
1033                persistent_keepalive_interval: None,
1034                is_admin: false,
1035                is_disabled: false,
1036                is_redeemed: true,
1037                invite_expires: None,
1038                candidates: vec![],
1039            },
1040        };
1041        let builder =
1042            PeerConfigBuilder::new(&Key::from_base64(PUBKEY).unwrap()).add_allowed_ip(ip, 32);
1043
1044        let config = builder.into_peer_config();
1045        let mut info = PeerInfo {
1046            config,
1047            stats: PeerStats {
1048                last_handshake_time: Some(SystemTime::now() - Duration::from_secs(200)),
1049                ..Default::default()
1050            },
1051        };
1052
1053        // If there hasn't been a recent handshake, endpoint should be being set.
1054        assert!(matches!(
1055            PeerDiff::new(Some(&info), Some(&peer)),
1056            Ok(Some(_))
1057        ));
1058
1059        // If there *has* been a recent handshake, endpoint should *not* be being set.
1060        info.stats.last_handshake_time = Some(SystemTime::now());
1061        assert!(matches!(PeerDiff::new(Some(&info), Some(&peer)), Ok(None)));
1062    }
1063}