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
next prev 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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox