From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Cc: Wolfgang Bumiller <w.bumiller@proxmox.com>
Subject: [pve-devel] [PATCH proxmox-firewall v2 08/39] config: firewall: add types for ipsets
Date: Wed, 17 Apr 2024 15:53:33 +0200 [thread overview]
Message-ID: <20240417135404.573490-9-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20240417135404.573490-1-s.hanreich@proxmox.com>
Reviewed-by: Lukas Wagner <l.wagner@proxmox.com>
Reviewed-by: Max Carrara <m.carrara@proxmox.com>
Co-authored-by: Wolfgang Bumiller <w.bumiller@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
proxmox-ve-config/src/firewall/types/ipset.rs | 349 ++++++++++++++++++
proxmox-ve-config/src/firewall/types/mod.rs | 2 +
2 files changed, 351 insertions(+)
create mode 100644 proxmox-ve-config/src/firewall/types/ipset.rs
diff --git a/proxmox-ve-config/src/firewall/types/ipset.rs b/proxmox-ve-config/src/firewall/types/ipset.rs
new file mode 100644
index 0000000..c1af642
--- /dev/null
+++ b/proxmox-ve-config/src/firewall/types/ipset.rs
@@ -0,0 +1,349 @@
+use core::fmt::Display;
+use std::ops::{Deref, DerefMut};
+use std::str::FromStr;
+
+use anyhow::{bail, format_err, Error};
+use serde_with::DeserializeFromStr;
+
+use crate::firewall::parse::match_non_whitespace;
+use crate::firewall::types::address::Cidr;
+use crate::firewall::types::alias::AliasName;
+use crate::guest::vm::NetworkConfig;
+
+#[derive(Debug, Clone, Copy, Eq, PartialEq)]
+pub enum IpsetScope {
+ Datacenter,
+ Guest,
+}
+
+impl FromStr for IpsetScope {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ Ok(match s {
+ "+dc" => IpsetScope::Datacenter,
+ "+guest" => IpsetScope::Guest,
+ _ => bail!("invalid scope for ipset: {s}"),
+ })
+ }
+}
+
+impl Display for IpsetScope {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ let prefix = match self {
+ Self::Datacenter => "dc",
+ Self::Guest => "guest",
+ };
+
+ f.write_str(prefix)
+ }
+}
+
+#[derive(Debug, Clone, DeserializeFromStr)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct IpsetName {
+ pub scope: IpsetScope,
+ pub name: String,
+}
+
+impl IpsetName {
+ pub fn new(scope: IpsetScope, name: impl Into<String>) -> Self {
+ Self {
+ scope,
+ name: name.into(),
+ }
+ }
+
+ pub fn name(&self) -> &str {
+ &self.name
+ }
+
+ pub fn scope(&self) -> IpsetScope {
+ self.scope
+ }
+}
+
+impl FromStr for IpsetName {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s.split_once('/') {
+ Some((prefix, name)) if !name.is_empty() => Ok(Self {
+ scope: prefix.parse()?,
+ name: name.to_string(),
+ }),
+ _ => {
+ bail!("Invalid IPSet name: {s}")
+ }
+ }
+ }
+}
+
+impl Display for IpsetName {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}/{}", self.scope, self.name)
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub enum IpsetAddress {
+ Alias(AliasName),
+ Cidr(Cidr),
+}
+
+impl FromStr for IpsetAddress {
+ type Err = Error;
+
+ fn from_str(s: &str) -> Result<Self, Error> {
+ if let Ok(cidr) = s.parse() {
+ return Ok(IpsetAddress::Cidr(cidr));
+ }
+
+ if let Ok(name) = s.parse() {
+ return Ok(IpsetAddress::Alias(name));
+ }
+
+ bail!("Invalid address in IPSet: {s}")
+ }
+}
+
+impl<T: Into<Cidr>> From<T> for IpsetAddress {
+ fn from(cidr: T) -> Self {
+ IpsetAddress::Cidr(cidr.into())
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct IpsetEntry {
+ pub nomatch: bool,
+ pub address: IpsetAddress,
+ pub comment: Option<String>,
+}
+
+impl<T: Into<IpsetAddress>> From<T> for IpsetEntry {
+ fn from(value: T) -> Self {
+ Self {
+ nomatch: false,
+ address: value.into(),
+ comment: None,
+ }
+ }
+}
+
+impl FromStr for IpsetEntry {
+ type Err = Error;
+
+ fn from_str(line: &str) -> Result<Self, Error> {
+ let line = line.trim_start();
+
+ let (nomatch, line) = match line.strip_prefix('!') {
+ Some(line) => (true, line),
+ None => (false, line),
+ };
+
+ let (address, line) =
+ match_non_whitespace(line.trim_start()).ok_or_else(|| format_err!("missing value"))?;
+
+ let address: IpsetAddress = address.parse()?;
+ let line = line.trim_start();
+
+ let comment = match line.strip_prefix('#') {
+ Some(comment) => Some(comment.trim().to_string()),
+ None if !line.is_empty() => bail!("trailing characters in ipset entry: {line:?}"),
+ None => None,
+ };
+
+ Ok(Self {
+ nomatch,
+ address,
+ comment,
+ })
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct Ipfilter<'a> {
+ index: i64,
+ ipset: &'a Ipset,
+}
+
+impl Ipfilter<'_> {
+ pub fn index(&self) -> i64 {
+ self.index
+ }
+
+ pub fn ipset(&self) -> &Ipset {
+ self.ipset
+ }
+
+ pub fn name_for_index(index: i64) -> String {
+ format!("ipfilter-net{index}")
+ }
+}
+
+#[derive(Debug)]
+#[cfg_attr(test, derive(Eq, PartialEq))]
+pub struct Ipset {
+ pub name: IpsetName,
+ set: Vec<IpsetEntry>,
+ pub comment: Option<String>,
+}
+
+impl Ipset {
+ pub const fn new(name: IpsetName) -> Self {
+ Self {
+ name,
+ set: Vec::new(),
+ comment: None,
+ }
+ }
+
+ pub fn name(&self) -> &IpsetName {
+ &self.name
+ }
+
+ pub fn from_parts(scope: IpsetScope, name: impl Into<String>) -> Self {
+ Self::new(IpsetName::new(scope, name))
+ }
+
+ pub(crate) fn parse_entry(&mut self, line: &str) -> Result<(), Error> {
+ self.set.push(line.parse()?);
+ Ok(())
+ }
+
+ pub fn ipfilter(&self) -> Option<Ipfilter> {
+ if self.name.scope() != IpsetScope::Guest {
+ return None;
+ }
+
+ let name = self.name.name();
+
+ if let Some(key) = name.strip_prefix("ipfilter-") {
+ let id = NetworkConfig::index_from_net_key(key);
+
+ if let Ok(id) = id {
+ return Some(Ipfilter {
+ index: id,
+ ipset: self,
+ });
+ }
+ }
+
+ None
+ }
+}
+
+impl Deref for Ipset {
+ type Target = Vec<IpsetEntry>;
+
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ &self.set
+ }
+}
+
+impl DerefMut for Ipset {
+ #[inline]
+ fn deref_mut(&mut self) -> &mut Vec<IpsetEntry> {
+ &mut self.set
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn test_parse_ipset_name() {
+ for test_case in [
+ ("+dc/proxmox-123", IpsetScope::Datacenter, "proxmox-123"),
+ ("+guest/proxmox_123", IpsetScope::Guest, "proxmox_123"),
+ ] {
+ let ipset_name = test_case.0.parse::<IpsetName>().expect("valid ipset name");
+
+ assert_eq!(
+ ipset_name,
+ IpsetName {
+ scope: test_case.1,
+ name: test_case.2.to_string(),
+ }
+ )
+ }
+
+ for name in ["+dc/", "+guests/proxmox_123", "guest/proxmox_123"] {
+ name.parse::<IpsetName>().expect_err("invalid ipset name");
+ }
+ }
+
+ #[test]
+ fn test_parse_ipset_address() {
+ let mut ipset_address = "10.0.0.1"
+ .parse::<IpsetAddress>()
+ .expect("valid ipset address");
+ assert!(matches!(ipset_address, IpsetAddress::Cidr(Cidr::Ipv4(..))));
+
+ ipset_address = "fe80::1/64"
+ .parse::<IpsetAddress>()
+ .expect("valid ipset address");
+ assert!(matches!(ipset_address, IpsetAddress::Cidr(Cidr::Ipv6(..))));
+
+ ipset_address = "dc/proxmox-123"
+ .parse::<IpsetAddress>()
+ .expect("valid ipset address");
+ assert!(matches!(ipset_address, IpsetAddress::Alias(..)));
+
+ ipset_address = "guest/proxmox_123"
+ .parse::<IpsetAddress>()
+ .expect("valid ipset address");
+ assert!(matches!(ipset_address, IpsetAddress::Alias(..)));
+ }
+
+ #[test]
+ fn test_ipfilter() {
+ let mut ipset = Ipset::from_parts(IpsetScope::Guest, "ipfilter-net0");
+ ipset.ipfilter().expect("is an ipfilter");
+
+ ipset = Ipset::from_parts(IpsetScope::Guest, "ipfilter-qwe");
+ assert!(ipset.ipfilter().is_none());
+
+ ipset = Ipset::from_parts(IpsetScope::Guest, "proxmox");
+ assert!(ipset.ipfilter().is_none());
+
+ ipset = Ipset::from_parts(IpsetScope::Datacenter, "ipfilter-net0");
+ assert!(ipset.ipfilter().is_none());
+ }
+
+ #[test]
+ fn test_parse_ipset_entry() {
+ let mut entry = "!10.0.0.1 # qweqweasd"
+ .parse::<IpsetEntry>()
+ .expect("valid ipset entry");
+
+ assert_eq!(
+ entry,
+ IpsetEntry {
+ nomatch: true,
+ comment: Some("qweqweasd".to_string()),
+ address: IpsetAddress::Cidr(Cidr::new_v4([10, 0, 0, 1], 32).unwrap())
+ }
+ );
+
+ entry = "fe80::1/48"
+ .parse::<IpsetEntry>()
+ .expect("valid ipset entry");
+
+ assert_eq!(
+ entry,
+ IpsetEntry {
+ nomatch: false,
+ comment: None,
+ address: IpsetAddress::Cidr(
+ Cidr::new_v6([0xFE80, 0, 0, 0, 0, 0, 0, 1], 48).unwrap()
+ )
+ }
+ )
+ }
+}
diff --git a/proxmox-ve-config/src/firewall/types/mod.rs b/proxmox-ve-config/src/firewall/types/mod.rs
index 69b69f4..5833787 100644
--- a/proxmox-ve-config/src/firewall/types/mod.rs
+++ b/proxmox-ve-config/src/firewall/types/mod.rs
@@ -1,7 +1,9 @@
pub mod address;
pub mod alias;
+pub mod ipset;
pub mod log;
pub mod port;
pub use address::Cidr;
pub use alias::Alias;
+pub use ipset::Ipset;
--
2.39.2
_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel
next prev parent reply other threads:[~2024-04-17 13:56 UTC|newest]
Thread overview: 41+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-04-17 13:53 [pve-devel] [PATCH container/docs/firewall/manager/proxmox-firewall/qemu-server v2 00/39] proxmox firewall nftables implementation Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 01/39] config: add proxmox-ve-config crate Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 02/39] config: firewall: add types for ip addresses Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 03/39] config: firewall: add types for ports Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 04/39] config: firewall: add types for log level and rate limit Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 05/39] config: firewall: add types for aliases Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 06/39] config: host: add helpers for host network configuration Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 07/39] config: guest: add helpers for parsing guest network config Stefan Hanreich
2024-04-17 13:53 ` Stefan Hanreich [this message]
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 09/39] config: firewall: add types for rules Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 10/39] config: firewall: add types for security groups Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 11/39] config: firewall: add generic parser for firewall configs Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 12/39] config: firewall: add cluster-specific config + option types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 13/39] config: firewall: add host specific " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 14/39] config: firewall: add guest-specific " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 15/39] config: firewall: add firewall macros Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 16/39] config: firewall: add conntrack helper types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 17/39] nftables: add crate for libnftables bindings Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 18/39] nftables: add helpers Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 19/39] nftables: expression: add types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 20/39] nftables: expression: implement conversion traits for firewall config Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 21/39] nftables: statement: add types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 22/39] nftables: statement: add conversion traits for config types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 23/39] nftables: commands: add types Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 24/39] nftables: types: add conversion traits Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 25/39] nftables: add libnftables bindings Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 26/39] firewall: add firewall crate Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 27/39] firewall: add base ruleset Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 28/39] firewall: add config loader Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 29/39] firewall: add rule generation logic Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 30/39] firewall: add object " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 31/39] firewall: add ruleset " Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 32/39] firewall: add proxmox-firewall binary Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 33/39] firewall: add files for debian packaging Stefan Hanreich
2024-04-17 13:53 ` [pve-devel] [PATCH proxmox-firewall v2 34/39] firewall: add integration test Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH qemu-server v2 35/39] firewall: add handling for new nft firewall Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH pve-container v2 36/39] " Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH pve-firewall v2 37/39] add configuration option for new nftables firewall Stefan Hanreich
2024-04-18 21:06 ` Thomas Lamprecht
2024-04-17 13:54 ` [pve-devel] [PATCH pve-manager v2 38/39] firewall: expose " Stefan Hanreich
2024-04-17 13:54 ` [pve-devel] [PATCH pve-docs v2 39/39] firewall: add documentation for proxmox-firewall Stefan Hanreich
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=20240417135404.573490-9-s.hanreich@proxmox.com \
--to=s.hanreich@proxmox.com \
--cc=pve-devel@lists.proxmox.com \
--cc=w.bumiller@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