wireguard_conf/models/interface.rs
1use derive_builder::Builder;
2use either::Either;
3use ipnet::IpNet;
4use itertools::Itertools as _;
5
6use std::fmt;
7use std::net::Ipv4Addr;
8use std::{convert::Infallible, net::IpAddr};
9
10#[cfg(feature = "serde")]
11#[cfg_attr(docsrs, doc(cfg(feature = "serde")))]
12use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
13
14use crate::prelude::*;
15
16/// Controls the routing table to which routes are added.
17#[derive(PartialEq, Eq, Clone, Debug, Default)]
18pub enum Table {
19 /// Routing table
20 RoutingTable(usize),
21
22 /// Disables the creation of routes altogether
23 Off,
24
25 /// Adds routes to the default table and enables special handling of default routes.
26 #[default]
27 Auto,
28}
29
30impl fmt::Display for Table {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 Table::RoutingTable(n) => write!(f, "{n}"),
34 Table::Off => write!(f, "off"),
35 Table::Auto => write!(f, "auto"),
36 }
37 }
38}
39
40#[cfg(feature = "serde")]
41impl Serialize for Table {
42 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
43 where
44 S: Serializer,
45 {
46 match self {
47 Table::RoutingTable(n) => serializer.serialize_u64(*n as u64),
48 Table::Off => serializer.serialize_str("off"),
49 Table::Auto => serializer.serialize_str("auto"),
50 }
51 }
52}
53
54#[cfg(feature = "serde")]
55impl<'de> Deserialize<'de> for Table {
56 fn deserialize<D>(deserializer: D) -> Result<Table, D::Error>
57 where
58 D: Deserializer<'de>,
59 {
60 struct TableVisitor;
61 impl de::Visitor<'_> for TableVisitor {
62 type Value = Table;
63
64 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
65 formatter.write_str("an routing table value (number, off or auto)")
66 }
67
68 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
69 where
70 E: de::Error,
71 {
72 match value {
73 "off" => Ok(Table::Off),
74 "auto" => Ok(Table::Auto),
75 _ => Err(E::invalid_value(de::Unexpected::Str(value), &self)),
76 }
77 }
78
79 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
80 where
81 E: de::Error,
82 {
83 Ok(Table::RoutingTable(
84 usize::try_from(value).map_err(E::custom)?,
85 ))
86 }
87 }
88
89 deserializer.deserialize_any(TableVisitor)
90 }
91}
92
93/// Struct, that represents complete configuration (contains both `[Interface]` and `[Peer]`
94/// sections).
95///
96/// Use [`InterfaceBuilder`] to create interface.
97///
98/// [Wireguard docs](https://github.com/pirate/wireguard-docs#interface)
99#[must_use]
100#[derive(Clone, Debug, PartialEq, Builder)]
101#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
102#[builder(build_fn(private, name = "fallible_build", error = "Infallible"))]
103pub struct Interface {
104 /// Interface's address.
105 ///
106 /// `/32` and `/128` IP networks will be generated as regular ips (f.e. `1.2.3.4/32` -> `1.2.3.4`)
107 ///
108 /// You can also use [`InterfaceBuilder::add_network()`] to add a single network and
109 /// [`InterfaceBuilder::add_address()`] to add a single address.
110 ///
111 /// [Wireguard docs](https://github.com/pirate/wireguard-docs#address)
112 #[builder(
113 setter(into),
114 default = "vec![IpNet::new_assert(Ipv4Addr::UNSPECIFIED.into(), 0)]"
115 )]
116 pub address: Vec<IpNet>,
117
118 /// Port to listen for incoming VPN connections.
119 ///
120 /// [Wireguard conf](https://github.com/pirate/wireguard-docs#listenport)
121 #[builder(setter(strip_option), default)]
122 pub listen_port: Option<u16>,
123
124 /// Node's private key.
125 ///
126 /// [Wireguard conf](https://github.com/pirate/wireguard-docs#privatekey)
127 #[builder(default = "PrivateKey::random()")]
128 pub private_key: PrivateKey,
129
130 /// The DNS servers to announce to VPN clients via DHCP.
131 ///
132 /// [Wireguard docs](https://github.com/pirate/wireguard-docs#dns-2)
133 #[cfg_attr(
134 feature = "serde",
135 serde(default, skip_serializing_if = "Vec::is_empty")
136 )]
137 #[builder(setter(into, strip_option), default)]
138 pub dns: Vec<String>,
139
140 /// Endpoint.
141 ///
142 /// - `[Interface]` section will have `# Name = <endpoint>` comment at the top.
143 /// - Exported [`Peer`] (via [`Interface::to_peer`]) will have this endpoint.
144 ///
145 /// [Wireguard Docs for `# Name`](https://github.com/pirate/wireguard-docs?tab=readme-ov-file#-name-1);
146 /// [Wireguard Docs for endpoint](https://github.com/pirate/wireguard-docs?tab=readme-ov-file#endpoint)
147 #[builder(setter(into, strip_option), default)]
148 pub endpoint: Option<String>,
149
150 /// Routing table to use for the WireGuard routes.
151 ///
152 /// See [`Table`] for special values.
153 ///
154 /// [Wireguard docs](https://github.com/pirate/wireguard-docs?tab=readme-ov-file#table)
155 #[builder(setter(strip_option), default)]
156 pub table: Option<Table>,
157
158 /// Maximum Transmission Unit (MTU, aka packet/frame size) to use when connecting to the peer.
159 ///
160 /// [Wireguard docs](https://github.com/pirate/wireguard-docs?tab=readme-ov-file#mtu)
161 #[builder(setter(strip_option), default)]
162 pub mtu: Option<usize>,
163
164 /// AmneziaWG obfuscation values.
165 ///
166 /// [AmneziaWG Docs](https://docs.amnezia.org/documentation/amnezia-wg)
167 #[cfg(feature = "amneziawg")]
168 #[builder(setter(strip_option), default)]
169 pub amnezia_settings: Option<AmneziaWG>,
170
171 /// Commands, that will be executed before the interface is brought up
172 ///
173 /// [Wireguard docs](https://github.com/pirate/wireguard-docs#preup)
174 #[cfg_attr(
175 feature = "serde",
176 serde(default, skip_serializing_if = "Vec::is_empty")
177 )]
178 #[builder(setter(into), default)]
179 pub pre_up: Vec<String>,
180
181 /// Commands, that will be executed before the interface is brought down
182 ///
183 /// [Wireguard docs](https://github.com/pirate/wireguard-docs#predown)
184 #[cfg_attr(
185 feature = "serde",
186 serde(default, skip_serializing_if = "Vec::is_empty")
187 )]
188 #[builder(setter(into), default)]
189 pub pre_down: Vec<String>,
190
191 /// Commands, that will be executed after the interface is brought up
192 ///
193 /// [Wireguard docs](https://github.com/pirate/wireguard-docs#postup)
194 #[cfg_attr(
195 feature = "serde",
196 serde(default, skip_serializing_if = "Vec::is_empty")
197 )]
198 #[builder(setter(into), default)]
199 pub post_up: Vec<String>,
200
201 /// Commands, that will be executed after the interface is brought down
202 ///
203 /// [Wireguard docs](https://github.com/pirate/wireguard-docs#postdown)
204 #[cfg_attr(
205 feature = "serde",
206 serde(default, skip_serializing_if = "Vec::is_empty")
207 )]
208 #[builder(setter(into), default)]
209 pub post_down: Vec<String>,
210
211 /// Peers.
212 ///
213 /// Create them using [`PeerBuilder`] or [`Interface::to_peer`] method.
214 ///
215 /// [Wireguard docs](https://github.com/pirate/wireguard-docs#peer)
216 #[builder(setter(into), default)]
217 pub peers: Vec<Peer>,
218}
219
220impl Interface {
221 /// Get [`Peer`] from interface.
222 ///
223 /// # Examples
224 ///
225 /// ```
226 /// # use wireguard_conf::prelude::*;
227 /// // Create server node
228 /// let mut server = InterfaceBuilder::new()
229 /// // <snip>
230 /// .build();
231 ///
232 /// // Create client node, and add server to client's peers
233 /// let client = InterfaceBuilder::new()
234 /// // <snip>
235 /// .peers([server.to_peer()]) // convert `Interface` to `Peer` using `.to_peer()` method.
236 /// .build();
237 ///
238 /// // Add client to server's peers
239 /// server.peers.push(client.to_peer());
240 ///
241 /// println!("Server config:\n{server}");
242 /// println!("Client config:\n{client}");
243 /// ```
244 pub fn to_peer(&self) -> Peer {
245 Peer {
246 endpoint: self.endpoint.as_ref().map(|server_endpoint| {
247 format!(
248 "{server_endpoint}:{server_port}",
249 server_port = self.listen_port.unwrap_or(51820)
250 )
251 }),
252 allowed_ips: self.address.clone(),
253 key: Either::Left(self.private_key.clone()),
254 preshared_key: None,
255 persistent_keepalive: 0,
256 }
257 }
258}
259
260impl Interface {
261 /// Create new `InterfaceBuilder`. Alias for `InterfaceBuilder::new()`.
262 ///
263 /// ```rust
264 /// # use wireguard_conf::prelude::*;
265 /// # use wireguard_conf::as_ipnet;
266 /// #
267 /// let interface = Interface::builder()
268 /// .address([as_ipnet!("10.0.0.1/24")])
269 /// // <snip>
270 /// .build();
271 /// ```
272 #[must_use]
273 pub fn builder() -> InterfaceBuilder {
274 InterfaceBuilder::default()
275 }
276}
277
278impl InterfaceBuilder {
279 /// Create new `InterfaceBuilder`.
280 ///
281 /// ```rust
282 /// # use wireguard_conf::prelude::*;
283 /// # use wireguard_conf::as_ipnet;
284 /// #
285 /// let interface = InterfaceBuilder::new()
286 /// .address([as_ipnet!("10.0.0.1/24")])
287 /// // <snip>
288 /// .build();
289 /// ```
290 #[must_use]
291 pub fn new() -> Self {
292 Self::default()
293 }
294
295 /// Adds IP Network to `Address = ...` field.
296 ///
297 /// `value` is [`Into<IpNet>`], which means that it can be either [`ipnet::IpNet`] or [`std::net::IpAddr`].
298 ///
299 /// # Example
300 ///
301 /// ```rust
302 /// use wireguard_conf::{as_ipnet, prelude::*};
303 ///
304 /// let interface = InterfaceBuilder::new()
305 /// .add_network(as_ipnet!("1.2.3.4/16"))
306 /// .add_network(as_ipnet!("fd00:DEAD:BEEF::1/48"))
307 /// .build();
308 ///
309 /// assert_eq!(
310 /// interface.address,
311 /// vec![
312 /// as_ipnet!("1.2.3.4/16"),
313 /// as_ipnet!("fd00:DEAD:BEEF::1/48")
314 /// ]
315 /// );
316 /// ```
317 pub fn add_network<T: Into<IpNet>>(&mut self, value: T) -> &mut Self {
318 if self.address.is_none() {
319 self.address = Some(Vec::with_capacity(1));
320 }
321
322 self.address
323 .as_mut()
324 .unwrap_or_else(|| unreachable!())
325 .push(value.into());
326 self
327 }
328
329 /// Adds IP address to `Address = ...` field.
330 ///
331 /// `value` is [`Into<IpAddr>`], which means that it can be either [`std::net::Ipv4Addr`] or [`std::net::Ipv6Addr`].
332 ///
333 /// # Example
334 ///
335 /// ```rust
336 /// use wireguard_conf::{as_ipaddr, as_ipnet, prelude::*};
337 ///
338 /// let interface = InterfaceBuilder::new()
339 /// .add_address(as_ipaddr!("1.2.3.4"))
340 /// .add_address(as_ipaddr!("fd00::1"))
341 /// .build();
342 ///
343 /// // /32 and /128 are added automatically
344 /// assert_eq!(
345 /// interface.address,
346 /// vec![
347 /// as_ipnet!("1.2.3.4/32"),
348 /// as_ipnet!("fd00::1/128"),
349 /// ]
350 /// );
351 /// ```
352 pub fn add_address<T: Into<IpAddr>>(&mut self, value: T) -> &mut Self {
353 if self.address.is_none() {
354 self.address = Some(Vec::with_capacity(1));
355 }
356
357 let ip_addr = value.into();
358 let ip_net = if ip_addr.is_ipv4() {
359 IpNet::new_assert(ip_addr, 32) // 1.2.3.4/32
360 } else {
361 IpNet::new_assert(ip_addr, 128) // fd00::1/128
362 };
363
364 self.address
365 .as_mut()
366 .unwrap_or_else(|| unreachable!())
367 .push(ip_net);
368 self
369 }
370
371 /// Builds an `Interface`.
372 pub fn build(&self) -> Interface {
373 self.fallible_build().unwrap_or_else(|_| unreachable!())
374 }
375}
376
377impl fmt::Display for Interface {
378 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
379 writeln!(f, "[Interface]")?;
380 if let Some(endpoint) = &self.endpoint {
381 writeln!(f, "# Name = {endpoint}")?;
382 }
383 writeln!(
384 f,
385 "Address = {}",
386 self.address
387 .iter()
388 .map(ToString::to_string)
389 .map(|addr| {
390 if addr.ends_with("/32") {
391 addr.trim_end_matches("/32").to_owned()
392 } else if addr.ends_with("/128") {
393 addr.trim_end_matches("/128").to_owned()
394 } else {
395 addr
396 }
397 })
398 .join(",")
399 )?;
400 if let Some(listen_port) = self.listen_port {
401 writeln!(f, "ListenPort = {listen_port}")?;
402 }
403 writeln!(f, "PrivateKey = {}", self.private_key)?;
404 if !self.dns.is_empty() {
405 writeln!(f, "DNS = {}", self.dns.join(","))?;
406 }
407 if let Some(table) = &self.table {
408 writeln!(f, "Table = {table}")?;
409 }
410 if let Some(mtu) = &self.mtu {
411 writeln!(f, "MTU = {mtu}")?;
412 }
413
414 if !self.pre_up.is_empty() {
415 writeln!(f)?;
416 for snippet in &self.pre_up {
417 writeln!(f, "PreUp = {snippet}")?;
418 }
419 }
420 if !self.pre_down.is_empty() {
421 writeln!(f)?;
422 for snippet in &self.pre_down {
423 writeln!(f, "PreDown = {snippet}")?;
424 }
425 }
426 if !self.post_up.is_empty() {
427 writeln!(f)?;
428 for snippet in &self.post_up {
429 writeln!(f, "PostUp = {snippet}")?;
430 }
431 }
432 if !self.post_down.is_empty() {
433 writeln!(f)?;
434 for snippet in &self.post_down {
435 writeln!(f, "PostDown = {snippet}")?;
436 }
437 }
438
439 #[cfg(feature = "amneziawg")]
440 if let Some(amnezia_settings) = &self.amnezia_settings {
441 writeln!(f)?;
442 writeln!(f, "{amnezia_settings}")?;
443 }
444
445 for peer in &self.peers {
446 writeln!(f)?;
447 writeln!(f, "{peer}")?;
448 }
449
450 fmt::Result::Ok(())
451 }
452}