public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Christoph Heiss <c.heiss@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox-ve-rs 11/11] ve-config: sdn: fabrics: add wireguard section config types
Date: Fri, 16 Jan 2026 16:33:16 +0100	[thread overview]
Message-ID: <20260116153317.1146323-12-c.heiss@proxmox.com> (raw)
In-Reply-To: <20260116153317.1146323-1-c.heiss@proxmox.com>

Signed-off-by: Christoph Heiss <c.heiss@proxmox.com>
---
 Cargo.toml                                    |   1 +
 proxmox-ve-config/Cargo.toml                  |   2 +
 proxmox-ve-config/debian/control              |  18 +-
 proxmox-ve-config/src/sdn/fabric/frr.rs       |   1 +
 proxmox-ve-config/src/sdn/fabric/mod.rs       | 119 +++++++++++++
 .../src/sdn/fabric/section_config/fabric.rs   |  23 +++
 .../src/sdn/fabric/section_config/mod.rs      |  19 ++
 .../src/sdn/fabric/section_config/node.rs     |  33 +++-
 .../sdn/fabric/section_config/protocol/mod.rs |   1 +
 .../section_config/protocol/wireguard.rs      | 162 ++++++++++++++++++
 10 files changed, 374 insertions(+), 5 deletions(-)
 create mode 100644 proxmox-ve-config/src/sdn/fabric/section_config/protocol/wireguard.rs

diff --git a/Cargo.toml b/Cargo.toml
index 99bd54a..d925d5a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -29,3 +29,4 @@ proxmox-network-types = { version = "0.1.1" }
 proxmox-schema = { version = "5" }
 proxmox-sdn-types = { version = "0.1", path = "proxmox-sdn-types" }
 proxmox-serde = { version = "1.0.0" }
+proxmox-wireguard = { version = "0.1.0" }
diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml
index 3a4dd61..130430c 100644
--- a/proxmox-ve-config/Cargo.toml
+++ b/proxmox-ve-config/Cargo.toml
@@ -22,6 +22,7 @@ proxmox-frr = { workspace = true, optional = true }
 proxmox-network-types = { workspace = true, features = [ "api-types" ] }
 proxmox-schema = { workspace = true, features = [ "api-types" ] }
 proxmox-sdn-types = { workspace = true }
+proxmox-wireguard = { workspace = true, optional = true }
 proxmox-section-config = { version = "3" }
 proxmox-serde = { workspace = true, features = [ "perl" ]}
 proxmox-sys = "1"
@@ -29,6 +30,7 @@ proxmox-sortable-macro = "1"
 
 [features]
 frr = ["dep:proxmox-frr"]
+wireguard = ["dep:proxmox-wireguard"]
 
 [dev-dependencies]
 insta = "1.21"
diff --git a/proxmox-ve-config/debian/control b/proxmox-ve-config/debian/control
index b211827..9cbe1b8 100644
--- a/proxmox-ve-config/debian/control
+++ b/proxmox-ve-config/debian/control
@@ -58,7 +58,8 @@ Depends:
  librust-thiserror-2+default-dev,
  librust-tracing-0.1+default-dev (>= 0.1.37-~~)
 Suggests:
- librust-proxmox-ve-config+frr-dev (= ${binary:Version})
+ librust-proxmox-ve-config+frr-dev (= ${binary:Version}),
+ librust-proxmox-ve-config+wireguard-dev (= ${binary:Version})
 Provides:
  librust-proxmox-ve-config+default-dev (= ${binary:Version}),
  librust-proxmox-ve-config-0-dev (= ${binary:Version}),
@@ -84,3 +85,18 @@ Provides:
 Description: Rust crate "proxmox-ve-config" - feature "frr"
  This metapackage enables feature "frr" for the Rust proxmox-ve-config crate, by
  pulling in any additional dependencies needed by that feature.
+
+Package: librust-proxmox-ve-config+wireguard-dev
+Architecture: any
+Multi-Arch: same
+Depends:
+ ${misc:Depends},
+ librust-proxmox-ve-config-dev (= ${binary:Version}),
+ librust-proxmox-wireguard-0.1+default-dev
+Provides:
+ librust-proxmox-ve-config-0+wireguard-dev (= ${binary:Version}),
+ librust-proxmox-ve-config-0.4+wireguard-dev (= ${binary:Version}),
+ librust-proxmox-ve-config-0.4.6+wireguard-dev (= ${binary:Version})
+Description: Rust crate "proxmox-ve-config" - feature "wireguard"
+ This metapackage enables feature "wireguard" for the Rust proxmox-ve-config
+ crate, by pulling in any additional dependencies needed by that feature.
diff --git a/proxmox-ve-config/src/sdn/fabric/frr.rs b/proxmox-ve-config/src/sdn/fabric/frr.rs
index 10025b3..fc41410 100644
--- a/proxmox-ve-config/src/sdn/fabric/frr.rs
+++ b/proxmox-ve-config/src/sdn/fabric/frr.rs
@@ -232,6 +232,7 @@ pub fn build_fabric(
 
                 frr_config.protocol_routemaps.insert(protocol_routemap);
             }
+            FabricEntry::WireGuard(_) => {} // not a frr fabric
         }
     }
     Ok(())
diff --git a/proxmox-ve-config/src/sdn/fabric/mod.rs b/proxmox-ve-config/src/sdn/fabric/mod.rs
index d0add92..a3b9606 100644
--- a/proxmox-ve-config/src/sdn/fabric/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/mod.rs
@@ -7,6 +7,10 @@ use std::marker::PhantomData;
 use std::ops::Deref;
 
 use anyhow::Error;
+use section_config::protocol::wireguard::{
+    WireGuardDeletableProperties, WireGuardNodeDeletableProperties, WireGuardNodeProperties,
+    WireGuardNodePropertiesUpdater, WireGuardProperties, WireGuardPropertiesUpdater,
+};
 use serde::{Deserialize, Serialize};
 
 use proxmox_section_config::typed::{ApiSectionDataEntry, SectionConfigData};
@@ -189,6 +193,7 @@ macro_rules! impl_entry {
 
 impl_entry!(Openfabric, OpenfabricProperties, OpenfabricNodeProperties);
 impl_entry!(Ospf, OspfProperties, OspfNodeProperties);
+impl_entry!(WireGuard, WireGuardProperties, WireGuardNodeProperties);
 
 /// All possible entries in a [`FabricConfig`].
 ///
@@ -198,6 +203,7 @@ impl_entry!(Ospf, OspfProperties, OspfNodeProperties);
 pub enum FabricEntry {
     Openfabric(Entry<OpenfabricProperties, OpenfabricNodeProperties>),
     Ospf(Entry<OspfProperties, OspfNodeProperties>),
+    WireGuard(Entry<WireGuardProperties, WireGuardNodeProperties>),
 }
 
 impl FabricEntry {
@@ -209,6 +215,9 @@ impl FabricEntry {
                 entry.add_node(node_section)
             }
             (FabricEntry::Ospf(entry), Node::Ospf(node_section)) => entry.add_node(node_section),
+            (FabricEntry::WireGuard(entry), Node::WireGuard(node_section)) => {
+                entry.add_node(node_section)
+            }
             _ => Err(FabricConfigError::ProtocolMismatch),
         }
     }
@@ -219,6 +228,7 @@ impl FabricEntry {
         match self {
             FabricEntry::Openfabric(entry) => entry.get_node(id),
             FabricEntry::Ospf(entry) => entry.get_node(id),
+            FabricEntry::WireGuard(entry) => entry.get_node(id),
         }
     }
 
@@ -228,6 +238,7 @@ impl FabricEntry {
         match self {
             FabricEntry::Openfabric(entry) => entry.get_node_mut(id),
             FabricEntry::Ospf(entry) => entry.get_node_mut(id),
+            FabricEntry::WireGuard(entry) => entry.get_node_mut(id),
         }
     }
 
@@ -307,6 +318,52 @@ impl FabricEntry {
 
                 Ok(())
             }
+            (Node::WireGuard(node_section), NodeUpdater::WireGuard(updater)) => {
+                let NodeDataUpdater::<
+                    WireGuardNodePropertiesUpdater,
+                    WireGuardNodeDeletableProperties,
+                > {
+                    ip,
+                    ip6,
+                    properties:
+                        WireGuardNodePropertiesUpdater {
+                            interfaces,
+                            listen_port,
+                        },
+                    delete,
+                } = updater;
+
+                if let Some(ip) = ip {
+                    node_section.ip = Some(ip);
+                }
+
+                if let Some(ip) = ip6 {
+                    node_section.ip6 = Some(ip);
+                }
+
+                if let Some(interfaces) = interfaces {
+                    node_section.properties.interfaces = interfaces;
+                }
+
+                if let Some(listen_port) = listen_port {
+                    node_section.properties.listen_port = Some(listen_port);
+                }
+
+                for property in delete {
+                    match property {
+                        NodeDeletableProperties::Ip => node_section.ip = None,
+                        NodeDeletableProperties::Ip6 => node_section.ip6 = None,
+                        NodeDeletableProperties::Protocol(
+                            WireGuardNodeDeletableProperties::Interfaces,
+                        ) => node_section.properties.interfaces = Vec::new(),
+                        NodeDeletableProperties::Protocol(
+                            WireGuardNodeDeletableProperties::ListenPort,
+                        ) => node_section.properties.listen_port = None,
+                    }
+                }
+
+                Ok(())
+            }
             _ => Err(FabricConfigError::ProtocolMismatch),
         }
     }
