innernet_shared/
interface_config.rs1use crate::{chmod, ensure_dirs_exist, Endpoint, Error, IoErrorContext, Peer, WrappedIoError};
2use indoc::writedoc;
3use ipnet::IpNet;
4use serde::{Deserialize, Serialize};
5use std::{
6 collections::BTreeMap,
7 fs::{File, OpenOptions},
8 io::{self, Write},
9 net::{IpAddr, SocketAddr},
10 path::{Path, PathBuf},
11};
12use wireguard_control::{InterfaceName, KeyPair};
13
14#[derive(Clone, Deserialize, Serialize, Debug)]
17#[serde(rename_all = "kebab-case")]
18pub struct InterfaceConfig {
19 pub interface: InterfaceInfo,
21
22 pub server: ServerInfo,
24
25 #[serde(default)]
28 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
29 peer_endpoint_overrides: BTreeMap<IpAddr, Endpoint>,
30}
31
32#[derive(Clone, Deserialize, Serialize, Debug)]
33#[serde(rename_all = "kebab-case")]
34pub struct InterfaceInfo {
35 pub network_name: String,
37
38 pub address: IpNet,
41
42 pub private_key: String,
44
45 pub listen_port: Option<u16>,
47}
48
49impl InterfaceInfo {
50 pub fn new(network_name: &InterfaceName, keypair: &KeyPair, address: IpNet) -> Self {
51 Self {
52 network_name: network_name.to_string(),
53 private_key: keypair.private.to_base64(),
54 address,
55 listen_port: None,
56 }
57 }
58}
59
60#[derive(Clone, Deserialize, Serialize, Debug)]
61#[serde(rename_all = "kebab-case")]
62pub struct ServerInfo {
63 pub public_key: String,
65
66 pub external_endpoint: Endpoint,
68
69 pub internal_endpoint: SocketAddr,
71}
72
73impl ServerInfo {
74 pub fn new(server_peer: &Peer, internal_endpoint: SocketAddr) -> Self {
75 Self {
76 external_endpoint: server_peer
77 .endpoint
78 .clone()
79 .expect("The innernet server should have a WireGuard endpoint"),
80 internal_endpoint,
81 public_key: server_peer.public_key.clone(),
82 }
83 }
84}
85
86impl InterfaceConfig {
87 fn new(interface: InterfaceInfo, server: ServerInfo) -> Self {
88 InterfaceConfig {
89 interface,
90 server,
91 peer_endpoint_overrides: BTreeMap::new(),
92 }
93 }
94
95 pub fn save_new(&self, path: impl AsRef<Path>, mode: u32) -> Result<(), WrappedIoError> {
97 let path = path.as_ref();
98 let mut file = OpenOptions::new()
99 .create_new(true)
100 .write(true)
101 .open(path)
102 .with_path(path)?;
103
104 chmod(&file, mode).with_path(path)?;
105
106 file.write_all(self.as_toml().as_bytes()).with_path(path)?;
107
108 Ok(())
109 }
110
111 pub fn save(&self, config_dir: &Path, interface: &InterfaceName) -> Result<PathBuf, Error> {
113 let path = Self::build_config_file_path(config_dir, interface)?;
114 File::create(&path)
115 .with_path(&path)?
116 .write_all(self.as_toml().as_bytes())?;
117
118 Ok(path)
119 }
120
121 fn as_toml(&self) -> String {
122 toml::to_string(self).unwrap()
123 }
124
125 pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self, Error> {
126 Ok(toml::from_str(
127 &std::fs::read_to_string(&path).with_path(path)?,
128 )?)
129 }
130
131 pub fn from_interface(config_dir: &Path, interface: &InterfaceName) -> Result<Self, Error> {
132 let path = Self::build_config_file_path(config_dir, interface)?;
133 crate::warn_on_dangerous_mode(&path).with_path(&path)?;
134 Self::from_file(path)
135 }
136
137 pub fn get_path(config_dir: &Path, interface: &InterfaceName) -> PathBuf {
138 config_dir
139 .join(interface.to_string())
140 .with_extension("conf")
141 }
142
143 pub fn build_config_file_path(
144 config_dir: &Path,
145 interface: &InterfaceName,
146 ) -> Result<PathBuf, WrappedIoError> {
147 ensure_dirs_exist(&[config_dir])?;
148 Ok(Self::get_path(config_dir, interface))
149 }
150
151 pub fn peer_endpoint_overrides(&self) -> &BTreeMap<IpAddr, Endpoint> {
152 &self.peer_endpoint_overrides
153 }
154
155 pub fn set_endpoint_override_for_peer(&mut self, peer_ip: IpAddr, endpoint: Endpoint) {
156 self.peer_endpoint_overrides.insert(peer_ip, endpoint);
157 }
158
159 pub fn unset_endpoint_override_for_peer(&mut self, peer_ip: IpAddr) {
160 self.peer_endpoint_overrides.remove(&peer_ip);
161 }
162}
163
164impl InterfaceInfo {
165 pub fn public_key(&self) -> Result<String, Error> {
166 Ok(wireguard_control::Key::from_base64(&self.private_key)?
167 .get_public()
168 .to_base64())
169 }
170}
171
172#[must_use]
173pub struct PeerInvitation {
174 interface_config: InterfaceConfig,
175}
176
177impl PeerInvitation {
178 pub fn new(interface: InterfaceInfo, server: ServerInfo) -> Self {
179 Self {
180 interface_config: InterfaceConfig::new(interface, server),
181 }
182 }
183
184 pub fn save_new(&self, path: impl AsRef<Path>) -> Result<(), io::Error> {
186 let mut file = OpenOptions::new()
187 .read(true)
188 .write(true)
189 .create_new(true)
190 .open(path)?;
191
192 writedoc!(
193 file,
194 r"
195 # This is an invitation file to an innernet network.
196 #
197 # To join, you must install innernet.
198 # See https://github.com/tonarino/innernet for instructions.
199 #
200 # If you have innernet, just run:
201 #
202 # innernet install <this file>
203 #
204 # Don't edit the contents below unless you love chaos and dysfunction.
205 "
206 )?;
207
208 file.write_all(self.interface_config.as_toml().as_bytes())?;
209
210 Ok(())
211 }
212}