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-ve-rs v2 05/27] ve-config: sdn: microseg: add carrier bridge section
Date: Thu,  9 Jul 2026 11:18:30 +0200	[thread overview]
Message-ID: <20260709091852.538885-6-h.laimer@proxmox.com> (raw)
In-Reply-To: <20260709091852.538885-1-h.laimer@proxmox.com>

A bridge section marks a bridge-facing interface as an identity carrier,
optionally restricted to a set of nodes. A carrier is only needed where
the underlay cannot transport the identity itself.

Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
---
 proxmox-ve-config/src/sdn/microseg/mod.rs | 98 +++++++++++++++++++++--
 1 file changed, 90 insertions(+), 8 deletions(-)

diff --git a/proxmox-ve-config/src/sdn/microseg/mod.rs b/proxmox-ve-config/src/sdn/microseg/mod.rs
index 8522a2c..8c38980 100644
--- a/proxmox-ve-config/src/sdn/microseg/mod.rs
+++ b/proxmox-ve-config/src/sdn/microseg/mod.rs
@@ -308,6 +308,36 @@ pub struct RegexAssignmentSection {
     pub(crate) comment: Option<String>,
 }
 
+#[api]
+#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
+/// A bridge-facing interface that carries the policy tag across hosts.
+pub struct BridgeSection {
+    pub(crate) id: MicrosegId,
+    /// Comma-separated list of nodes this bridge applies on (empty or absent means all nodes).
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub(crate) nodes: Option<String>,
+}
+
+impl BridgeSection {
+    /// Iterate the node-name list, trimming whitespace and skipping empty entries.
+    pub fn nodes(&self) -> impl Iterator<Item = &str> {
+        self.nodes
+            .as_deref()
+            .into_iter()
+            .flat_map(|s| s.split(','))
+            .map(str::trim)
+            .filter(|n| !n.is_empty())
+    }
+    /// Whether this bridge applies on `node`. Empty / absent `nodes` means all nodes.
+    pub fn applies_to(&self, node: &str) -> bool {
+        let mut iter = self.nodes();
+        match iter.next() {
+            None => true,
+            Some(first) => first == node || iter.any(|n| n == node),
+        }
+    }
+}
+
 #[api(
     "id-property": "id",
     "id-schema": {
@@ -331,6 +361,8 @@ pub enum MicrosegEntry {
     TagAssignment(TagAssignmentSection),
     /// A name-regex-matcher NIC-to-groups assignment.
     RegexAssignment(RegexAssignmentSection),
+    /// A bridge-facing carrier interface.
+    Bridge(BridgeSection),
 }
 
 impl MicrosegEntry {
@@ -342,6 +374,7 @@ impl MicrosegEntry {
             Self::GuestAssignment(a) => &a.id,
             Self::TagAssignment(a) => &a.id,
             Self::RegexAssignment(a) => &a.id,
+            Self::Bridge(b) => &b.id,
         }
     }
 }
@@ -433,6 +466,13 @@ impl MicrosegRunningConfig {
         })
     }
 
+    pub fn bridges(&self) -> impl Iterator<Item = (&str, &BridgeSection)> + '_ {
+        self.ids.iter().filter_map(|(k, v)| match v {
+            MicrosegEntry::Bridge(b) => Some((k.as_str(), b)),
+            _ => None,
+        })
+    }
+
     /// The rendered concrete per-NIC assignments (static plus selector-expanded) the agent enforces.
     pub fn realized(&self) -> &[RealizedAssignment] {
         &self.realized
@@ -526,7 +566,7 @@ pub fn validate(entries: &HashMap<String, MicrosegEntry>) -> Result<(), anyhow::
                 }
                 check_assignment_groups(name, &assignment.groups)?;
             }
-            MicrosegEntry::Group(_) => {}
+            MicrosegEntry::Group(_) | MicrosegEntry::Bridge(_) => {}
         }
     }
 
@@ -645,7 +685,7 @@ pub fn realized_assignments(
                     regex_matchers.push((re, assignment));
                 }
             }
-            MicrosegEntry::Group(_) | MicrosegEntry::Rule(_) => {}
+            MicrosegEntry::Group(_) | MicrosegEntry::Rule(_) | MicrosegEntry::Bridge(_) => {}
         }
     }
 
@@ -741,8 +781,7 @@ pub fn realized_per_assignment(
                         Err(_) => Vec::new(),
                     }
                 }
-
-                MicrosegEntry::Group(_) | MicrosegEntry::Rule(_) => {
+                MicrosegEntry::Group(_) | MicrosegEntry::Rule(_) | MicrosegEntry::Bridge(_) => {
                     return None;
                 }
             };
@@ -939,7 +978,7 @@ pub mod api {
     use serde::Deserialize;
 
     use super::{
-        GroupSection, GuestAssignmentSection, MicrosegEntry, MicrosegId,
+        BridgeSection, GroupSection, GuestAssignmentSection, MicrosegEntry, MicrosegId,
         PredicateMatch, RegexAssignmentSection, RuleSection, Tag, TagAssignmentSection,
     };
 
@@ -951,6 +990,7 @@ pub mod api {
         GuestAssignment(GuestAssignmentCreate),
         TagAssignment(TagAssignmentCreate),
         RegexAssignment(RegexAssignmentCreate),
+        Bridge(BridgeCreate),
     }
 
     #[derive(Debug, Clone, Deserialize)]
@@ -1018,6 +1058,13 @@ pub mod api {
         pub comment: Option<String>,
     }
 
+    #[derive(Debug, Clone, Deserialize)]
+    pub struct BridgeCreate {
+        pub id: MicrosegId,
+        #[serde(default)]
+        pub nodes: Option<String>,
+    }
+
     #[derive(Debug, Clone, Deserialize)]
     #[serde(tag = "type", rename_all = "lowercase")]
     pub enum MicrosegUpdate {
@@ -1026,6 +1073,7 @@ pub mod api {
         GuestAssignment(AssignmentUpdate),
         TagAssignment(AssignmentUpdate),
         RegexAssignment(AssignmentUpdate),
+        Bridge(BridgeUpdate),
     }
 
     #[derive(Debug, Clone, Default, Deserialize)]
@@ -1076,6 +1124,20 @@ pub mod api {
         Comment,
     }
 
+    #[derive(Debug, Clone, Default, Deserialize)]
+    pub struct BridgeUpdate {
+        #[serde(default)]
+        pub nodes: Option<String>,
+        #[serde(default)]
+        pub delete: Vec<BridgeDeletableProperty>,
+    }
+
+    #[derive(Debug, Clone, Copy, Deserialize)]
+    #[serde(rename_all = "lowercase")]
+    pub enum BridgeDeletableProperty {
+        Nodes,
+    }
+
     /// Lowest unused group mark in `1..=65535`.
     pub fn next_free_mark(entries: &HashMap<String, MicrosegEntry>) -> Option<u16> {
         let used: HashSet<u16> = entries
@@ -1263,6 +1325,10 @@ pub mod api {
                     comment: assignment.comment,
                 })
             }
+            MicrosegCreate::Bridge(bridge) => MicrosegEntry::Bridge(BridgeSection {
+                id: bridge.id,
+                nodes: bridge.nodes,
+            }),
         })
     }
 
@@ -1341,6 +1407,16 @@ pub mod api {
                     }
                 }
             }
+            (MicrosegEntry::Bridge(bridge), MicrosegUpdate::Bridge(update)) => {
+                if update.nodes.is_some() {
+                    bridge.nodes = update.nodes;
+                }
+                for property in update.delete {
+                    match property {
+                        BridgeDeletableProperty::Nodes => bridge.nodes = None,
+                    }
+                }
+            }
             _ => bail!("update type does not match the existing object's type"),
         }
         Ok(())
@@ -1407,7 +1483,7 @@ pub mod api {
                 };
                 (base, assignment.comment.as_deref())
             }
-            MicrosegEntry::Group(_) => ("object".to_string(), None),
+            MicrosegEntry::Group(_) | MicrosegEntry::Bridge(_) => ("object".to_string(), None),
         };
         match comment {
             Some(comment) => format!("{desc} (comment: \"{comment}\")"),
@@ -1437,7 +1513,7 @@ pub mod api {
                 MicrosegEntry::RegexAssignment(assignment) => {
                     assignment.groups.iter().any(|g| g.as_ref() == name)
                 }
-                MicrosegEntry::Group(_) => false,
+                MicrosegEntry::Group(_) | MicrosegEntry::Bridge(_) => false,
             })
             .map(describe_referrer)
             .collect();
@@ -1573,6 +1649,13 @@ mod tests {
                     comment: None,
                 }),
             ),
+            (
+                "vmbr0".to_string(),
+                MicrosegEntry::Bridge(BridgeSection {
+                    id: id("vmbr0"),
+                    nodes: Some("pve1,pve2".to_string()),
+                }),
+            ),
         ]);
 
         validate(&entries).expect("config is valid");
@@ -2148,7 +2231,6 @@ mod tests {
         assert!(realized_per_assignment(&entries, &inv)["sel"].is_empty());
     }
 
-
     #[test]
     fn render_with_inventory_allocates_tag_identities_and_round_trips() {
         use PredicateMatch::Any;
-- 
2.47.3





  parent reply	other threads:[~2026-07-09  9:20 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 ` Hannes Laimer [this message]
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 ` [PATCH proxmox-perl-rs v2 10/27] pve-rs: sdn: add microseg config binding Hannes Laimer
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-6-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