@@ -316,6 +373,7 @@ impl FabricEntry {
         match self {
             FabricEntry::Openfabric(entry) => entry.nodes.iter(),
             FabricEntry::Ospf(entry) => entry.nodes.iter(),
+            FabricEntry::WireGuard(entry) => entry.nodes.iter(),
         }
     }
 
@@ -324,6 +382,7 @@ impl FabricEntry {
         match self {
             FabricEntry::Openfabric(entry) => entry.delete_node(id),
             FabricEntry::Ospf(entry) => entry.delete_node(id),
+            FabricEntry::WireGuard(entry) => entry.delete_node(id),
         }
     }
 
@@ -333,6 +392,7 @@ impl FabricEntry {
         match self {
             FabricEntry::Openfabric(entry) => entry.into_pair(),
             FabricEntry::Ospf(entry) => entry.into_pair(),
+            FabricEntry::WireGuard(entry) => entry.into_pair(),
         }
     }
 
@@ -341,6 +401,7 @@ impl FabricEntry {
         match self {
             FabricEntry::Openfabric(entry) => &entry.fabric,
             FabricEntry::Ospf(entry) => &entry.fabric,
+            FabricEntry::WireGuard(entry) => &entry.fabric,
         }
     }
 
@@ -349,6 +410,7 @@ impl FabricEntry {
         match self {
             FabricEntry::Openfabric(entry) => &mut entry.fabric,
             FabricEntry::Ospf(entry) => &mut entry.fabric,
+            FabricEntry::WireGuard(entry) => &mut entry.fabric,
         }
     }
 }
@@ -360,6 +422,7 @@ impl From<Fabric> for FabricEntry {
                 FabricEntry::Openfabric(Entry::new(fabric_section))
             }
             Fabric::Ospf(fabric_section) => FabricEntry::Ospf(Entry::new(fabric_section)),
+            Fabric::WireGuard(fabric_section) => FabricEntry::WireGuard(Entry::new(fabric_section)),
         }
     }
 }
@@ -541,6 +604,13 @@ impl Validatable for FabricConfig {
                             return Err(FabricConfigError::DuplicateInterface);
                         }
                     }
+                    Node::WireGuard(node_section) => {
+                        if !node_section.properties().interfaces().all(|interface| {
+                            node_interfaces.insert((node_id, interface.name.as_str()))
+                        }) {
+                            return Err(FabricConfigError::DuplicateInterface);
+                        }
+                    }
                 }
             }
 
@@ -695,6 +765,55 @@ impl FabricConfig {
 
                 Ok(())
             }
+            (Fabric::WireGuard(fabric_section), FabricUpdater::WireGuard(updater)) => {
+                let FabricSectionUpdater::<
+                    WireGuardPropertiesUpdater,
+                    WireGuardDeletableProperties,
+                > {
+                    ip_prefix,
+                    ip6_prefix,
+                    properties:
+                        WireGuardPropertiesUpdater {
+                            persistent_keepalive,
+                            listen_port,
+                        },
+                    delete,
+                } = updater;
+
+                if let Some(prefix) = ip_prefix {
+                    fabric_section.ip_prefix = Some(prefix);
+                }
+
+                if let Some(prefix) = ip6_prefix {
+                    fabric_section.ip6_prefix = Some(prefix);
+                }
+
+                if let Some(keepalive) = persistent_keepalive {
+                    fabric_section.properties.persistent_keepalive = Some(keepalive);
+                }
+
+                if let Some(listen_port) = listen_port {
+                    fabric_section.properties.listen_port = listen_port;
+                }
+
+                for property in delete {
+                    match property {
+                        FabricDeletableProperties::IpPrefix => {
+                            fabric_section.ip_prefix = None;
+                        }
+                        FabricDeletableProperties::Ip6Prefix => {
+                            fabric_section.ip6_prefix = None;
+                        }
+                        FabricDeletableProperties::Protocol(
+                            WireGuardDeletableProperties::PersistentKeepalive,
+                        ) => {
+                            fabric_section.properties.persistent_keepalive = None;
+                        }
+                    }
+                }
+
+                Ok(())
+            }
             _ => Err(FabricConfigError::ProtocolMismatch),
         }
     }
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
index 38911a6..fbfd1a8 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/fabric.rs
@@ -16,6 +16,10 @@ use crate::sdn::fabric::section_config::protocol::ospf::{
 };
 use crate::sdn::fabric::FabricConfigError;
 
