From: Hannes Laimer <h.laimer@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH proxmox-perl-rs v2 10/27] pve-rs: sdn: add microseg config binding
Date: Thu, 9 Jul 2026 11:18:35 +0200 [thread overview]
Message-ID: <20260709091852.538885-11-h.laimer@proxmox.com> (raw)
In-Reply-To: <20260709091852.538885-1-h.laimer@proxmox.com>
Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
pve-rs/Makefile | 1 +
pve-rs/src/bindings/sdn/microseg.rs | 235 ++++++++++++++++++++++++++++
pve-rs/src/bindings/sdn/mod.rs | 1 +
3 files changed, 237 insertions(+)
create mode 100644 pve-rs/src/bindings/sdn/microseg.rs
diff --git a/pve-rs/Makefile b/pve-rs/Makefile
index bb1cd2d..fc199ae 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 0000000..0e8f6d9
--- /dev/null
+++ b/pve-rs/src/bindings/sdn/microseg.rs
@@ -0,0 +1,235 @@
+#[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::{Error, anyhow, bail};
+ use openssl::hash::{MessageDigest, hash};
+
+ use perlmod::Value;
+
+ use proxmox_section_config::typed::{ApiSectionDataEntry, SectionConfigData};
+ use proxmox_ve_config::sdn::microseg::api::{MicrosegCreate, MicrosegUpdate};
+ use proxmox_ve_config::sdn::microseg::identity::Registry;
+ use proxmox_ve_config::sdn::microseg::{
+ self, AssignmentMember, GuestInfo, IdentityRules, MicrosegEntry, MicrosegRunningConfig,
+ RealizedAssignment,
+ };
+
+ /// A SDN Microseg config instance.
+ pub struct PerlMicrosegConfig {
+ pub microseg: Mutex<HashMap<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)?;
+
+ microseg::validate(config.deref())?;
+
+ Ok(
+ perlmod::instantiate_magic!(&class, MAGIC => Box::new(PerlMicrosegConfig {
+ microseg: Mutex::new(config.deref().clone()),
+ })),
+ )
+ }
+
+ /// Class method: Parse the configuration from `/etc/pve/sdn/.running-config`.
+ #[export]
+ pub fn running_config(
+ #[raw] class: Value,
+ entries: HashMap<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 (the current `microseg.cfg`) plus the
+ /// previous identity registry and the current guest inventory, keeping wire ids stable. Pass the
+ /// previous running config's `identities` (or `undef`/empty on the first apply) so the registry
+ /// is carried forward. Feeding a fresh registry would renumber the whole cluster. `inventory` is
+ /// the list of guests the selectors are expanded against. Returns the new running config (`ids`,
+ /// `identities`, `realized`).
+ #[export]
+ pub fn render(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ previous: Option<Registry>,
+ inventory: Vec<GuestInfo>,
+ ) -> Result<MicrosegRunningConfig, Error> {
+ let entries = this.microseg.lock().unwrap().deref().clone();
+ let previous = previous.unwrap_or_default();
+ // the render node's clock drives the id-reclamation quarantine, and a clock stuck at the
+ // epoch keeps quarantines closed, which is the safe direction
+ let now = std::time::SystemTime::now()
+ .duration_since(std::time::UNIX_EPOCH)
+ .map(|since| since.as_secs())
+ .unwrap_or(0);
+ microseg::render(&previous, entries, &inventory, now)
+ }
+
+ /// Method: Expand the selectors in this config against the guest `inventory` into the concrete
+ /// per-NIC assignments (merged with the static assignments), without allocating identities. Used
+ /// to detect selector-driven pending changes: compare the result against the running config's
+ /// stored `realized` list.
+ #[export]
+ pub fn realized(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ inventory: Vec<GuestInfo>,
+ ) -> Result<Vec<RealizedAssignment>, Error> {
+ let entries = this.microseg.lock().unwrap();
+ Ok(microseg::realized_assignments(entries.deref(), &inventory))
+ }
+
+ /// Method: Expand each assignment against the guest `inventory` into the guests/NICs it covers,
+ /// keyed by assignment id.
+ #[export]
+ pub fn realized_members(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ inventory: Vec<GuestInfo>,
+ ) -> Result<HashMap<String, Vec<AssignmentMember>>, Error> {
+ let entries = this.microseg.lock().unwrap();
+ Ok(microseg::realized_per_assignment(entries.deref(), &inventory))
+ }
+
+ /// Method: For each realized identity class (a distinct group-set carried by some NIC), the
+ /// rules that govern it as source (`outbound`) and destination (`inbound`), so callers need not
+ /// re-implement the match semantics.
+ #[export]
+ pub fn rules_by_identity(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ inventory: Vec<GuestInfo>,
+ ) -> Result<Vec<IdentityRules>, Error> {
+ let entries = this.microseg.lock().unwrap();
+ microseg::rules_by_identity(entries.deref(), &inventory)
+ }
+
+ /// Method: Used for writing the running configuration.
+ #[export]
+ pub fn to_sections(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ ) -> Result<HashMap<String, MicrosegEntry>, Error> {
+ let config = this.microseg.lock().unwrap();
+ microseg::validate(config.deref())?;
+ Ok(config.deref().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();
+ microseg::validate(config.deref())?;
+ // write sections in id order so the output, and the digest taken over it, stay stable
+ // across calls even though the config is stored in an unordered HashMap
+ let ordered: BTreeMap<String, MicrosegEntry> = config.deref().clone().into_iter().collect();
+ let data: SectionConfigData<MicrosegEntry> = SectionConfigData::from_iter(ordered);
+
+ 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 all microseg objects, keyed by id.
+ #[export]
+ pub fn list(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ ) -> Result<HashMap<String, MicrosegEntry>, Error> {
+ Ok(this.microseg.lock().unwrap().deref().clone())
+ }
+
+ /// Method: Returns a single microseg object.
+ #[export]
+ pub fn get(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ id: &str,
+ ) -> Result<Option<MicrosegEntry>, Error> {
+ Ok(this.microseg.lock().unwrap().get(id).cloned())
+ }
+
+ /// Method: Create a new microseg object. A group with no mark gets the lowest free one.
+ #[export]
+ pub fn create(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ create: MicrosegCreate,
+ ) -> Result<(), Error> {
+ let mut entries = this.microseg.lock().unwrap();
+
+ let entry = microseg::api::build_entry(create, &entries)?;
+ let id = entry.id().to_string();
+
+ if entries.contains_key(&id) {
+ bail!("microseg object '{id}' already exists");
+ }
+
+ entries.insert(id, entry);
+ microseg::validate(&entries)?;
+
+ Ok(())
+ }
+
+ /// Method: Update an existing microseg object.
+ #[export]
+ pub fn update(
+ #[try_from_ref] this: &PerlMicrosegConfig,
+ id: &str,
+ update: MicrosegUpdate,
+ ) -> Result<(), Error> {
+ let mut entries = this.microseg.lock().unwrap();
+
+ let mut entry = entries
+ .get(id)
+ .cloned()
+ .ok_or_else(|| anyhow!("microseg object '{id}' does not exist"))?;
+
+ microseg::api::apply_update(&mut entry, update)?;
+ entries.insert(id.to_string(), entry);
+ microseg::validate(&entries)?;
+
+ Ok(())
+ }
+
+ /// Method: Delete a microseg object. A group still referenced by a rule or assignment cannot
+ /// be removed.
+ #[export]
+ pub fn delete(#[try_from_ref] this: &PerlMicrosegConfig, id: &str) -> Result<(), Error> {
+ let mut entries = this.microseg.lock().unwrap();
+
+ if !entries.contains_key(id) {
+ bail!("microseg object '{id}' does not exist");
+ }
+
+ if matches!(entries.get(id), Some(MicrosegEntry::Group(_))) {
+ let referrers = microseg::api::group_referenced_by(&entries, id);
+ if !referrers.is_empty() {
+ bail!("group '{id}' is still referenced by {}", referrers.join(", "));
+ }
+ }
+
+ entries.remove(id);
+
+ Ok(())
+ }
+}
diff --git a/pve-rs/src/bindings/sdn/mod.rs b/pve-rs/src/bindings/sdn/mod.rs
index dcae046..1d4c23f 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
next prev parent reply other threads:[~2026-07-09 9:22 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-09 9:18 SPAM: [RFC cluster/docs/ifupdown2/manager/network/proxmox{-ve-rs,-ebpf,-perl-rs} v2 00/27] sdn: add microsegmentation support Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ve-rs v2 01/27] ve-config: sdn: add microseg signature-identity engine Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ve-rs v2 02/27] ve-config: sdn: add microseg config types Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ve-rs v2 03/27] ve-config: sdn: microseg: add tag matcher Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ve-rs v2 04/27] ve-config: sdn: microseg: add name regex matcher Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ve-rs v2 05/27] ve-config: sdn: microseg: add carrier bridge section Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ebpf v2 06/27] agent: add userspace coordinator and stateless policy subsystem Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ebpf v2 07/27] bpf: add bridge subsystem Hannes Laimer
2026-07-09 9:18 ` [PATCH proxmox-ebpf v2 08/27] debian: add packaging and boot-time oneshot unit Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-cluster v2 09/27] cfs: add 'sdn/microseg.cfg' to observed files Hannes Laimer
2026-07-09 9:18 ` Hannes Laimer [this message]
2026-07-09 9:18 ` [PATCH ifupdown2 v2 11/27] d/patches: add support for VXLAN-GBP flag Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 12/27] sdn: microseg: add config, API and guest inventory Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 13/27] sdn: dry-run: surface pending microseg changes Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 14/27] sdn: zones: trigger microseg apply on tap_plug Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 15/27] sdn: zones: add vxlan-gbp option to vxlan and evpn zones Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 16/27] evpn: disable vxlan-learning on create if GBP is enabled Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 17/27] sdn: microseg: add tag matcher Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 18/27] sdn: microseg: add name regex matcher Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-network v2 19/27] sdn: microseg: add carrier bridge API Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-manager v2 20/27] ui: sdn: add microsegmentation panel Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-manager v2 21/27] ui: sdn: dry-run: show pending microseg diff Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-manager v2 22/27] network: apply microseg state on reload Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-manager v2 23/27] ui: sdn: zones: add vxlan-gbp checkbox to vxlan and evpn Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-manager v2 24/27] ui: sdn: microseg: add tag matcher Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-manager v2 25/27] ui: sdn: microseg: add name regex matcher Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-docs v2 26/27] sdn: add microsegmentation section Hannes Laimer
2026-07-09 9:18 ` [PATCH pve-docs v2 27/27] sdn: add VXLAN-GBP flag to evpn/vxlan zone sections 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=20260709091852.538885-11-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.