Skip to main content

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}