+use super::protocol::wireguard::{
+    WireGuardDeletableProperties, WireGuardProperties, WireGuardPropertiesUpdater,
+};
+
 pub const FABRIC_ID_REGEX_STR: &str = r"(?:[a-zA-Z0-9])(?:[a-zA-Z0-9\-]){0,6}(?:[a-zA-Z0-9])?";
 
 const_regex! {
@@ -139,6 +143,10 @@ impl UpdaterType for FabricSection<OspfProperties> {
     type Updater = FabricSectionUpdater<OspfPropertiesUpdater, OspfDeletableProperties>;
 }
 
+impl UpdaterType for FabricSection<WireGuardProperties> {
+    type Updater = FabricSectionUpdater<WireGuardPropertiesUpdater, WireGuardDeletableProperties>;
+}
+
 /// Enum containing all types of fabrics.
 ///
 /// It utilizes [`FabricSection<T>`] to define all possible types of fabrics. For parsing the
@@ -159,6 +167,7 @@ impl UpdaterType for FabricSection<OspfProperties> {
 pub enum Fabric {
     Openfabric(FabricSection<OpenfabricProperties>),
     Ospf(FabricSection<OspfProperties>),
+    WireGuard(FabricSection<WireGuardProperties>),
 }
 
 impl UpdaterType for Fabric {
@@ -173,6 +182,7 @@ impl Fabric {
         match self {
             Self::Openfabric(fabric_section) => fabric_section.id(),
             Self::Ospf(fabric_section) => fabric_section.id(),
+            Self::WireGuard(fabric_section) => fabric_section.id(),
         }
     }
 
@@ -183,6 +193,7 @@ impl Fabric {
         match self {
             Fabric::Openfabric(fabric_section) => fabric_section.ip_prefix(),
             Fabric::Ospf(fabric_section) => fabric_section.ip_prefix(),
+            Fabric::WireGuard(fabric_section) => fabric_section.ip_prefix(),
         }
     }
 
@@ -193,6 +204,7 @@ impl Fabric {
         match self {
             Fabric::Openfabric(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
             Fabric::Ospf(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
+            Fabric::WireGuard(fabric_section) => fabric_section.ip_prefix = Some(ipv4_cidr),
         }
     }
 
@@ -203,6 +215,7 @@ impl Fabric {
         match self {
             Fabric::Openfabric(fabric_section) => fabric_section.ip6_prefix(),
             Fabric::Ospf(fabric_section) => fabric_section.ip6_prefix(),
+            Fabric::WireGuard(fabric_section) => fabric_section.ip6_prefix(),
         }
     }
 
@@ -213,6 +226,7 @@ impl Fabric {
         match self {
             Fabric::Openfabric(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
             Fabric::Ospf(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
+            Fabric::WireGuard(fabric_section) => fabric_section.ip6_prefix = Some(ipv6_cidr),
         }
     }
 }
@@ -225,6 +239,7 @@ impl Validatable for Fabric {
         match self {
             Fabric::Openfabric(fabric_section) => fabric_section.validate(),
             Fabric::Ospf(fabric_section) => fabric_section.validate(),
+            Fabric::WireGuard(fabric_section) => fabric_section.validate(),
         }
     }
 }
@@ -241,12 +256,19 @@ impl From<FabricSection<OspfProperties>> for Fabric {
     }
 }
 
+impl From<FabricSection<WireGuardProperties>> for Fabric {
+    fn from(section: FabricSection<WireGuardProperties>) -> Self {
+        Fabric::WireGuard(section)
+    }
+}
+
 /// Enum containing all updater types for fabrics
 #[derive(Debug, Clone, Serialize, Deserialize)]
 #[serde(rename_all = "snake_case", tag = "protocol")]
 pub enum FabricUpdater {
     Openfabric(<FabricSection<OpenfabricProperties> as UpdaterType>::Updater),
     Ospf(<FabricSection<OspfProperties> as UpdaterType>::Updater),
+    WireGuard(<FabricSection<WireGuardProperties> as UpdaterType>::Updater),
 }
 
 impl Updater for FabricUpdater {
@@ -254,6 +276,7 @@ impl Updater for FabricUpdater {
         match self {
             FabricUpdater::Openfabric(updater) => updater.is_empty(),
             FabricUpdater::Ospf(updater) => updater.is_empty(),
+            FabricUpdater::WireGuard(updater) => updater.is_empty(),
         }
     }
 }
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
index d02d4ae..454145d 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/mod.rs
@@ -4,6 +4,7 @@ pub mod node;
 pub mod protocol;
 
 use const_format::concatcp;
+use protocol::wireguard::{WireGuardNodeProperties, WireGuardProperties};
 use serde::{Deserialize, Serialize};
 
 use crate::sdn::fabric::section_config::{
@@ -31,8 +32,10 @@ impl From<Section> for FabricOrNode<Fabric, Node> {
         match section {
             Section::OpenfabricFabric(fabric_section) => Self::Fabric(fabric_section.into()),
             Section::OspfFabric(fabric_section) => Self::Fabric(fabric_section.into()),
+            Section::WireGuardFabric(fabric_section) => Self::Fabric(fabric_section.into()),
             Section::OpenfabricNode(node_section) => Self::Node(node_section.into()),
             Section::OspfNode(node_section) => Self::Node(node_section.into()),
+            Section::WireGuardNode(node_section) => Self::Node(node_section.into()),
         }
     }
 }
@@ -62,8 +65,10 @@ pub const SECTION_ID_FORMAT: ApiStringFormat = ApiStringFormat::Pattern(&SECTION
 pub enum Section {
     OpenfabricFabric(FabricSection<OpenfabricProperties>),
     OspfFabric(FabricSection<OspfProperties>),
+    WireGuardFabric(FabricSection<WireGuardProperties>),
     OpenfabricNode(NodeSection<OpenfabricNodeProperties>),
     OspfNode(NodeSection<OspfNodeProperties>),
+    WireGuardNode(NodeSection<WireGuardNodeProperties>),
 }
 
 impl From<FabricSection<OpenfabricProperties>> for Section {
@@ -78,6 +83,12 @@ impl From<FabricSection<OspfProperties>> for Section {
     }
 }
 
+impl From<FabricSection<WireGuardProperties>> for Section {
+    fn from(section: FabricSection<WireGuardProperties>) -> Self {
+        Self::WireGuardFabric(section)
+    }
+}
+
 impl From<NodeSection<OpenfabricNodeProperties>> for Section {
     fn from(section: NodeSection<OpenfabricNodeProperties>) -> Self {
         Self::OpenfabricNode(section)
@@ -90,11 +101,18 @@ impl From<NodeSection<OspfNodeProperties>> for Section {
     }
 }
 
+impl From<NodeSection<WireGuardNodeProperties>> for Section {
+    fn from(section: NodeSection<WireGuardNodeProperties>) -> Self {
+        Self::WireGuardNode(section)
+    }
+}
+
 impl From<Fabric> for Section {
     fn from(fabric: Fabric) -> Self {
         match fabric {
             Fabric::Openfabric(fabric_section) => fabric_section.into(),
             Fabric::Ospf(fabric_section) => fabric_section.into(),
+            Fabric::WireGuard(fabric_section) => fabric_section.into(),
         }
     }
 }
@@ -104,6 +122,7 @@ impl From<Node> for Section {
         match node {
             Node::Openfabric(node_section) => node_section.into(),
             Node::Ospf(node_section) => node_section.into(),
+            Node::WireGuard(node_section) => node_section.into(),
         }
     }
 }
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
index 17d2f0b..5397b17 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/node.rs
@@ -16,6 +16,8 @@ use crate::sdn::fabric::section_config::{
 };
 use crate::sdn::fabric::FabricConfigError;
 
+use super::protocol::wireguard::WireGuardNodeProperties;
+
 pub const NODE_ID_REGEX_STR: &str = r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]){0,61}(?:[a-zA-Z0-9]){0,1})";
 
 const_regex! {
@@ -147,8 +149,8 @@ impl<T> NodeSection<T> {
     /// Get the IPv4 address (Router-ID) of the [`NodeSection`].
     ///
     /// Either the [`NodeSection::ip`] (IPv4) address or the [`NodeSection::ip6`] (IPv6) address *must*
-    /// be set. This is checked during the validation, so it's guaranteed. OpenFabric can also be
-    /// used dual-stack, so both IPv4 and IPv6 addresses can be set.
+    /// be set. This is checked during the validation, so it's guaranteed. OpenFabric and WireGuard
+    /// can also be used dual-stack, so both IPv4 and IPv6 addresses can be set.
     pub fn ip(&self) -> Option<std::net::Ipv4Addr> {
         self.ip.as_deref().copied()
     }
@@ -156,8 +158,8 @@ impl<T> NodeSection<T> {
     /// Get the IPv6 address (Router-ID) of the [`NodeSection`].
     ///
     /// Either the [`NodeSection::ip`] (IPv4) address or the [`NodeSection::ip6`] (IPv6) address *must*
-    /// be set. This is checked during the validation, so it's guaranteed. OpenFabric can also be
-    /// used dual-stack, so both IPv4 and IPv6 addresses can be set.
+    /// be set. This is checked during the validation, so it's guaranteed. OpenFabric and WireGuard
+    /// can also be used dual-stack, so both IPv4 and IPv6 addresses can be set.
     pub fn ip6(&self) -> Option<std::net::Ipv6Addr> {
         self.ip6.as_deref().copied()
     }
@@ -186,6 +188,7 @@ impl<T: ApiType> ApiType for NodeSection<T> {
 pub enum Node {
     Openfabric(NodeSection<OpenfabricNodeProperties>),
     Ospf(NodeSection<OspfNodeProperties>),
+    WireGuard(NodeSection<WireGuardNodeProperties>),
 }
 
 impl Node {
@@ -194,6 +197,7 @@ impl Node {
         match self {
             Node::Openfabric(node_section) => node_section.id(),
             Node::Ospf(node_section) => node_section.id(),
+            Node::WireGuard(node_section) => node_section.id(),
         }
     }
 
@@ -202,6 +206,7 @@ impl Node {
         match self {
             Node::Openfabric(node_section) => node_section.ip(),
             Node::Ospf(node_section) => node_section.ip(),
+            Node::WireGuard(node_section) => node_section.ip(),
         }
     }
 
@@ -210,6 +215,7 @@ impl Node {
         match self {
             Node::Openfabric(node_section) => node_section.ip6(),
             Node::Ospf(node_section) => node_section.ip6(),
+            Node::WireGuard(node_section) => node_section.ip6(),
         }
     }
 }
@@ -221,6 +227,7 @@ impl Validatable for Node {
         match self {
             Node::Openfabric(node_section) => node_section.validate(),
             Node::Ospf(node_section) => node_section.validate(),
+            Node::WireGuard(node_section) => node_section.validate(),
         }
     }
 }
@@ -237,6 +244,12 @@ impl From<NodeSection<OspfNodeProperties>> for Node {
     }
 }
 
+impl From<NodeSection<WireGuardNodeProperties>> for Node {
+    fn from(value: NodeSection<WireGuardNodeProperties>) -> Self {
+        Self::WireGuard(value)
+    }
+}
+
 /// API types for SDN fabric node configurations.
 ///
 /// This module provides specialized types that are used for API interactions when retrieving,
@@ -263,6 +276,7 @@ pub mod api {
             OpenfabricNodePropertiesUpdater,
         },
         ospf::{OspfNodeDeletableProperties, OspfNodeProperties, OspfNodePropertiesUpdater},
+        wireguard::{WireGuardNodeDeletableProperties, WireGuardNodePropertiesUpdater},
     };
 
     use super::*;
@@ -320,6 +334,7 @@ pub mod api {
     pub enum Node {
         Openfabric(NodeData<OpenfabricNodeProperties>),
         Ospf(NodeData<OspfNodeProperties>),
+        WireGuard(NodeData<WireGuardNodeProperties>),
     }
 
     impl From<super::Node> for Node {
@@ -327,6 +342,7 @@ pub mod api {
             match value {
                 super::Node::Openfabric(node_section) => Self::Openfabric(node_section.into()),
                 super::Node::Ospf(node_section) => Self::Ospf(node_section.into()),
+                super::Node::WireGuard(node_section) => Self::WireGuard(node_section.into()),
             }
         }
     }
@@ -336,6 +352,7 @@ pub mod api {
             match value {
                 Node::Openfabric(node_section) => Self::Openfabric(node_section.into()),
                 Node::Ospf(node_section) => Self::Ospf(node_section.into()),
+                Node::WireGuard(node_section) => Self::WireGuard(node_section.into()),
             }
         }
     }
@@ -349,6 +366,11 @@ pub mod api {
         type Updater = NodeDataUpdater<OspfNodePropertiesUpdater, OspfNodeDeletableProperties>;
     }
 
+    impl UpdaterType for NodeData<WireGuardNodeProperties> {
+        type Updater =
+            NodeDataUpdater<WireGuardNodePropertiesUpdater, WireGuardNodeDeletableProperties>;
+    }
+
     #[derive(Debug, Clone, Serialize, Deserialize)]
     pub struct NodeDataUpdater<T, D> {
         #[serde(skip_serializing_if = "Option::is_none")]
@@ -384,6 +406,9 @@ pub mod api {
             NodeDataUpdater<OpenfabricNodePropertiesUpdater, OpenfabricNodeDeletableProperties>,
         ),
         Ospf(NodeDataUpdater<OspfNodePropertiesUpdater, OspfNodeDeletableProperties>),
+        WireGuard(
+            NodeDataUpdater<WireGuardNodePropertiesUpdater, WireGuardNodeDeletableProperties>,
+        ),
     }
 
     #[derive(Debug, Clone, Serialize, Deserialize)]
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
index c1ec847..fd77426 100644
--- a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/mod.rs
@@ -1,2 +1,3 @@
 pub mod openfabric;
 pub mod ospf;
+pub mod wireguard;
diff --git a/proxmox-ve-config/src/sdn/fabric/section_config/protocol/wireguard.rs b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/wireguard.rs
new file mode 100644
index 0000000..2cc44fc
--- /dev/null
+++ b/proxmox-ve-config/src/sdn/fabric/section_config/protocol/wireguard.rs
@@ -0,0 +1,162 @@
+//! API and section-config interface for WireGuard as an SDN fabric.
+
+use std::ops::{Deref, DerefMut};
+
+use anyhow::Result;
+use proxmox_network_types::ip_address::{Ipv4Cidr, Ipv6Cidr};
+use proxmox_schema::{
+    api, api_types::PORT_SCHEMA, property_string::PropertyString, ApiStringFormat, Updater,
+};
+use proxmox_sdn_types::wireguard::PersistentKeepalive;
+use serde::{Deserialize, Serialize};
+
+use crate::{
+    common::valid::Validatable,
+    sdn::fabric::{
+        section_config::{fabric::FabricSection, interface::InterfaceName, node::NodeSection},
+        FabricConfigError,
+    },
+};
+
+/// Protocol-specific options for an WireGuard fabric.
+#[api]
+#[derive(Clone, Debug, Serialize, Deserialize, Updater, Hash)]
+pub struct WireGuardProperties {
+    /// Fabric-wide persistent keepalive interval between peers.
+    // While this is actually a per-peer property,
+    // we only allow setting it on a per-fabric level (for now) keep configuration
+    // simpler, as you rarely actually want specific intervals per peer, especially
+    // in a cluster environment.
+    #[serde(skip_serializing_if = "persistent_keepalive_is_off")]
+    pub(crate) persistent_keepalive: Option<PersistentKeepalive>,
+    /// Fabric-wide listen port for WireGuard traffic.
+    pub(crate) listen_port: u16,
+}
+
+impl Validatable for FabricSection<WireGuardProperties> {
+    type Error = FabricConfigError;
+
+    /// Validates a [FabricSection<WireGuardProperties>].
+    fn validate(&self) -> Result<(), Self::Error> {
+        Ok(())
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum WireGuardDeletableProperties {
+    PersistentKeepalive,
+}
+
+/// Properties of a WireGuard node.
+#[api(
+    properties: {
+        interfaces: {
+            type: Array,
+            optional: true,
+            items: {
+                type: String,
+                description: "WireGuard interface",
+                format: &ApiStringFormat::PropertyString(&WireGuardInterfaceProperties::API_SCHEMA),
+            }
+        },
+        "listen-port": {
+            optional: true,
+            schema: PORT_SCHEMA,
+        },
+    }
+)]
+#[derive(Clone, Debug, Serialize, Deserialize, Updater, Hash)]
+#[serde(rename_all = "kebab-case")]
+pub struct WireGuardNodeProperties {
+    /// Interface properties for this node.
+    #[serde(default)]
+    pub(crate) interfaces: Vec<PropertyString<WireGuardInterfaceProperties>>,
+    /// Listen port for WireGuard on this node. Overrides the fabric-wide setting.
+    pub(crate) listen_port: Option<u16>,
+    // TODO: add public key to "pin" them in the section config?
+}
+
+impl WireGuardNodeProperties {
+    pub fn interfaces(&self) -> impl Iterator<Item = &WireGuardInterfaceProperties> {
+        self.interfaces
+            .iter()
+            .map(|property_string| property_string.deref())
+    }
+
+    /// Returns an iterator over all the interfaces (mutable).
+    pub fn interfaces_mut(&mut self) -> impl Iterator<Item = &mut WireGuardInterfaceProperties> {
+        self.interfaces
+            .iter_mut()
+            .map(|property_string| property_string.deref_mut())
+    }
+}
+
+impl Validatable for NodeSection<WireGuardNodeProperties> {
+    type Error = FabricConfigError;
+
+    /// Validates the [FabricSection<WireGuardNodeProperties>].
+    ///
+    /// Checks if we have either an IPv4 or an IPv6 address. If neither is set, return an error.
+    fn validate(&self) -> Result<(), Self::Error> {
+        if self.ip().is_none() && self.ip6().is_none() {
+            return Err(FabricConfigError::NodeNoIp(self.id().to_string()));
+        }
+
+        Ok(())
+    }
+}
+
+#[derive(Clone, Debug, Serialize, Deserialize)]
+#[serde(rename_all = "snake_case")]
+pub enum WireGuardNodeDeletableProperties {
+    Interfaces,
+    ListenPort,
+}
+
+/// Properties of a WireGuard interface.
+#[api]
+#[derive(Clone, Debug, Serialize, Deserialize, Updater, Hash)]
+pub struct WireGuardInterfaceProperties {
+    /// Name for this WireGuard interface.
+    pub(crate) name: InterfaceName,
+
+    /// If ip and ip6 are unset, then this is an point-to-point interface.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) ip: Option<Ipv4Cidr>,
+
+    /// If ip6 and ip are unset, then this is an point-to-point interface.
+    #[serde(skip_serializing_if = "Option::is_none")]
+    pub(crate) ip6: Option<Ipv6Cidr>,
+}
+
+impl WireGuardInterfaceProperties {
+    /// Get the name of the interface.
+    pub fn name(&self) -> &InterfaceName {
+        &self.name
+    }
+
+    /// Set the name of the interface.
+    pub fn set_name(&mut self, name: InterfaceName) {
+        self.name = name
+    }
+
+    /// Get the ip (IPv4) of the interface.
+    pub fn ip(&self) -> Option<&Ipv4Cidr> {
+        self.ip.as_ref()
+    }
+
+    /// Get the ip6 (IPv6) of the interface.
+    pub fn ip6(&self) -> Option<&Ipv6Cidr> {
+        self.ip6.as_ref()
+    }
+}
+
+/// Determines whether the given `PersistentKeepalive` value means that it is
+/// turned off. Useful for usage with serde's `skip_serializing_if`.
+fn persistent_keepalive_is_off(value: &Option<PersistentKeepalive>) -> bool {
+    value
+        .as_ref()
+        .map(PersistentKeepalive::is_off)
+        .unwrap_or(true)
+}
-- 
2.52.0



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel


      parent reply	other threads:[~2026-01-16 15:34 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-01-16 15:33 [pve-devel] [PATCH proxmox{, -ve-rs} 00/11] sdn: add wireguard fabric configuration support Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 01/11] serde: implement ini serializer Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 02/11] serde: add base64 module for byte arrays Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 03/11] network-types: add ServiceEndpoint type as host/port tuple abstraction Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 04/11] schema: provide integer schema for node ports Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 05/11] schema: api-types: add ed25519 base64 encoded key schema Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 06/11] wireguard: init configuration support crate Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 07/11] wireguard: implement api for PublicKey Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox 08/11] wireguard: make per-peer preshared key optional Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox-ve-rs 09/11] sdn-types: add wireguard-specific PersistentKeepalive api type Christoph Heiss
2026-01-16 15:33 ` [pve-devel] [PATCH proxmox-ve-rs 10/11] ve-config: fabric: refactor fabric config entry impl using macro Christoph Heiss
2026-01-16 15:33 ` Christoph Heiss [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260116153317.1146323-12-c.heiss@proxmox.com \
    --to=c.heiss@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal