all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Hannes Laimer <h.laimer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH proxmox-perl-rs v3 17/45] pve-rs: sdn: add microseg config binding
Date: Tue, 27 Oct 2026 09:54:59 +0100	[thread overview]
Message-ID: <20261027085527.702738-18-h.laimer@proxmox.com> (raw)
In-Reply-To: <20261027085527.702738-1-h.laimer@proxmox.com>

Expose the microseg config to Perl, shaped like the other SDN config
bindings. Which NICs an assignment wins is not stored, so the model
answers it for a guest inventory the caller passes in. Creating an
object returns its id, since rule and assignment ids are generated.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
 pve-rs/Makefile                     |   1 +
 pve-rs/src/bindings/sdn/microseg.rs | 243 ++++++++++++++++++++++++++++
 pve-rs/src/bindings/sdn/mod.rs      |   1 +
 3 files changed, 245 insertions(+)
 create mode 100644 pve-rs/src/bindings/sdn/microseg.rs

diff --git a/pve-rs/Makefile b/pve-rs/Makefile
index bb1cd2d5f73a..fc199ae5c9b8 100644
--- a/pve-rs/Makefile
+++ b/pve-rs/Makefile
@@ -33,6 +33,7 @@ PERLMOD_PACKAGES := \
 	  PVE::RS::ResourceScheduling::Static \
 	  PVE::RS::ResourceScheduling::Dynamic \
 	  PVE::RS::SDN::Fabrics \
+	  PVE::RS::SDN::Microseg \
 	  PVE::RS::SDN::PrefixLists \
 	  PVE::RS::SDN::RouteMaps \
 	  PVE::RS::SDN::WireGuard::PrivateKeys \
