innernet_shared/
interface_config.rs

1use crate::{chmod, ensure_dirs_exist, Endpoint, Error, IoErrorContext, WrappedIoError};
2use indoc::writedoc;
3use ipnet::IpNet;
4use serde::{Deserialize, Serialize};
5use std::{
6    fs::{File, OpenOptions},
7    io::{self, Write},
8    net::SocketAddr,
9    path::{Path, PathBuf},
10};
11use wireguard_control::InterfaceName;
12
13#[derive(Clone, Deserialize, Serialize, Debug)]
14#[serde(rename_all = "kebab-case")]
15pub struct InterfaceConfig {
16    /// The information to bring up the interface.
17    pub interface: InterfaceInfo,
18
19    /// The necessary contact information for the server.
20    pub server: ServerInfo,
21}
22
23#[derive(Clone, Deserialize, Serialize, Debug)]
24#[serde(rename_all = "kebab-case")]
25pub struct InterfaceInfo {
26    /// The interface name (i.e. "tonari")
27    pub network_name: String,
28
29    /// The invited peer's internal IP address that's been allocated to it, inside
30    /// the entire network's CIDR prefix.
31    pub address: IpNet,
32
33    /// WireGuard private key (base64)
34    pub private_key: String,
35
36    /// The local listen port. A random port will be used if `None`.
37    pub listen_port: Option<u16>,
38}
39
40#[derive(Clone, Deserialize, Serialize, Debug)]
41#[serde(rename_all = "kebab-case")]
42pub struct ServerInfo {
43    /// The server's WireGuard public key
44    pub public_key: String,
45
46    /// The external internet endpoint to reach the server.
47    pub external_endpoint: Endpoint,
48
49    /// An internal endpoint in the WireGuard network that hosts the coordination API.
50    pub internal_endpoint: SocketAddr,
51}
52
53impl InterfaceConfig {
54    pub fn write_to(
55        &self,
56        target_file: &mut File,
57        comments: bool,
58        mode: Option<u32>,
59    ) -> Result<(), io::Error> {
60        if let Some(val) = mode {
61            chmod(target_file, val)?;
62        }
63
64        if comments {
65            writedoc!(
66                target_file,
67                r"
68                    # This is an invitation file to an innernet network.
69                    #
70                    # To join, you must install innernet.
71                    # See https://github.com/tonarino/innernet for instructions.
72                    #
73                    # If you have innernet, just run:
74                    #
75                    #   innernet install <this file>
76                    #
77                    # Don't edit the contents below unless you love chaos and dysfunction.
78                "
79            )?;
80        }
81        target_file.write_all(toml::to_string(self).unwrap().as_bytes())?;
82        Ok(())
83    }
84
85    pub fn write_to_path<P: AsRef<Path>>(
86        &self,
87        path: P,
88        comments: bool,
89        mode: Option<u32>,
90    ) -> Result<(), WrappedIoError> {
91        let path = path.as_ref();
92        let mut target_file = OpenOptions::new()
93            .create_new(true)
94            .write(true)
95            .open(path)
96            .with_path(path)?;
97        self.write_to(&mut target_file, comments, mode)
98            .with_path(path)
99    }
100
101    /// Overwrites the config file if it already exists.
102    pub fn write_to_interface(
103        &self,
104        config_dir: &Path,
105        interface: &InterfaceName,
106    ) -> Result<PathBuf, Error> {
107        let path = Self::build_config_file_path(config_dir, interface)?;
108        File::create(&path)
109            .with_path(&path)?
110            .write_all(toml::to_string(self).unwrap().as_bytes())?;
111        Ok(path)
112    }
113
114    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
115        Ok(toml::from_str(
116            &std::fs::read_to_string(&path).with_path(path)?,
117        )?)
118    }
119
120    pub fn from_interface(config_dir: &Path, interface: &InterfaceName) -> Result<Self, Error> {
121        let path = Self::build_config_file_path(config_dir, interface)?;
122        crate::warn_on_dangerous_mode(&path).with_path(&path)?;
123        Self::from_file(path)
124    }
125
126    pub fn get_path(config_dir: &Path, interface: &InterfaceName) -> PathBuf {
127        config_dir
128            .join(interface.to_string())
129            .with_extension("conf")
130    }
131
132    fn build_config_file_path(
133        config_dir: &Path,
134        interface: &InterfaceName,
135    ) -> Result<PathBuf, WrappedIoError> {
136        ensure_dirs_exist(&[config_dir])?;
137        Ok(Self::get_path(config_dir, interface))
138    }
139}
140
141impl InterfaceInfo {
142    pub fn public_key(&self) -> Result<String, Error> {
143        Ok(wireguard_control::Key::from_base64(&self.private_key)?
144            .get_public()
145            .to_base64())
146    }
147}