diff --git a/pve-rs/src/bindings/sdn/microseg.rs b/pve-rs/src/bindings/sdn/microseg.rs
new file mode 100644
index 000000000000..5deaf5189aec
--- /dev/null
+++ b/pve-rs/src/bindings/sdn/microseg.rs
@@ -0,0 +1,243 @@
+#[perlmod::package(name = "PVE::RS::SDN::Microseg", lib = "pve_rs")]
+pub mod pve_rs_sdn_microseg {
+    //! The `PVE::RS::SDN::Microseg` package.
+    //!
+    //! This provides the configuration for SDN microseg, as well as helper methods for reading
+    //! / writing the configuration.
+
+    use std::collections::{BTreeMap, HashMap};
+    use std::ops::Deref;
+    use std::sync::Mutex;
+
+    use anyhow::{Context, Error};
+    use openssl::hash::{MessageDigest, hash};
+
+    use perlmod::Value;
+    use serde::Serialize;
+
+    use proxmox_section_config::typed::{ApiSectionDataEntry, SectionConfigData};
+    use proxmox_ve_config::sdn::microseg::api::{MicrosegCreate, MicrosegUpdate};
+    use proxmox_ve_config::sdn::microseg::identity::{IdSpace, Registry};
+    use proxmox_ve_config::sdn::microseg::{
+        self, GroupSetRules, GuestInfo, MicrosegEntry, MicrosegId, MicrosegRunningConfig,
+    };
+
+    /// A SDN Microseg config instance.
+    pub struct PerlMicrosegConfig {
+        pub microseg: Mutex<BTreeMap<String, MicrosegEntry>>,
+    }
+
+    perlmod::declare_magic!(Box<PerlMicrosegConfig> : &PerlMicrosegConfig as "PVE::RS::SDN::Microseg::Config");
+
+    /// Class method: Parse the raw configuration from `/etc/pve/sdn/microseg.cfg`.
+    #[export]
+    pub fn config(#[raw] class: Value, raw_config: &[u8]) -> Result<perlmod::Value, Error> {
+        let raw_config = std::str::from_utf8(raw_config)?;
+        let config = MicrosegEntry::parse_section_config("microseg.cfg", raw_config)?;
+        let entries: BTreeMap<String, MicrosegEntry> = config.into_iter().collect();
+
+        microseg::validate(&entries).context("microseg config")?;
+
+        Ok(
+            perlmod::instantiate_magic!(&class, MAGIC => Box::new(PerlMicrosegConfig {
+                microseg: Mutex::new(entries),
+            })),
+        )
+    }
+
+    /// Class method: Parse the configuration from `/etc/pve/sdn/.running-config`.
+    #[export]
+    pub fn running_config(
+        #[raw] class: Value,
+        entries: BTreeMap<String, MicrosegEntry>,
+    ) -> Result<perlmod::Value, Error> {
+        microseg::validate(&entries)?;
+
+        Ok(
+            perlmod::instantiate_magic!(&class, MAGIC => Box::new(PerlMicrosegConfig {
+                microseg: Mutex::new(entries),
+            })),
+        )
+    }
+
+    /// Method: Render the running config from this config, keeping the ids of `previous`, the
+    /// `identities` of the last one (`undef` renumbers everything).
+    #[export]
+    pub fn render(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+        previous: Option<Registry>,
+    ) -> Result<MicrosegRunningConfig, Error> {
+        let entries = this.microseg.lock().unwrap().deref().clone();
+        let previous = previous.unwrap_or_default();
+        // the quarantine is only as reliable as the clocks of the nodes that render
+        let now = std::time::SystemTime::now()
+            .duration_since(std::time::UNIX_EPOCH)
+            .context("the system clock is set before 1970")?
+            .as_secs();
+        microseg::render(&previous, entries, now)
+    }
+
+    #[derive(Serialize)]
+    pub struct AssignmentMember {
+        vmid: u32,
+        nics: Vec<u32>,
+    }
+
+    /// Method: Returns the guests and NICs each assignment wins in `inventory`, keyed by assignment
+    /// id.
+    #[export]
+    pub fn assigned_members(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+        inventory: Vec<GuestInfo>,
+    ) -> HashMap<String, Vec<AssignmentMember>> {
+        let entries = this.microseg.lock().unwrap();
+        let mut members: HashMap<String, Vec<AssignmentMember>> = HashMap::new();
+        // sorted by guest, so a guest that already has an entry is the last one in the list
+        for nic in microseg::assigned_nics(entries.deref(), &inventory) {
+            let guests = members.entry(nic.assignment.to_string()).or_default();
+            match guests.last_mut() {
+                Some(member) if member.vmid == nic.vmid => member.nics.push(nic.iface),
+                _ => guests.push(AssignmentMember {
+                    vmid: nic.vmid,
+                    nics: vec![nic.iface],
+                }),
+            }
+        }
+        members
+    }
+
+    /// Method: Returns, for each group set an assignment names, the rules that fire on it.
+    #[export]
+    pub fn rules_by_group_set(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+    ) -> Result<Vec<GroupSetRules>, Error> {
+        let entries = this.microseg.lock().unwrap();
+        microseg::rules_by_group_set(entries.deref())
+    }
+
+    /// Method: Returns each rule's place in the evaluation order, by rule id.
+    #[export]
+    pub fn rule_order(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+    ) -> Result<HashMap<MicrosegId, usize>, Error> {
+        let entries = this.microseg.lock().unwrap();
+        microseg::rule_order(entries.deref())
+    }
+
+    /// Method: Returns each assignment's place in the match order, by id.
+    #[export]
+    pub fn assignment_order(#[try_from_ref] this: &PerlMicrosegConfig) -> HashMap<String, usize> {
+        let entries = this.microseg.lock().unwrap();
+        microseg::assignment_order(entries.deref())
+    }
+
+    /// Method: Whether a guest renamed from `old` to `new` enters or leaves the names a regex
+    /// assignment matches.
+    #[export]
+    pub fn name_match_changed(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+        old: Option<&str>,
+        new: Option<&str>,
+    ) -> bool {
+        let entries = this.microseg.lock().unwrap();
+        microseg::name_match_changed(entries.deref(), old, new)
+    }
+
+    /// Method: Returns how specific each rule is, by rule id.
+    #[export]
+    pub fn rule_specificity(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+    ) -> Result<HashMap<MicrosegId, usize>, Error> {
+        let entries = this.microseg.lock().unwrap();
+        microseg::rule_specificity(entries.deref())
+    }
+
+    /// How much of the id space the `identities` of a running config use.
+    #[export]
+    pub fn id_space(identities: Option<Registry>) -> IdSpace {
+        identities.unwrap_or_default().id_space()
+    }
+
+    /// The microseg block of a running config as text.
+    #[export]
+    pub fn summary(microseg: MicrosegRunningConfig, inventory: Vec<GuestInfo>) -> String {
+        microseg::api::summary(&microseg, &inventory)
+    }
+
+    /// Method: Returns all microseg objects, keyed by id.
+    #[export]
+    pub fn to_sections(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+    ) -> BTreeMap<String, MicrosegEntry> {
+        this.microseg.lock().unwrap().clone()
+    }
+
+    /// Method: Convert the configuration into the section config string.
+    ///
+    /// Used for writing `/etc/pve/sdn/microseg.cfg`
+    #[export]
+    pub fn to_raw(#[try_from_ref] this: &PerlMicrosegConfig) -> Result<String, Error> {
+        let config = this.microseg.lock().unwrap();
+        // id order keeps the output and its digest stable across calls
+        let data: SectionConfigData<MicrosegEntry> = config
+            .iter()
+            .map(|(id, entry)| (id.clone(), entry.clone()))
+            .collect();
+
+        MicrosegEntry::write_section_config("microseg.cfg", &data)
+    }
+
+    /// Method: Generate a digest for the whole configuration.
+    #[export]
+    pub fn digest(#[try_from_ref] this: &PerlMicrosegConfig) -> Result<String, Error> {
+        let raw = to_raw(this)?;
+        let digest = hash(MessageDigest::sha256(), raw.as_bytes())?;
+
+        Ok(hex::encode(digest))
+    }
+
+    /// Method: Returns a single microseg object.
+    #[export]
+    pub fn get(#[try_from_ref] this: &PerlMicrosegConfig, id: &str) -> Option<MicrosegEntry> {
+        this.microseg.lock().unwrap().get(id).cloned()
+    }
+
+    /// The model's edits validate after they change the map, so a failed one must not reach the
+    /// stored config.
+    fn change<T>(
+        this: &PerlMicrosegConfig,
+        change: impl FnOnce(&mut BTreeMap<String, MicrosegEntry>) -> Result<T, Error>,
+    ) -> Result<T, Error> {
+        let mut entries = this.microseg.lock().unwrap();
+        let mut candidate = entries.clone();
+        let result = change(&mut candidate)?;
+        *entries = candidate;
+
+        Ok(result)
+    }
+
+    /// Method: Create a new microseg object and return its id.
+    #[export]
+    pub fn create(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+        create: MicrosegCreate,
+    ) -> Result<String, Error> {
+        change(this, |entries| microseg::api::create(entries, create))
+    }
+
+    /// Method: Update an existing microseg object.
+    #[export]
+    pub fn update(
+        #[try_from_ref] this: &PerlMicrosegConfig,
+        id: &str,
+        update: MicrosegUpdate,
+    ) -> Result<(), Error> {
+        change(this, |entries| microseg::api::update(entries, id, update))
+    }
+
+    /// Method: Delete a microseg object.
+    #[export]
+    pub fn delete(#[try_from_ref] this: &PerlMicrosegConfig, id: &str) -> Result<(), Error> {
+        change(this, |entries| microseg::api::delete(entries, id))
+    }
+}
diff --git a/pve-rs/src/bindings/sdn/mod.rs b/pve-rs/src/bindings/sdn/mod.rs
index dcae046a7e46..1d4c23fb4258 100644
--- a/pve-rs/src/bindings/sdn/mod.rs
+++ b/pve-rs/src/bindings/sdn/mod.rs
@@ -1,4 +1,5 @@
 pub(crate) mod fabrics;
+pub(crate) mod microseg;
 pub(crate) mod prefix_lists;
 pub(crate) mod route_maps;
 pub(crate) mod wireguard;
-- 
2.47.3





  parent reply	other threads:[~2026-10-27  8:58 UTC|newest]

Thread overview: 46+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-27  8:54 [PATCH access-control/cluster/container/docs/manager/network/proxmox{-ebpf,-ve-rs,-perl-rs}/qemu-server v3 00/45] sdn: add microsegmentation support Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ebpf v3 01/45] build: keep the build directory out of the BPF objects Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ebpf v3 02/45] policy: add the enforcement subsystem for guest interfaces Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ebpf v3 03/45] policy: add a check for non-first fragments Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 04/45] ve-config: firewall: add a constructor for sctp matches Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 05/45] ve-config: sdn: microseg: add identity allocation for group sets Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 06/45] ve-config: sdn: microseg: add config types Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 07/45] ve-config: sdn: microseg: add tag matcher Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 08/45] ve-config: sdn: microseg: add name regex matcher Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 09/45] ve-config: sdn: microseg: narrow rules by protocol and ports Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 10/45] ve-config: sdn: microseg: add group hierarchy Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 11/45] ve-config: firewall: expose protocol, icmp type and code numbers Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 12/45] ve-config: sdn: microseg: add the desired state Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 13/45] microseg: add a crate that applies the policy to a node's guest NICs Hannes Laimer
2026-10-27  8:54 ` [PATCH proxmox-ve-rs v3 14/45] microseg: add a rule option that matches non-first fragments Hannes Laimer
2026-10-27  8:54 ` [PATCH pve-cluster v3 15/45] cfs: add 'sdn/microseg.cfg' to observed files Hannes Laimer
2026-10-27  8:54 ` [PATCH pve-access-control v3 16/45] permissions: add ACL path for SDN microsegmentation Hannes Laimer
2026-10-27  8:54 ` Hannes Laimer [this message]
2026-10-27  8:55 ` [PATCH proxmox-perl-rs v3 18/45] pve-rs: sdn: microseg: bind the data plane Hannes Laimer
2026-10-27  8:55 ` [PATCH proxmox-perl-rs v3 19/45] pve-rs: sdn: microseg: check the data plane limits on render Hannes Laimer
2026-10-27  8:55 ` [PATCH proxmox-perl-rs v3 20/45] pve-rs: debian: fire the pve-microseg-update trigger on upgrade Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 21/45] sdn: zones: add vxlan-gbp option to vxlan and evpn zones Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 22/45] sdn: microseg: add config, API and guest inventory Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 23/45] sdn: dry-run: show pending microseg changes Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 24/45] sdn: microseg: enforce the policy on guest NICs Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 25/45] sdn: microseg: add tag matcher Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 26/45] sdn: microseg: add name regex matcher Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 27/45] sdn: microseg: add an update for a running NIC Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 28/45] sdn: microseg: expose protocol, port and macro rule options Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 29/45] sdn: microseg: allow nesting groups under a parent Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-network v3 30/45] sdn: microseg: expose the fragments rule option Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 31/45] network: apply the microseg policy on reload Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 32/45] pve-sdn-commit: apply the microseg policy at boot Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 33/45] ui: sdn: zones: add vxlan-gbp checkbox to vxlan and evpn Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 34/45] ui: sdn: add microsegmentation panel Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 35/45] ui: sdn: dry-run: show pending microseg diff Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 36/45] ui: sdn: microseg: add tag matcher Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 37/45] ui: sdn: microseg: add name regex matcher Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 38/45] ui: sdn: microseg: add narrowing to the rule form Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 39/45] ui: sdn: microseg: show and edit the group hierarchy Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 40/45] ui: sdn: microseg: add fragments option to the rule form Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-manager v3 41/45] ui: sdn: microseg: add a guest tab with its groups and rules Hannes Laimer
2026-10-27  8:55 ` [PATCH qemu-server v3 42/45] network: update the microseg enforcement on a tag or name change Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-container v3 43/45] lxc: update the microseg enforcement on a tag or hostname change Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-docs v3 44/45] sdn: zones: document the VXLAN-GBP option Hannes Laimer
2026-10-27  8:55 ` [PATCH pve-docs v3 45/45] sdn: add microsegmentation section Hannes Laimer

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=20261027085527.702738-18-h.laimer@proxmox.com \
    --to=h.laimer@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal