From: Stefan Hanreich <s.hanreich@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [pve-devel] [PATCH proxmox-firewall v3 17/24] add proxmox-ve-rs crate - move proxmox-ve-config there
Date: Tue, 12 Nov 2024 13:25:55 +0100 [thread overview]
Message-ID: <20241112122602.88598-18-s.hanreich@proxmox.com> (raw)
In-Reply-To: <20241112122602.88598-1-s.hanreich@proxmox.com>
Signed-off-by: Stefan Hanreich <s.hanreich@proxmox.com>
---
Cargo.toml | 4 +-
Makefile | 2 +-
proxmox-firewall/Cargo.toml | 2 +-
proxmox-nftables/Cargo.toml | 2 +-
proxmox-ve-config/Cargo.toml | 25 -
proxmox-ve-config/resources/ct_helper.json | 52 -
proxmox-ve-config/resources/macros.json | 923 -----------------
proxmox-ve-config/src/firewall/cluster.rs | 374 -------
proxmox-ve-config/src/firewall/common.rs | 184 ----
proxmox-ve-config/src/firewall/ct_helper.rs | 115 ---
proxmox-ve-config/src/firewall/fw_macros.rs | 69 --
proxmox-ve-config/src/firewall/guest.rs | 237 -----
proxmox-ve-config/src/firewall/host.rs | 372 -------
proxmox-ve-config/src/firewall/mod.rs | 10 -
proxmox-ve-config/src/firewall/parse.rs | 494 ---------
proxmox-ve-config/src/firewall/ports.rs | 80 --
.../src/firewall/types/address.rs | 615 -----------
proxmox-ve-config/src/firewall/types/alias.rs | 174 ----
proxmox-ve-config/src/firewall/types/group.rs | 36 -
proxmox-ve-config/src/firewall/types/ipset.rs | 349 -------
proxmox-ve-config/src/firewall/types/log.rs | 222 ----
proxmox-ve-config/src/firewall/types/mod.rs | 14 -
proxmox-ve-config/src/firewall/types/port.rs | 181 ----
proxmox-ve-config/src/firewall/types/rule.rs | 412 --------
.../src/firewall/types/rule_match.rs | 977 ------------------
proxmox-ve-config/src/guest/mod.rs | 115 ---
proxmox-ve-config/src/guest/types.rs | 38 -
proxmox-ve-config/src/guest/vm.rs | 510 ---------
proxmox-ve-config/src/host/mod.rs | 1 -
proxmox-ve-config/src/host/utils.rs | 70 --
proxmox-ve-config/src/lib.rs | 3 -
31 files changed, 6 insertions(+), 6656 deletions(-)
delete mode 100644 proxmox-ve-config/Cargo.toml
delete mode 100644 proxmox-ve-config/resources/ct_helper.json
delete mode 100644 proxmox-ve-config/resources/macros.json
delete mode 100644 proxmox-ve-config/src/firewall/cluster.rs
delete mode 100644 proxmox-ve-config/src/firewall/common.rs
delete mode 100644 proxmox-ve-config/src/firewall/ct_helper.rs
delete mode 100644 proxmox-ve-config/src/firewall/fw_macros.rs
delete mode 100644 proxmox-ve-config/src/firewall/guest.rs
delete mode 100644 proxmox-ve-config/src/firewall/host.rs
delete mode 100644 proxmox-ve-config/src/firewall/mod.rs
delete mode 100644 proxmox-ve-config/src/firewall/parse.rs
delete mode 100644 proxmox-ve-config/src/firewall/ports.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/address.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/alias.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/group.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/ipset.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/log.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/mod.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/port.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/rule.rs
delete mode 100644 proxmox-ve-config/src/firewall/types/rule_match.rs
delete mode 100644 proxmox-ve-config/src/guest/mod.rs
delete mode 100644 proxmox-ve-config/src/guest/types.rs
delete mode 100644 proxmox-ve-config/src/guest/vm.rs
delete mode 100644 proxmox-ve-config/src/host/mod.rs
delete mode 100644 proxmox-ve-config/src/host/utils.rs
delete mode 100644 proxmox-ve-config/src/lib.rs
diff --git a/Cargo.toml b/Cargo.toml
index 1fbc2e0..3894d9f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,7 +1,9 @@
[workspace]
members = [
- "proxmox-ve-config",
"proxmox-nftables",
"proxmox-firewall",
]
resolver = "2"
+
+[workspace.dependencies]
+proxmox-ve-config = { version = "0.1.0" }
diff --git a/Makefile b/Makefile
index e49e58f..a134423 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ cargo-build:
build: $(BUILDDIR)
$(BUILDDIR):
rm -rf $@ $@.tmp; mkdir $@.tmp
- cp -a proxmox-firewall proxmox-nftables proxmox-ve-config debian Cargo.toml Makefile defines.mk $@.tmp/
+ cp -a proxmox-firewall proxmox-nftables debian Cargo.toml Makefile defines.mk $@.tmp/
mv $@.tmp $@
.PHONY: deb
diff --git a/proxmox-firewall/Cargo.toml b/proxmox-firewall/Cargo.toml
index 6cb1b09..c2adcac 100644
--- a/proxmox-firewall/Cargo.toml
+++ b/proxmox-firewall/Cargo.toml
@@ -21,7 +21,7 @@ serde_json = "1"
signal-hook = "0.3"
proxmox-nftables = { path = "../proxmox-nftables", features = ["config-ext"] }
-proxmox-ve-config = { path = "../proxmox-ve-config" }
+proxmox-ve-config = { workspace = true }
[dev-dependencies]
insta = { version = "1.21", features = ["json"] }
diff --git a/proxmox-nftables/Cargo.toml b/proxmox-nftables/Cargo.toml
index e84509d..4ff6f41 100644
--- a/proxmox-nftables/Cargo.toml
+++ b/proxmox-nftables/Cargo.toml
@@ -22,4 +22,4 @@ serde = { version = "1", features = [ "derive" ] }
serde_json = "1"
serde_plain = "1"
-proxmox-ve-config = { path = "../proxmox-ve-config", optional = true }
+proxmox-ve-config = { workspace = true, optional = true }
diff --git a/proxmox-ve-config/Cargo.toml b/proxmox-ve-config/Cargo.toml
deleted file mode 100644
index 0239c08..0000000
--- a/proxmox-ve-config/Cargo.toml
+++ /dev/null
@@ -1,25 +0,0 @@
-[package]
-name = "proxmox-ve-config"
-version = "0.1.0"
-edition = "2021"
-authors = [
- "Wolfgang Bumiller <w.bumiller@proxmox.com>",
- "Stefan Hanreich <s.hanreich@proxmox.com>",
- "Proxmox Support Team <support@proxmox.com>",
-]
-description = "Proxmox VE config parsing"
-license = "AGPL-3"
-
-[dependencies]
-log = "0.4"
-anyhow = "1"
-nix = "0.26"
-
-serde = { version = "1", features = [ "derive" ] }
-serde_json = "1"
-serde_plain = "1"
-serde_with = "3"
-
-proxmox-schema = "3.1.2"
-proxmox-sys = "0.6"
-proxmox-sortable-macro = "0.1.3"
diff --git a/proxmox-ve-config/resources/ct_helper.json b/proxmox-ve-config/resources/ct_helper.json
deleted file mode 100644
index 5e70a3a..0000000
--- a/proxmox-ve-config/resources/ct_helper.json
+++ /dev/null
@@ -1,52 +0,0 @@
-[
- {
- "name": "amanda",
- "v4": true,
- "v6": true,
- "udp": 10080
- },
- {
- "name": "ftp",
- "v4": true,
- "v6": true,
- "tcp": 21
- } ,
- {
- "name": "irc",
- "v4": true,
- "tcp": 6667
- },
- {
- "name": "netbios-ns",
- "v4": true,
- "udp": 137
- },
- {
- "name": "pptp",
- "v4": true,
- "tcp": 1723
- },
- {
- "name": "sane",
- "v4": true,
- "v6": true,
- "tcp": 6566
- },
- {
- "name": "sip",
- "v4": true,
- "v6": true,
- "udp": 5060
- },
- {
- "name": "snmp",
- "v4": true,
- "udp": 161
- },
- {
- "name": "tftp",
- "v4": true,
- "v6": true,
- "udp": 69
- }
-]
diff --git a/proxmox-ve-config/resources/macros.json b/proxmox-ve-config/resources/macros.json
deleted file mode 100644
index 2fcc0fb..0000000
--- a/proxmox-ve-config/resources/macros.json
+++ /dev/null
@@ -1,923 +0,0 @@
-{
- "Amanda": {
- "code": [
- {
- "dport": "10080",
- "proto": "udp"
- },
- {
- "dport": "10080",
- "proto": "tcp"
- }
- ],
- "desc": "Amanda Backup"
- },
- "Auth": {
- "code": [
- {
- "dport": "113",
- "proto": "tcp"
- }
- ],
- "desc": "Auth (identd) traffic"
- },
- "BGP": {
- "code": [
- {
- "dport": "179",
- "proto": "tcp"
- }
- ],
- "desc": "Border Gateway Protocol traffic"
- },
- "BitTorrent": {
- "code": [
- {
- "dport": "6881:6889",
- "proto": "tcp"
- },
- {
- "dport": "6881",
- "proto": "udp"
- }
- ],
- "desc": "BitTorrent traffic for BitTorrent 3.1 and earlier"
- },
- "BitTorrent32": {
- "code": [
- {
- "dport": "6881:6999",
- "proto": "tcp"
- },
- {
- "dport": "6881",
- "proto": "udp"
- }
- ],
- "desc": "BitTorrent traffic for BitTorrent 3.2 and later"
- },
- "CVS": {
- "code": [
- {
- "dport": "2401",
- "proto": "tcp"
- }
- ],
- "desc": "Concurrent Versions System pserver traffic"
- },
- "Ceph": {
- "code": [
- {
- "dport": "6789",
- "proto": "tcp"
- },
- {
- "dport": "3300",
- "proto": "tcp"
- },
- {
- "dport": "6800:7300",
- "proto": "tcp"
- }
- ],
- "desc": "Ceph Storage Cluster traffic (Ceph Monitors, OSD & MDS Daemons)"
- },
- "Citrix": {
- "code": [
- {
- "dport": "1494",
- "proto": "tcp"
- },
- {
- "dport": "1604",
- "proto": "udp"
- },
- {
- "dport": "2598",
- "proto": "tcp"
- }
- ],
- "desc": "Citrix/ICA traffic (ICA, ICA Browser, CGP)"
- },
- "DAAP": {
- "code": [
- {
- "dport": "3689",
- "proto": "tcp"
- },
- {
- "dport": "3689",
- "proto": "udp"
- }
- ],
- "desc": "Digital Audio Access Protocol traffic (iTunes, Rythmbox daemons)"
- },
- "DCC": {
- "code": [
- {
- "dport": "6277",
- "proto": "tcp"
- }
- ],
- "desc": "Distributed Checksum Clearinghouse spam filtering mechanism"
- },
- "DHCPfwd": {
- "code": [
- {
- "dport": "67:68",
- "proto": "udp",
- "sport": "67:68"
- }
- ],
- "desc": "Forwarded DHCP traffic"
- },
- "DHCPv6": {
- "code": [
- {
- "dport": "546:547",
- "proto": "udp",
- "sport": "546:547"
- }
- ],
- "desc": "DHCPv6 traffic"
- },
- "DNS": {
- "code": [
- {
- "dport": "53",
- "proto": "udp"
- },
- {
- "dport": "53",
- "proto": "tcp"
- }
- ],
- "desc": "Domain Name System traffic (upd and tcp)"
- },
- "Distcc": {
- "code": [
- {
- "dport": "3632",
- "proto": "tcp"
- }
- ],
- "desc": "Distributed Compiler service"
- },
- "FTP": {
- "code": [
- {
- "dport": "21",
- "proto": "tcp"
- }
- ],
- "desc": "File Transfer Protocol"
- },
- "Finger": {
- "code": [
- {
- "dport": "79",
- "proto": "tcp"
- }
- ],
- "desc": "Finger protocol (RFC 742)"
- },
- "GNUnet": {
- "code": [
- {
- "dport": "2086",
- "proto": "tcp"
- },
- {
- "dport": "2086",
- "proto": "udp"
- },
- {
- "dport": "1080",
- "proto": "tcp"
- },
- {
- "dport": "1080",
- "proto": "udp"
- }
- ],
- "desc": "GNUnet secure peer-to-peer networking traffic"
- },
- "GRE": {
- "code": [
- {
- "proto": "47"
- }
- ],
- "desc": "Generic Routing Encapsulation tunneling protocol"
- },
- "Git": {
- "code": [
- {
- "dport": "9418",
- "proto": "tcp"
- }
- ],
- "desc": "Git distributed revision control traffic"
- },
- "HKP": {
- "code": [
- {
- "dport": "11371",
- "proto": "tcp"
- }
- ],
- "desc": "OpenPGP HTTP key server protocol traffic"
- },
- "HTTP": {
- "code": [
- {
- "dport": "80",
- "proto": "tcp"
- }
- ],
- "desc": "Hypertext Transfer Protocol (WWW)"
- },
- "HTTPS": {
- "code": [
- {
- "dport": "443",
- "proto": "tcp"
- }
- ],
- "desc": "Hypertext Transfer Protocol (WWW) over SSL"
- },
- "HTTP/3": {
- "code": [
- {
- "dport": "443",
- "proto": "udp"
- }
- ],
- "desc": "Hypertext Transfer Protocol v3"
- },
- "ICPV2": {
- "code": [
- {
- "dport": "3130",
- "proto": "udp"
- }
- ],
- "desc": "Internet Cache Protocol V2 (Squid) traffic"
- },
- "ICQ": {
- "code": [
- {
- "dport": "5190",
- "proto": "tcp"
- }
- ],
- "desc": "AOL Instant Messenger traffic"
- },
- "IMAP": {
- "code": [
- {
- "dport": "143",
- "proto": "tcp"
- }
- ],
- "desc": "Internet Message Access Protocol"
- },
- "IMAPS": {
- "code": [
- {
- "dport": "993",
- "proto": "tcp"
- }
- ],
- "desc": "Internet Message Access Protocol over SSL"
- },
- "IPIP": {
- "code": [
- {
- "proto": "94"
- }
- ],
- "desc": "IPIP capsulation traffic"
- },
- "IPsec": {
- "code": [
- {
- "dport": "500",
- "proto": "udp",
- "sport": "500"
- },
- {
- "proto": "50"
- }
- ],
- "desc": "IPsec traffic"
- },
- "IPsecah": {
- "code": [
- {
- "dport": "500",
- "proto": "udp",
- "sport": "500"
- },
- {
- "proto": "51"
- }
- ],
- "desc": "IPsec authentication (AH) traffic"
- },
- "IPsecnat": {
- "code": [
- {
- "dport": "500",
- "proto": "udp"
- },
- {
- "dport": "4500",
- "proto": "udp"
- },
- {
- "proto": "50"
- }
- ],
- "desc": "IPsec traffic and Nat-Traversal"
- },
- "IRC": {
- "code": [
- {
- "dport": "6667",
- "proto": "tcp"
- }
- ],
- "desc": "Internet Relay Chat traffic"
- },
- "Jetdirect": {
- "code": [
- {
- "dport": "9100",
- "proto": "tcp"
- }
- ],
- "desc": "HP Jetdirect printing"
- },
- "L2TP": {
- "code": [
- {
- "dport": "1701",
- "proto": "udp"
- }
- ],
- "desc": "Layer 2 Tunneling Protocol traffic"
- },
- "LDAP": {
- "code": [
- {
- "dport": "389",
- "proto": "tcp"
- }
- ],
- "desc": "Lightweight Directory Access Protocol traffic"
- },
- "LDAPS": {
- "code": [
- {
- "dport": "636",
- "proto": "tcp"
- }
- ],
- "desc": "Secure Lightweight Directory Access Protocol traffic"
- },
- "MDNS": {
- "code": [
- {
- "dport": "5353",
- "proto": "udp"
- }
- ],
- "desc": "Multicast DNS"
- },
- "MSNP": {
- "code": [
- {
- "dport": "1863",
- "proto": "tcp"
- }
- ],
- "desc": "Microsoft Notification Protocol"
- },
- "MSSQL": {
- "code": [
- {
- "dport": "1433",
- "proto": "tcp"
- }
- ],
- "desc": "Microsoft SQL Server"
- },
- "Mail": {
- "code": [
- {
- "dport": "25",
- "proto": "tcp"
- },
- {
- "dport": "465",
- "proto": "tcp"
- },
- {
- "dport": "587",
- "proto": "tcp"
- }
- ],
- "desc": "Mail traffic (SMTP, SMTPS, Submission)"
- },
- "Munin": {
- "code": [
- {
- "dport": "4949",
- "proto": "tcp"
- }
- ],
- "desc": "Munin networked resource monitoring traffic"
- },
- "MySQL": {
- "code": [
- {
- "dport": "3306",
- "proto": "tcp"
- }
- ],
- "desc": "MySQL server"
- },
- "NNTP": {
- "code": [
- {
- "dport": "119",
- "proto": "tcp"
- }
- ],
- "desc": "NNTP traffic (Usenet)."
- },
- "NNTPS": {
- "code": [
- {
- "dport": "563",
- "proto": "tcp"
- }
- ],
- "desc": "Encrypted NNTP traffic (Usenet)"
- },
- "NTP": {
- "code": [
- {
- "dport": "123",
- "proto": "udp"
- }
- ],
- "desc": "Network Time Protocol (ntpd)"
- },
- "NeighborDiscovery": {
- "code": [
- {
- "dport": "nd-router-solicit",
- "proto": "icmpv6"
- },
- {
- "dport": "nd-router-advert",
- "proto": "icmpv6"
- },
- {
- "dport": "nd-neighbor-solicit",
- "proto": "icmpv6"
- },
- {
- "dport": "nd-neighbor-advert",
- "proto": "icmpv6"
- }
- ],
- "desc": "IPv6 neighbor solicitation, neighbor and router advertisement"
- },
- "OSPF": {
- "code": [
- {
- "proto": "89"
- }
- ],
- "desc": "OSPF multicast traffic"
- },
- "OpenVPN": {
- "code": [
- {
- "dport": "1194",
- "proto": "udp"
- }
- ],
- "desc": "OpenVPN traffic"
- },
- "PBS": {
- "code": [
- {
- "dport": "8007",
- "proto": "tcp"
- }
- ],
- "desc": "Proxmox Backup Server"
- },
- "PCA": {
- "code": [
- {
- "dport": "5632",
- "proto": "udp"
- },
- {
- "dport": "5631",
- "proto": "tcp"
- }
- ],
- "desc": "Symantec PCAnywere (tm)"
- },
- "PMG": {
- "code": [
- {
- "dport": "8006",
- "proto": "tcp"
- }
- ],
- "desc": "Proxmox Mail Gateway web interface"
- },
- "POP3": {
- "code": [
- {
- "dport": "110",
- "proto": "tcp"
- }
- ],
- "desc": "POP3 traffic"
- },
- "POP3S": {
- "code": [
- {
- "dport": "995",
- "proto": "tcp"
- }
- ],
- "desc": "Encrypted POP3 traffic"
- },
- "PPtP": {
- "code": [
- {
- "proto": "47"
- },
- {
- "dport": "1723",
- "proto": "tcp"
- }
- ],
- "desc": "Point-to-Point Tunneling Protocol"
- },
- "Ping": {
- "code": [
- {
- "dport": "echo-request",
- "proto": "icmp"
- }
- ],
- "desc": "ICMP echo request"
- },
- "PostgreSQL": {
- "code": [
- {
- "dport": "5432",
- "proto": "tcp"
- }
- ],
- "desc": "PostgreSQL server"
- },
- "Printer": {
- "code": [
- {
- "dport": "515",
- "proto": "tcp"
- }
- ],
- "desc": "Line Printer protocol printing"
- },
- "RDP": {
- "code": [
- {
- "dport": "3389",
- "proto": "tcp"
- }
- ],
- "desc": "Microsoft Remote Desktop Protocol traffic"
- },
- "RIP": {
- "code": [
- {
- "dport": "520",
- "proto": "udp"
- }
- ],
- "desc": "Routing Information Protocol (bidirectional)"
- },
- "RNDC": {
- "code": [
- {
- "dport": "953",
- "proto": "tcp"
- }
- ],
- "desc": "BIND remote management protocol"
- },
- "Razor": {
- "code": [
- {
- "dport": "2703",
- "proto": "tcp"
- }
- ],
- "desc": "Razor Antispam System"
- },
- "Rdate": {
- "code": [
- {
- "dport": "37",
- "proto": "tcp"
- }
- ],
- "desc": "Remote time retrieval (rdate)"
- },
- "Rsync": {
- "code": [
- {
- "dport": "873",
- "proto": "tcp"
- }
- ],
- "desc": "Rsync server"
- },
- "SANE": {
- "code": [
- {
- "dport": "6566",
- "proto": "tcp"
- }
- ],
- "desc": "SANE network scanning"
- },
- "SMB": {
- "code": [
- {
- "dport": "135,445",
- "proto": "udp"
- },
- {
- "dport": "137:139",
- "proto": "udp"
- },
- {
- "dport": "1024:65535",
- "proto": "udp",
- "sport": "137"
- },
- {
- "dport": "135,139,445",
- "proto": "tcp"
- }
- ],
- "desc": "Microsoft SMB traffic"
- },
- "SMBswat": {
- "code": [
- {
- "dport": "901",
- "proto": "tcp"
- }
- ],
- "desc": "Samba Web Administration Tool"
- },
- "SMTP": {
- "code": [
- {
- "dport": "25",
- "proto": "tcp"
- }
- ],
- "desc": "Simple Mail Transfer Protocol"
- },
- "SMTPS": {
- "code": [
- {
- "dport": "465",
- "proto": "tcp"
- }
- ],
- "desc": "Encrypted Simple Mail Transfer Protocol"
- },
- "SNMP": {
- "code": [
- {
- "dport": "161:162",
- "proto": "udp"
- },
- {
- "dport": "161",
- "proto": "tcp"
- }
- ],
- "desc": "Simple Network Management Protocol"
- },
- "SPAMD": {
- "code": [
- {
- "dport": "783",
- "proto": "tcp"
- }
- ],
- "desc": "Spam Assassin SPAMD traffic"
- },
- "SPICEproxy": {
- "code": [
- {
- "dport": "3128",
- "proto": "tcp"
- }
- ],
- "desc": "Proxmox VE SPICE display proxy traffic"
- },
- "SSH": {
- "code": [
- {
- "dport": "22",
- "proto": "tcp"
- }
- ],
- "desc": "Secure shell traffic"
- },
- "SVN": {
- "code": [
- {
- "dport": "3690",
- "proto": "tcp"
- }
- ],
- "desc": "Subversion server (svnserve)"
- },
- "SixXS": {
- "code": [
- {
- "dport": "3874",
- "proto": "tcp"
- },
- {
- "dport": "3740",
- "proto": "udp"
- },
- {
- "proto": "41"
- },
- {
- "dport": "5072,8374",
- "proto": "udp"
- }
- ],
- "desc": "SixXS IPv6 Deployment and Tunnel Broker"
- },
- "Squid": {
- "code": [
- {
- "dport": "3128",
- "proto": "tcp"
- }
- ],
- "desc": "Squid web proxy traffic"
- },
- "Submission": {
- "code": [
- {
- "dport": "587",
- "proto": "tcp"
- }
- ],
- "desc": "Mail message submission traffic"
- },
- "Syslog": {
- "code": [
- {
- "dport": "514",
- "proto": "udp"
- },
- {
- "dport": "514",
- "proto": "tcp"
- }
- ],
- "desc": "Syslog protocol (RFC 5424) traffic"
- },
- "TFTP": {
- "code": [
- {
- "dport": "69",
- "proto": "udp"
- }
- ],
- "desc": "Trivial File Transfer Protocol traffic"
- },
- "Telnet": {
- "code": [
- {
- "dport": "23",
- "proto": "tcp"
- }
- ],
- "desc": "Telnet traffic"
- },
- "Telnets": {
- "code": [
- {
- "dport": "992",
- "proto": "tcp"
- }
- ],
- "desc": "Telnet over SSL"
- },
- "Time": {
- "code": [
- {
- "dport": "37",
- "proto": "tcp"
- }
- ],
- "desc": "RFC 868 Time protocol"
- },
- "Trcrt": {
- "code": [
- {
- "dport": "33434:33524",
- "proto": "udp"
- },
- {
- "dport": "echo-request",
- "proto": "icmp"
- }
- ],
- "desc": "Traceroute (for up to 30 hops) traffic"
- },
- "VNC": {
- "code": [
- {
- "dport": "5900:5999",
- "proto": "tcp"
- }
- ],
- "desc": "VNC traffic for VNC display's 0 - 99"
- },
- "VNCL": {
- "code": [
- {
- "dport": "5500",
- "proto": "tcp"
- }
- ],
- "desc": "VNC traffic from Vncservers to Vncviewers in listen mode"
- },
- "Web": {
- "code": [
- {
- "dport": "80",
- "proto": "tcp"
- },
- {
- "dport": "443",
- "proto": "tcp"
- }
- ],
- "desc": "WWW traffic (HTTP and HTTPS)"
- },
- "Webcache": {
- "code": [
- {
- "dport": "8080",
- "proto": "tcp"
- }
- ],
- "desc": "Web Cache/Proxy traffic (port 8080)"
- },
- "Webmin": {
- "code": [
- {
- "dport": "10000",
- "proto": "tcp"
- }
- ],
- "desc": "Webmin traffic"
- },
- "Whois": {
- "code": [
- {
- "dport": "43",
- "proto": "tcp"
- }
- ],
- "desc": "Whois (nicname, RFC 3912) traffic"
- }
-}
diff --git a/proxmox-ve-config/src/firewall/cluster.rs b/proxmox-ve-config/src/firewall/cluster.rs
deleted file mode 100644
index 223124b..0000000
--- a/proxmox-ve-config/src/firewall/cluster.rs
+++ /dev/null
@@ -1,374 +0,0 @@
-use std::collections::BTreeMap;
-use std::io;
-
-use anyhow::Error;
-use serde::Deserialize;
-
-use crate::firewall::common::ParserConfig;
-use crate::firewall::types::ipset::{Ipset, IpsetScope};
-use crate::firewall::types::log::LogRateLimit;
-use crate::firewall::types::rule::{Direction, Verdict};
-use crate::firewall::types::{Alias, Group, Rule};
-
-use crate::firewall::parse::{serde_option_bool, serde_option_log_ratelimit};
-
-#[derive(Debug, Default)]
-pub struct Config {
- pub(crate) config: super::common::Config<Options>,
-}
-
-/// default setting for [`Config::is_enabled()`]
-pub const CLUSTER_ENABLED_DEFAULT: bool = false;
-/// default setting for [`Config::ebtables()`]
-pub const CLUSTER_EBTABLES_DEFAULT: bool = false;
-/// default setting for [`Config::default_policy()`]
-pub const CLUSTER_POLICY_IN_DEFAULT: Verdict = Verdict::Drop;
-/// default setting for [`Config::default_policy()`]
-pub const CLUSTER_POLICY_OUT_DEFAULT: Verdict = Verdict::Accept;
-
-impl Config {
- pub fn parse<R: io::BufRead>(input: R) -> Result<Self, Error> {
- let parser_config = ParserConfig {
- guest_iface_names: false,
- ipset_scope: Some(IpsetScope::Datacenter),
- };
-
- Ok(Self {
- config: super::common::Config::parse(input, &parser_config)?,
- })
- }
-
- pub fn rules(&self) -> &Vec<Rule> {
- &self.config.rules
- }
-
- pub fn groups(&self) -> &BTreeMap<String, Group> {
- &self.config.groups
- }
-
- pub fn ipsets(&self) -> &BTreeMap<String, Ipset> {
- &self.config.ipsets
- }
-
- pub fn alias(&self, name: &str) -> Option<&Alias> {
- self.config.alias(name)
- }
-
- pub fn is_enabled(&self) -> bool {
- self.config
- .options
- .enable
- .unwrap_or(CLUSTER_ENABLED_DEFAULT)
- }
-
- /// returns the ebtables option from the cluster config or [`CLUSTER_EBTABLES_DEFAULT`] if
- /// unset
- ///
- /// this setting is leftover from the old firewall, but has no effect on the nftables firewall
- pub fn ebtables(&self) -> bool {
- self.config
- .options
- .ebtables
- .unwrap_or(CLUSTER_EBTABLES_DEFAULT)
- }
-
- /// returns policy_in / out or [`CLUSTER_POLICY_IN_DEFAULT`] / [`CLUSTER_POLICY_OUT_DEFAULT`] if
- /// unset
- pub fn default_policy(&self, dir: Direction) -> Verdict {
- match dir {
- Direction::In => self
- .config
- .options
- .policy_in
- .unwrap_or(CLUSTER_POLICY_IN_DEFAULT),
- Direction::Out => self
- .config
- .options
- .policy_out
- .unwrap_or(CLUSTER_POLICY_OUT_DEFAULT),
- }
- }
-
- /// returns the rate_limit for logs or [`None`] if rate limiting is disabled
- ///
- /// If there is no rate limit set, then [`LogRateLimit::default`] is used
- pub fn log_ratelimit(&self) -> Option<LogRateLimit> {
- let rate_limit = self
- .config
- .options
- .log_ratelimit
- .clone()
- .unwrap_or_default();
-
- match rate_limit.enabled() {
- true => Some(rate_limit),
- false => None,
- }
- }
-}
-
-#[derive(Debug, Default, Deserialize)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Options {
- #[serde(default, with = "serde_option_bool")]
- enable: Option<bool>,
-
- #[serde(default, with = "serde_option_bool")]
- ebtables: Option<bool>,
-
- #[serde(default, with = "serde_option_log_ratelimit")]
- log_ratelimit: Option<LogRateLimit>,
-
- policy_in: Option<Verdict>,
- policy_out: Option<Verdict>,
-}
-
-#[cfg(test)]
-mod tests {
- use crate::firewall::types::{
- address::IpList,
- alias::{AliasName, AliasScope},
- ipset::{IpsetAddress, IpsetEntry},
- log::{LogLevel, LogRateLimitTimescale},
- rule::{Kind, RuleGroup},
- rule_match::{
- Icmpv6, Icmpv6Code, IpAddrMatch, IpMatch, Ports, Protocol, RuleMatch, Tcp, Udp,
- },
- Cidr,
- };
-
- use super::*;
-
- #[test]
- fn test_parse_config() {
- const CONFIG: &str = r#"
-[OPTIONS]
-enable: 1
-log_ratelimit: 1,rate=10/second,burst=20
-ebtables: 0
-policy_in: REJECT
-policy_out: REJECT
-
-[ALIASES]
-
-another 8.8.8.18
-analias 7.7.0.0/16 # much
-wide cccc::/64
-
-[IPSET a-set]
-
-!5.5.5.5
-1.2.3.4/30
-dc/analias # a comment
-dc/wide
-dddd::/96
-
-[RULES]
-
-GROUP tgr -i eth0 # acomm
-IN ACCEPT -p udp -dport 33 -sport 22 -log warning
-
-[group tgr] # comment for tgr
-
-|OUT ACCEPT -source fe80::1/48 -dest dddd:3:3::9/64 -p icmpv6 -log nolog -icmp-type port-unreachable
-OUT ACCEPT -p tcp -sport 33 -log nolog
-IN BGP(REJECT) -log crit -source 1.2.3.4
-"#;
-
- let mut config = CONFIG.as_bytes();
- let config = Config::parse(&mut config).unwrap();
-
- assert_eq!(
- config.config.options,
- Options {
- ebtables: Some(false),
- enable: Some(true),
- log_ratelimit: Some(LogRateLimit::new(
- true,
- 10,
- LogRateLimitTimescale::Second,
- 20
- )),
- policy_in: Some(Verdict::Reject),
- policy_out: Some(Verdict::Reject),
- }
- );
-
- assert_eq!(config.config.aliases.len(), 3);
-
- assert_eq!(
- config.config.aliases["another"],
- Alias::new("another", Cidr::new_v4([8, 8, 8, 18], 32).unwrap(), None),
- );
-
- assert_eq!(
- config.config.aliases["analias"],
- Alias::new(
- "analias",
- Cidr::new_v4([7, 7, 0, 0], 16).unwrap(),
- "much".to_string()
- ),
- );
-
- assert_eq!(
- config.config.aliases["wide"],
- Alias::new(
- "wide",
- Cidr::new_v6(
- [0xCCCC, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x000],
- 64
- )
- .unwrap(),
- None
- ),
- );
-
- assert_eq!(config.config.ipsets.len(), 1);
-
- let mut ipset_elements = vec![
- IpsetEntry {
- nomatch: true,
- address: Cidr::new_v4([5, 5, 5, 5], 32).unwrap().into(),
- comment: None,
- },
- IpsetEntry {
- nomatch: false,
- address: Cidr::new_v4([1, 2, 3, 4], 30).unwrap().into(),
- comment: None,
- },
- IpsetEntry {
- nomatch: false,
- address: IpsetAddress::Alias(AliasName::new(AliasScope::Datacenter, "analias")),
- comment: Some("a comment".to_string()),
- },
- IpsetEntry {
- nomatch: false,
- address: IpsetAddress::Alias(AliasName::new(AliasScope::Datacenter, "wide")),
- comment: None,
- },
- IpsetEntry {
- nomatch: false,
- address: Cidr::new_v6([0xdd, 0xdd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 96)
- .unwrap()
- .into(),
- comment: None,
- },
- ];
-
- let mut ipset = Ipset::from_parts(IpsetScope::Datacenter, "a-set");
- ipset.append(&mut ipset_elements);
-
- assert_eq!(config.config.ipsets["a-set"], ipset,);
-
- assert_eq!(config.config.rules.len(), 2);
-
- assert_eq!(
- config.config.rules[0],
- Rule {
- disabled: false,
- comment: Some("acomm".to_string()),
- kind: Kind::Group(RuleGroup {
- group: "tgr".to_string(),
- iface: Some("eth0".to_string()),
- }),
- },
- );
-
- assert_eq!(
- config.config.rules[1],
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Accept,
- proto: Some(Protocol::Udp(Udp::new(Ports::from_u16(22, 33)))),
- log: Some(LogLevel::Warning),
- ..Default::default()
- }),
- },
- );
-
- assert_eq!(config.config.groups.len(), 1);
-
- let entry = &config.config.groups["tgr"];
- assert_eq!(entry.comment(), Some("comment for tgr"));
- assert_eq!(entry.rules().len(), 3);
-
- assert_eq!(
- entry.rules()[0],
- Rule {
- disabled: true,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::Out,
- verdict: Verdict::Accept,
- ip: Some(IpMatch {
- src: Some(IpAddrMatch::Ip(IpList::from(
- Cidr::new_v6(
- [0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
- 48
- )
- .unwrap()
- ))),
- dst: Some(IpAddrMatch::Ip(IpList::from(
- Cidr::new_v6(
- [0xdd, 0xdd, 0, 3, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9],
- 64
- )
- .unwrap()
- ))),
- }),
- proto: Some(Protocol::Icmpv6(Icmpv6::new_code(Icmpv6Code::Named(
- "port-unreachable"
- )))),
- log: Some(LogLevel::Nolog),
- ..Default::default()
- }),
- },
- );
- assert_eq!(
- entry.rules()[1],
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::Out,
- verdict: Verdict::Accept,
- proto: Some(Protocol::Tcp(Tcp::new(Ports::from_u16(33, None)))),
- log: Some(LogLevel::Nolog),
- ..Default::default()
- }),
- },
- );
-
- assert_eq!(
- entry.rules()[2],
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Reject,
- log: Some(LogLevel::Critical),
- fw_macro: Some("BGP".to_string()),
- ip: Some(IpMatch {
- src: Some(IpAddrMatch::Ip(IpList::from(
- Cidr::new_v4([1, 2, 3, 4], 32).unwrap()
- ))),
- dst: None,
- }),
- ..Default::default()
- }),
- },
- );
-
- let empty_config = Config::parse("".as_bytes()).expect("empty config is invalid");
-
- assert_eq!(empty_config.config.options, Options::default());
- assert!(empty_config.config.rules.is_empty());
- assert!(empty_config.config.aliases.is_empty());
- assert!(empty_config.config.ipsets.is_empty());
- assert!(empty_config.config.groups.is_empty());
- }
-}
diff --git a/proxmox-ve-config/src/firewall/common.rs b/proxmox-ve-config/src/firewall/common.rs
deleted file mode 100644
index a08f19c..0000000
--- a/proxmox-ve-config/src/firewall/common.rs
+++ /dev/null
@@ -1,184 +0,0 @@
-use std::collections::{BTreeMap, HashMap};
-use std::io;
-
-use anyhow::{bail, format_err, Error};
-use serde::de::IntoDeserializer;
-
-use crate::firewall::parse::{parse_named_section_tail, split_key_value, SomeString};
-use crate::firewall::types::ipset::{IpsetName, IpsetScope};
-use crate::firewall::types::{Alias, Group, Ipset, Rule};
-
-#[derive(Debug, Default)]
-pub struct Config<O>
-where
- O: Default + std::fmt::Debug + serde::de::DeserializeOwned,
-{
- pub(crate) options: O,
- pub(crate) rules: Vec<Rule>,
- pub(crate) aliases: BTreeMap<String, Alias>,
- pub(crate) ipsets: BTreeMap<String, Ipset>,
- pub(crate) groups: BTreeMap<String, Group>,
-}
-
-enum Sec {
- None,
- Options,
- Aliases,
- Rules,
- Ipset(String, Ipset),
- Group(String, Group),
-}
-
-#[derive(Default)]
-pub struct ParserConfig {
- /// Network interfaces must be of the form `netX`.
- pub guest_iface_names: bool,
- pub ipset_scope: Option<IpsetScope>,
-}
-
-impl<O> Config<O>
-where
- O: Default + std::fmt::Debug + serde::de::DeserializeOwned,
-{
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn parse<R: io::BufRead>(input: R, parser_cfg: &ParserConfig) -> Result<Self, Error> {
- let mut section = Sec::None;
-
- let mut this = Self::new();
- let mut options = HashMap::new();
-
- for line in input.lines() {
- let line = line?;
- let line = line.trim();
-
- if line.is_empty() || line.starts_with('#') {
- continue;
- }
-
- log::trace!("parsing config line {line}");
-
- if line.eq_ignore_ascii_case("[OPTIONS]") {
- this.set_section(&mut section, Sec::Options)?;
- } else if line.eq_ignore_ascii_case("[ALIASES]") {
- this.set_section(&mut section, Sec::Aliases)?;
- } else if line.eq_ignore_ascii_case("[RULES]") {
- this.set_section(&mut section, Sec::Rules)?;
- } else if let Some(line) = line.strip_prefix("[IPSET") {
- let (name, comment) = parse_named_section_tail("ipset", line)?;
-
- let scope = parser_cfg.ipset_scope.ok_or_else(|| {
- format_err!("IPSET in config, but no scope set in parser config")
- })?;
-
- let ipset_name = IpsetName::new(scope, name.to_string());
- let mut ipset = Ipset::new(ipset_name);
- ipset.comment = comment.map(str::to_owned);
-
- this.set_section(&mut section, Sec::Ipset(name.to_string(), ipset))?;
- } else if let Some(line) = line.strip_prefix("[group") {
- let (name, comment) = parse_named_section_tail("group", line)?;
- let mut group = Group::new();
-
- group.set_comment(comment.map(str::to_owned));
-
- this.set_section(&mut section, Sec::Group(name.to_owned(), group))?;
- } else if line.starts_with('[') {
- bail!("invalid section {line:?}");
- } else {
- match &mut section {
- Sec::None => bail!("config line with no section: {line:?}"),
- Sec::Options => Self::parse_option(line, &mut options)?,
- Sec::Aliases => this.parse_alias(line)?,
- Sec::Rules => this.parse_rule(line, parser_cfg)?,
- Sec::Ipset(_name, ipset) => ipset.parse_entry(line)?,
- Sec::Group(_name, group) => group.parse_entry(line)?,
- }
- }
- }
- this.set_section(&mut section, Sec::None)?;
-
- this.options = O::deserialize(IntoDeserializer::<
- '_,
- crate::firewall::parse::SerdeStringError,
- >::into_deserializer(options))?;
-
- Ok(this)
- }
-
- fn parse_option(line: &str, options: &mut HashMap<String, SomeString>) -> Result<(), Error> {
- let (key, value) = split_key_value(line)
- .ok_or_else(|| format_err!("expected colon separated key and value, found {line:?}"))?;
-
- if options.insert(key.to_string(), value.into()).is_some() {
- bail!("duplicate option {key:?}");
- }
-
- Ok(())
- }
-
- fn parse_alias(&mut self, line: &str) -> Result<(), Error> {
- let alias: Alias = line.parse()?;
-
- if self
- .aliases
- .insert(alias.name().to_string(), alias)
- .is_some()
- {
- bail!("duplicate alias: {line}");
- }
-
- Ok(())
- }
-
- fn parse_rule(&mut self, line: &str, parser_cfg: &ParserConfig) -> Result<(), Error> {
- let rule: Rule = line.parse()?;
-
- if parser_cfg.guest_iface_names {
- if let Some(iface) = rule.iface() {
- let _ = iface
- .strip_prefix("net")
- .ok_or_else(|| {
- format_err!("interface name must be of the form \"net<number>\"")
- })?
- .parse::<u16>()
- .map_err(|_| {
- format_err!("interface name must be of the form \"net<number>\"")
- })?;
- }
- }
-
- self.rules.push(rule);
- Ok(())
- }
-
- fn set_section(&mut self, sec: &mut Sec, to: Sec) -> Result<(), Error> {
- let prev = std::mem::replace(sec, to);
-
- match prev {
- Sec::Ipset(name, ipset) => {
- if self.ipsets.insert(name.clone(), ipset).is_some() {
- bail!("duplicate ipset: {name:?}");
- }
- }
- Sec::Group(name, group) => {
- if self.groups.insert(name.clone(), group).is_some() {
- bail!("duplicate group: {name:?}");
- }
- }
- _ => (),
- }
-
- Ok(())
- }
-
- pub fn ipsets(&self) -> &BTreeMap<String, Ipset> {
- &self.ipsets
- }
-
- pub fn alias(&self, name: &str) -> Option<&Alias> {
- self.aliases.get(name)
- }
-}
diff --git a/proxmox-ve-config/src/firewall/ct_helper.rs b/proxmox-ve-config/src/firewall/ct_helper.rs
deleted file mode 100644
index 40e4fee..0000000
--- a/proxmox-ve-config/src/firewall/ct_helper.rs
+++ /dev/null
@@ -1,115 +0,0 @@
-use anyhow::{bail, Error};
-use serde::Deserialize;
-use std::collections::HashMap;
-use std::sync::OnceLock;
-
-use crate::firewall::types::address::Family;
-use crate::firewall::types::rule_match::{Ports, Protocol, Tcp, Udp};
-
-#[derive(Clone, Debug, Deserialize)]
-pub struct CtHelperMacroJson {
- pub v4: Option<bool>,
- pub v6: Option<bool>,
- pub name: String,
- pub tcp: Option<u16>,
- pub udp: Option<u16>,
-}
-
-impl TryFrom<CtHelperMacroJson> for CtHelperMacro {
- type Error = Error;
-
- fn try_from(value: CtHelperMacroJson) -> Result<Self, Self::Error> {
- if value.tcp.is_none() && value.udp.is_none() {
- bail!("Neither TCP nor UDP port set in CT helper!");
- }
-
- let family = match (value.v4, value.v6) {
- (Some(true), Some(true)) => None,
- (Some(true), _) => Some(Family::V4),
- (_, Some(true)) => Some(Family::V6),
- _ => bail!("Neither v4 nor v6 set in CT Helper Macro!"),
- };
-
- let mut ct_helper = CtHelperMacro {
- family,
- name: value.name,
- tcp: None,
- udp: None,
- };
-
- if let Some(dport) = value.tcp {
- let ports = Ports::from_u16(None, dport);
- ct_helper.tcp = Some(Tcp::new(ports).into());
- }
-
- if let Some(dport) = value.udp {
- let ports = Ports::from_u16(None, dport);
- ct_helper.udp = Some(Udp::new(ports).into());
- }
-
- Ok(ct_helper)
- }
-}
-
-#[derive(Clone, Debug, Deserialize)]
-#[serde(try_from = "CtHelperMacroJson")]
-pub struct CtHelperMacro {
- family: Option<Family>,
- name: String,
- tcp: Option<Protocol>,
- udp: Option<Protocol>,
-}
-
-impl CtHelperMacro {
- fn helper_name(&self, protocol: &str) -> String {
- format!("helper-{}-{protocol}", self.name)
- }
-
- pub fn tcp_helper_name(&self) -> String {
- self.helper_name("tcp")
- }
-
- pub fn udp_helper_name(&self) -> String {
- self.helper_name("udp")
- }
-
- pub fn family(&self) -> Option<Family> {
- self.family
- }
-
- pub fn name(&self) -> &str {
- self.name.as_ref()
- }
-
- pub fn tcp(&self) -> Option<&Protocol> {
- self.tcp.as_ref()
- }
-
- pub fn udp(&self) -> Option<&Protocol> {
- self.udp.as_ref()
- }
-}
-
-fn hashmap() -> &'static HashMap<String, CtHelperMacro> {
- const MACROS: &str = include_str!("../../resources/ct_helper.json");
- static HASHMAP: OnceLock<HashMap<String, CtHelperMacro>> = OnceLock::new();
-
- HASHMAP.get_or_init(|| {
- let macro_data: Vec<CtHelperMacro> = match serde_json::from_str(MACROS) {
- Ok(data) => data,
- Err(err) => {
- log::error!("could not load data for ct helpers: {err}");
- Vec::new()
- }
- };
-
- macro_data
- .into_iter()
- .map(|elem| (elem.name.clone(), elem))
- .collect()
- })
-}
-
-pub fn get_cthelper(name: &str) -> Option<&'static CtHelperMacro> {
- hashmap().get(name)
-}
diff --git a/proxmox-ve-config/src/firewall/fw_macros.rs b/proxmox-ve-config/src/firewall/fw_macros.rs
deleted file mode 100644
index 5fa8dab..0000000
--- a/proxmox-ve-config/src/firewall/fw_macros.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use std::collections::HashMap;
-
-use serde::Deserialize;
-use std::sync::OnceLock;
-
-use crate::firewall::types::rule_match::Protocol;
-
-use super::types::rule_match::RuleOptions;
-
-#[derive(Clone, Debug, Default, Deserialize)]
-struct FwMacroData {
- #[serde(rename = "desc")]
- pub description: &'static str,
- pub code: Vec<RuleOptions>,
-}
-
-#[derive(Clone, Debug, Default)]
-pub struct FwMacro {
- pub _description: &'static str,
- pub code: Vec<Protocol>,
-}
-
-fn macros() -> &'static HashMap<String, FwMacro> {
- const MACROS: &str = include_str!("../../resources/macros.json");
- static HASHMAP: OnceLock<HashMap<String, FwMacro>> = OnceLock::new();
-
- HASHMAP.get_or_init(|| {
- let macro_data: HashMap<String, FwMacroData> = match serde_json::from_str(MACROS) {
- Ok(m) => m,
- Err(err) => {
- log::error!("could not load data for macros: {err}");
- HashMap::new()
- }
- };
-
- let mut macros = HashMap::new();
-
- 'outer: for (name, data) in macro_data {
- let mut code = Vec::new();
-
- for c in data.code {
- match Protocol::from_options(&c) {
- Ok(Some(p)) => code.push(p),
- Ok(None) => {
- continue 'outer;
- }
- Err(err) => {
- log::error!("could not parse data for macro {name}: {err}");
- continue 'outer;
- }
- }
- }
-
- macros.insert(
- name,
- FwMacro {
- _description: data.description,
- code,
- },
- );
- }
-
- macros
- })
-}
-
-pub fn get_macro(name: &str) -> Option<&'static FwMacro> {
- macros().get(name)
-}
diff --git a/proxmox-ve-config/src/firewall/guest.rs b/proxmox-ve-config/src/firewall/guest.rs
deleted file mode 100644
index c7e282f..0000000
--- a/proxmox-ve-config/src/firewall/guest.rs
+++ /dev/null
@@ -1,237 +0,0 @@
-use std::collections::BTreeMap;
-use std::io;
-
-use crate::guest::types::Vmid;
-use crate::guest::vm::NetworkConfig;
-
-use crate::firewall::types::alias::{Alias, AliasName};
-use crate::firewall::types::ipset::IpsetScope;
-use crate::firewall::types::log::LogLevel;
-use crate::firewall::types::rule::{Direction, Rule, Verdict};
-use crate::firewall::types::Ipset;
-
-use anyhow::{bail, Error};
-use serde::Deserialize;
-
-use crate::firewall::parse::serde_option_bool;
-
-/// default return value for [`Config::is_enabled()`]
-pub const GUEST_ENABLED_DEFAULT: bool = false;
-/// default return value for [`Config::allow_ndp()`]
-pub const GUEST_ALLOW_NDP_DEFAULT: bool = true;
-/// default return value for [`Config::allow_dhcp()`]
-pub const GUEST_ALLOW_DHCP_DEFAULT: bool = true;
-/// default return value for [`Config::allow_ra()`]
-pub const GUEST_ALLOW_RA_DEFAULT: bool = false;
-/// default return value for [`Config::macfilter()`]
-pub const GUEST_MACFILTER_DEFAULT: bool = true;
-/// default return value for [`Config::ipfilter()`]
-pub const GUEST_IPFILTER_DEFAULT: bool = false;
-/// default return value for [`Config::default_policy()`]
-pub const GUEST_POLICY_IN_DEFAULT: Verdict = Verdict::Drop;
-/// default return value for [`Config::default_policy()`]
-pub const GUEST_POLICY_OUT_DEFAULT: Verdict = Verdict::Accept;
-
-#[derive(Debug, Default, Deserialize)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Options {
- #[serde(default, with = "serde_option_bool")]
- dhcp: Option<bool>,
-
- #[serde(default, with = "serde_option_bool")]
- enable: Option<bool>,
-
- #[serde(default, with = "serde_option_bool")]
- ipfilter: Option<bool>,
-
- #[serde(default, with = "serde_option_bool")]
- ndp: Option<bool>,
-
- #[serde(default, with = "serde_option_bool")]
- radv: Option<bool>,
-
- log_level_in: Option<LogLevel>,
- log_level_out: Option<LogLevel>,
-
- #[serde(default, with = "serde_option_bool")]
- macfilter: Option<bool>,
-
- #[serde(rename = "policy_in")]
- policy_in: Option<Verdict>,
-
- #[serde(rename = "policy_out")]
- policy_out: Option<Verdict>,
-}
-
-#[derive(Debug)]
-pub struct Config {
- vmid: Vmid,
-
- /// The interface prefix: "veth" for containers, "tap" for VMs.
- iface_prefix: &'static str,
-
- network_config: NetworkConfig,
- config: super::common::Config<Options>,
-}
-
-impl Config {
- pub fn parse<T: io::BufRead, U: io::BufRead>(
- vmid: &Vmid,
- iface_prefix: &'static str,
- firewall_input: T,
- network_input: U,
- ) -> Result<Self, Error> {
- let parser_cfg = super::common::ParserConfig {
- guest_iface_names: true,
- ipset_scope: Some(IpsetScope::Guest),
- };
-
- let config = super::common::Config::parse(firewall_input, &parser_cfg)?;
- if !config.groups.is_empty() {
- bail!("guest firewall config cannot declare groups");
- }
-
- let network_config = NetworkConfig::parse(network_input)?;
-
- Ok(Self {
- vmid: *vmid,
- iface_prefix,
- config,
- network_config,
- })
- }
-
- pub fn vmid(&self) -> Vmid {
- self.vmid
- }
-
- pub fn alias(&self, name: &AliasName) -> Option<&Alias> {
- self.config.alias(name.name())
- }
-
- pub fn iface_name_by_key(&self, key: &str) -> Result<String, Error> {
- let index = NetworkConfig::index_from_net_key(key)?;
- Ok(format!("{}{}i{index}", self.iface_prefix, self.vmid))
- }
-
- pub fn iface_name_by_index(&self, index: i64) -> String {
- format!("{}{}i{index}", self.iface_prefix, self.vmid)
- }
-
- /// returns the value of the enabled config key or [`GUEST_ENABLED_DEFAULT`] if unset
- pub fn is_enabled(&self) -> bool {
- self.config.options.enable.unwrap_or(GUEST_ENABLED_DEFAULT)
- }
-
- pub fn rules(&self) -> &[Rule] {
- &self.config.rules
- }
-
- pub fn log_level(&self, dir: Direction) -> LogLevel {
- match dir {
- Direction::In => self.config.options.log_level_in.unwrap_or_default(),
- Direction::Out => self.config.options.log_level_out.unwrap_or_default(),
- }
- }
-
- /// returns the value of the ndp config key or [`GUEST_ALLOW_NDP_DEFAULT`] if unset
- pub fn allow_ndp(&self) -> bool {
- self.config.options.ndp.unwrap_or(GUEST_ALLOW_NDP_DEFAULT)
- }
-
- /// returns the value of the dhcp config key or [`GUEST_ALLOW_DHCP_DEFAULT`] if unset
- pub fn allow_dhcp(&self) -> bool {
- self.config.options.dhcp.unwrap_or(GUEST_ALLOW_DHCP_DEFAULT)
- }
-
- /// returns the value of the radv config key or [`GUEST_ALLOW_RA_DEFAULT`] if unset
- pub fn allow_ra(&self) -> bool {
- self.config.options.radv.unwrap_or(GUEST_ALLOW_RA_DEFAULT)
- }
-
- /// returns the value of the macfilter config key or [`GUEST_MACFILTER_DEFAULT`] if unset
- pub fn macfilter(&self) -> bool {
- self.config
- .options
- .macfilter
- .unwrap_or(GUEST_MACFILTER_DEFAULT)
- }
-
- /// returns the value of the ipfilter config key or [`GUEST_IPFILTER_DEFAULT`] if unset
- pub fn ipfilter(&self) -> bool {
- self.config
- .options
- .ipfilter
- .unwrap_or(GUEST_IPFILTER_DEFAULT)
- }
-
- /// returns the value of the policy_in/out config key or
- /// [`GUEST_POLICY_IN_DEFAULT`] / [`GUEST_POLICY_OUT_DEFAULT`] if unset
- pub fn default_policy(&self, dir: Direction) -> Verdict {
- match dir {
- Direction::In => self
- .config
- .options
- .policy_in
- .unwrap_or(GUEST_POLICY_IN_DEFAULT),
- Direction::Out => self
- .config
- .options
- .policy_out
- .unwrap_or(GUEST_POLICY_OUT_DEFAULT),
- }
- }
-
- pub fn network_config(&self) -> &NetworkConfig {
- &self.network_config
- }
-
- pub fn ipsets(&self) -> &BTreeMap<String, Ipset> {
- self.config.ipsets()
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_config() {
- // most of the stuff is already tested in cluster parsing, only testing
- // guest specific options here
- const CONFIG: &str = r#"
-[OPTIONS]
-enable: 1
-dhcp: 1
-ipfilter: 0
-log_level_in: emerg
-log_level_out: crit
-macfilter: 0
-ndp:1
-radv:1
-policy_in: REJECT
-policy_out: REJECT
-"#;
-
- let config = CONFIG.as_bytes();
- let network_config: Vec<u8> = Vec::new();
- let config =
- Config::parse(&Vmid::new(100), "tap", config, network_config.as_slice()).unwrap();
-
- assert_eq!(
- config.config.options,
- Options {
- dhcp: Some(true),
- enable: Some(true),
- ipfilter: Some(false),
- ndp: Some(true),
- radv: Some(true),
- log_level_in: Some(LogLevel::Emergency),
- log_level_out: Some(LogLevel::Critical),
- macfilter: Some(false),
- policy_in: Some(Verdict::Reject),
- policy_out: Some(Verdict::Reject),
- }
- );
- }
-}
diff --git a/proxmox-ve-config/src/firewall/host.rs b/proxmox-ve-config/src/firewall/host.rs
deleted file mode 100644
index 3de6fad..0000000
--- a/proxmox-ve-config/src/firewall/host.rs
+++ /dev/null
@@ -1,372 +0,0 @@
-use std::io;
-use std::net::IpAddr;
-
-use anyhow::{bail, Error};
-use serde::Deserialize;
-
-use crate::host::utils::{host_ips, network_interface_cidrs};
-use proxmox_sys::nodename;
-
-use crate::firewall::parse;
-use crate::firewall::types::log::LogLevel;
-use crate::firewall::types::rule::Direction;
-use crate::firewall::types::{Alias, Cidr, Rule};
-
-/// default setting for the enabled key
-pub const HOST_ENABLED_DEFAULT: bool = true;
-/// default setting for the nftables key
-pub const HOST_NFTABLES_DEFAULT: bool = false;
-/// default return value for [`Config::allow_ndp()`]
-pub const HOST_ALLOW_NDP_DEFAULT: bool = true;
-/// default return value for [`Config::block_smurfs()`]
-pub const HOST_BLOCK_SMURFS_DEFAULT: bool = true;
-/// default return value for [`Config::block_synflood()`]
-pub const HOST_BLOCK_SYNFLOOD_DEFAULT: bool = false;
-/// default rate limit for synflood rule (packets / second)
-pub const HOST_BLOCK_SYNFLOOD_RATE_DEFAULT: i64 = 200;
-/// default rate limit for synflood rule (packets / second)
-pub const HOST_BLOCK_SYNFLOOD_BURST_DEFAULT: i64 = 1000;
-/// default return value for [`Config::block_invalid_tcp()`]
-pub const HOST_BLOCK_INVALID_TCP_DEFAULT: bool = false;
-/// default return value for [`Config::block_invalid_conntrack()`]
-pub const HOST_BLOCK_INVALID_CONNTRACK: bool = false;
-/// default setting for logging of invalid conntrack entries
-pub const HOST_LOG_INVALID_CONNTRACK: bool = false;
-
-#[derive(Debug, Default, Deserialize)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Options {
- #[serde(default, with = "parse::serde_option_bool")]
- enable: Option<bool>,
-
- #[serde(default, with = "parse::serde_option_bool")]
- nftables: Option<bool>,
-
- log_level_in: Option<LogLevel>,
- log_level_out: Option<LogLevel>,
-
- #[serde(default, with = "parse::serde_option_bool")]
- log_nf_conntrack: Option<bool>,
- #[serde(default, with = "parse::serde_option_bool")]
- ndp: Option<bool>,
-
- #[serde(default, with = "parse::serde_option_bool")]
- nf_conntrack_allow_invalid: Option<bool>,
-
- // is Option<Vec<>> for easier deserialization
- #[serde(default, with = "parse::serde_option_conntrack_helpers")]
- nf_conntrack_helpers: Option<Vec<String>>,
-
- #[serde(default, with = "parse::serde_option_number")]
- nf_conntrack_max: Option<i64>,
- #[serde(default, with = "parse::serde_option_number")]
- nf_conntrack_tcp_timeout_established: Option<i64>,
- #[serde(default, with = "parse::serde_option_number")]
- nf_conntrack_tcp_timeout_syn_recv: Option<i64>,
-
- #[serde(default, with = "parse::serde_option_bool")]
- nosmurfs: Option<bool>,
-
- #[serde(default, with = "parse::serde_option_bool")]
- protection_synflood: Option<bool>,
- #[serde(default, with = "parse::serde_option_number")]
- protection_synflood_burst: Option<i64>,
- #[serde(default, with = "parse::serde_option_number")]
- protection_synflood_rate: Option<i64>,
-
- smurf_log_level: Option<LogLevel>,
- tcp_flags_log_level: Option<LogLevel>,
-
- #[serde(default, with = "parse::serde_option_bool")]
- tcpflags: Option<bool>,
-}
-
-#[derive(Debug, Default)]
-pub struct Config {
- pub(crate) config: super::common::Config<Options>,
-}
-
-impl Config {
- pub fn new() -> Self {
- Self {
- config: Default::default(),
- }
- }
-
- pub fn parse<R: io::BufRead>(input: R) -> Result<Self, Error> {
- let config = super::common::Config::parse(input, &Default::default())?;
-
- if !config.groups.is_empty() {
- bail!("host firewall config cannot declare groups");
- }
-
- if !config.aliases.is_empty() {
- bail!("host firewall config cannot declare aliases");
- }
-
- if !config.ipsets.is_empty() {
- bail!("host firewall config cannot declare ipsets");
- }
-
- Ok(Self { config })
- }
-
- pub fn rules(&self) -> &[Rule] {
- &self.config.rules
- }
-
- pub fn management_ips() -> Result<Vec<Cidr>, Error> {
- let mut management_cidrs = Vec::new();
-
- for host_ip in host_ips() {
- for network_interface_cidr in network_interface_cidrs() {
- match (host_ip, network_interface_cidr) {
- (IpAddr::V4(ip), Cidr::Ipv4(cidr)) => {
- if cidr.contains_address(&ip) {
- management_cidrs.push(network_interface_cidr);
- }
- }
- (IpAddr::V6(ip), Cidr::Ipv6(cidr)) => {
- if cidr.contains_address(&ip) {
- management_cidrs.push(network_interface_cidr);
- }
- }
- _ => continue,
- };
- }
- }
-
- Ok(management_cidrs)
- }
-
- pub fn hostname() -> &'static str {
- nodename()
- }
-
- pub fn get_alias(&self, name: &str) -> Option<&Alias> {
- self.config.alias(name)
- }
-
- /// returns value of enabled key or [`HOST_ENABLED_DEFAULT`] if unset
- pub fn is_enabled(&self) -> bool {
- self.config.options.enable.unwrap_or(HOST_ENABLED_DEFAULT)
- }
-
- /// returns value of nftables key or [`HOST_NFTABLES_DEFAULT`] if unset
- pub fn nftables(&self) -> bool {
- self.config
- .options
- .nftables
- .unwrap_or(HOST_NFTABLES_DEFAULT)
- }
-
- /// returns value of ndp key or [`HOST_ALLOW_NDP_DEFAULT`] if unset
- pub fn allow_ndp(&self) -> bool {
- self.config.options.ndp.unwrap_or(HOST_ALLOW_NDP_DEFAULT)
- }
-
- /// returns value of nosmurfs key or [`HOST_BLOCK_SMURFS_DEFAULT`] if unset
- pub fn block_smurfs(&self) -> bool {
- self.config
- .options
- .nosmurfs
- .unwrap_or(HOST_BLOCK_SMURFS_DEFAULT)
- }
-
- /// returns the log level for the smurf protection rule
- ///
- /// If there is no log level set, it returns [`LogLevel::default()`]
- pub fn block_smurfs_log_level(&self) -> LogLevel {
- self.config.options.smurf_log_level.unwrap_or_default()
- }
-
- /// returns value of protection_synflood key or [`HOST_BLOCK_SYNFLOOD_DEFAULT`] if unset
- pub fn block_synflood(&self) -> bool {
- self.config
- .options
- .protection_synflood
- .unwrap_or(HOST_BLOCK_SYNFLOOD_DEFAULT)
- }
-
- /// returns value of protection_synflood_rate key or [`HOST_BLOCK_SYNFLOOD_RATE_DEFAULT`] if
- /// unset
- pub fn synflood_rate(&self) -> i64 {
- self.config
- .options
- .protection_synflood_rate
- .unwrap_or(HOST_BLOCK_SYNFLOOD_RATE_DEFAULT)
- }
-
- /// returns value of protection_synflood_burst key or [`HOST_BLOCK_SYNFLOOD_BURST_DEFAULT`] if
- /// unset
- pub fn synflood_burst(&self) -> i64 {
- self.config
- .options
- .protection_synflood_burst
- .unwrap_or(HOST_BLOCK_SYNFLOOD_BURST_DEFAULT)
- }
-
- /// returns value of tcpflags key or [`HOST_BLOCK_INVALID_TCP_DEFAULT`] if unset
- pub fn block_invalid_tcp(&self) -> bool {
- self.config
- .options
- .tcpflags
- .unwrap_or(HOST_BLOCK_INVALID_TCP_DEFAULT)
- }
-
- /// returns the log level for the block invalid TCP packets rule
- ///
- /// If there is no log level set, it returns [`LogLevel::default()`]
- pub fn block_invalid_tcp_log_level(&self) -> LogLevel {
- self.config.options.tcp_flags_log_level.unwrap_or_default()
- }
-
- /// returns value of nf_conntrack_allow_invalid key or [`HOST_BLOCK_INVALID_CONNTRACK`] if
- /// unset
- pub fn block_invalid_conntrack(&self) -> bool {
- !self
- .config
- .options
- .nf_conntrack_allow_invalid
- .unwrap_or(HOST_BLOCK_INVALID_CONNTRACK)
- }
-
- pub fn nf_conntrack_max(&self) -> Option<i64> {
- self.config.options.nf_conntrack_max
- }
-
- pub fn nf_conntrack_tcp_timeout_established(&self) -> Option<i64> {
- self.config.options.nf_conntrack_tcp_timeout_established
- }
-
- pub fn nf_conntrack_tcp_timeout_syn_recv(&self) -> Option<i64> {
- self.config.options.nf_conntrack_tcp_timeout_syn_recv
- }
-
- /// returns value of log_nf_conntrack key or [`HOST_LOG_INVALID_CONNTRACK`] if unset
- pub fn log_nf_conntrack(&self) -> bool {
- self.config
- .options
- .log_nf_conntrack
- .unwrap_or(HOST_LOG_INVALID_CONNTRACK)
- }
-
- pub fn conntrack_helpers(&self) -> Option<&Vec<String>> {
- self.config.options.nf_conntrack_helpers.as_ref()
- }
-
- /// returns the log level for the given direction
- ///
- /// If there is no log level set it returns [`LogLevel::default()`]
- pub fn log_level(&self, dir: Direction) -> LogLevel {
- match dir {
- Direction::In => self.config.options.log_level_in.unwrap_or_default(),
- Direction::Out => self.config.options.log_level_out.unwrap_or_default(),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::firewall::types::{
- log::LogLevel,
- rule::{Kind, RuleGroup, Verdict},
- rule_match::{Ports, Protocol, RuleMatch, Udp},
- };
-
- use super::*;
-
- #[test]
- fn test_parse_config() {
- const CONFIG: &str = r#"
-[OPTIONS]
-enable: 1
-nftables: 1
-log_level_in: debug
-log_level_out: emerg
-log_nf_conntrack: 0
-ndp: 1
-nf_conntrack_allow_invalid: yes
-nf_conntrack_helpers: ftp
-nf_conntrack_max: 44000
-nf_conntrack_tcp_timeout_established: 500000
-nf_conntrack_tcp_timeout_syn_recv: 44
-nosmurfs: no
-protection_synflood: 1
-protection_synflood_burst: 2500
-protection_synflood_rate: 300
-smurf_log_level: notice
-tcp_flags_log_level: nolog
-tcpflags: yes
-
-[RULES]
-
-GROUP tgr -i eth0 # acomm
-IN ACCEPT -p udp -dport 33 -sport 22 -log warning
-
-"#;
-
- let mut config = CONFIG.as_bytes();
- let config = Config::parse(&mut config).unwrap();
-
- assert_eq!(
- config.config.options,
- Options {
- enable: Some(true),
- nftables: Some(true),
- log_level_in: Some(LogLevel::Debug),
- log_level_out: Some(LogLevel::Emergency),
- log_nf_conntrack: Some(false),
- ndp: Some(true),
- nf_conntrack_allow_invalid: Some(true),
- nf_conntrack_helpers: Some(vec!["ftp".to_string()]),
- nf_conntrack_max: Some(44000),
- nf_conntrack_tcp_timeout_established: Some(500000),
- nf_conntrack_tcp_timeout_syn_recv: Some(44),
- nosmurfs: Some(false),
- protection_synflood: Some(true),
- protection_synflood_burst: Some(2500),
- protection_synflood_rate: Some(300),
- smurf_log_level: Some(LogLevel::Notice),
- tcp_flags_log_level: Some(LogLevel::Nolog),
- tcpflags: Some(true),
- }
- );
-
- assert_eq!(config.config.rules.len(), 2);
-
- assert_eq!(
- config.config.rules[0],
- Rule {
- disabled: false,
- comment: Some("acomm".to_string()),
- kind: Kind::Group(RuleGroup {
- group: "tgr".to_string(),
- iface: Some("eth0".to_string()),
- }),
- },
- );
-
- assert_eq!(
- config.config.rules[1],
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Accept,
- proto: Some(Protocol::Udp(Udp::new(Ports::from_u16(22, 33)))),
- log: Some(LogLevel::Warning),
- ..Default::default()
- }),
- },
- );
-
- Config::parse("[ALIASES]\ntest 127.0.0.1".as_bytes())
- .expect_err("host config cannot contain aliases");
-
- Config::parse("[GROUP test]".as_bytes()).expect_err("host config cannot contain groups");
-
- Config::parse("[IPSET test]".as_bytes()).expect_err("host config cannot contain ipsets");
- }
-}
diff --git a/proxmox-ve-config/src/firewall/mod.rs b/proxmox-ve-config/src/firewall/mod.rs
deleted file mode 100644
index 2cf57e2..0000000
--- a/proxmox-ve-config/src/firewall/mod.rs
+++ /dev/null
@@ -1,10 +0,0 @@
-pub mod cluster;
-pub mod common;
-pub mod ct_helper;
-pub mod fw_macros;
-pub mod guest;
-pub mod host;
-pub mod ports;
-pub mod types;
-
-pub(crate) mod parse;
diff --git a/proxmox-ve-config/src/firewall/parse.rs b/proxmox-ve-config/src/firewall/parse.rs
deleted file mode 100644
index 7bf00c0..0000000
--- a/proxmox-ve-config/src/firewall/parse.rs
+++ /dev/null
@@ -1,494 +0,0 @@
-use std::fmt;
-
-use anyhow::{bail, format_err, Error};
-
-const NAME_SPECIAL_CHARACTERS: [u8; 2] = [b'-', b'_'];
-
-/// Parses out a "name" which can be alphanumeric and include dashes.
-///
-/// Returns `None` if the name part would be empty.
-///
-/// Returns a tuple with the name and the remainder (not trimmed).
-///
-/// # Examples
-/// ```ignore
-/// assert_eq!(match_name("some-name someremainder"), Some(("some-name", " someremainder")));
-/// assert_eq!(match_name("some-name@someremainder"), Some(("some-name", "@someremainder")));
-/// assert_eq!(match_name(""), None);
-/// assert_eq!(match_name(" someremainder"), None);
-/// ```
-pub fn match_name(line: &str) -> Option<(&str, &str)> {
- if !line.starts_with(|c: char| c.is_ascii_alphabetic()) {
- return None;
- }
-
- let end = line
- .as_bytes()
- .iter()
- .position(|&b| !(b.is_ascii_alphanumeric() || NAME_SPECIAL_CHARACTERS.contains(&b)));
-
- let (name, rest) = match end {
- Some(end) => line.split_at(end),
- None => (line, ""),
- };
-
- if name.is_empty() {
- None
- } else {
- Some((name, rest))
- }
-}
-
-/// Parses up to the next whitespace character or end of the string.
-///
-/// Returns `None` if the non-whitespace part would be empty.
-///
-/// Returns a tuple containing the parsed section and the *trimmed* remainder.
-pub fn match_non_whitespace(line: &str) -> Option<(&str, &str)> {
- let (text, rest) = line
- .as_bytes()
- .iter()
- .position(|&b| b.is_ascii_whitespace())
- .map(|pos| {
- let (a, b) = line.split_at(pos);
- (a, b.trim_start())
- })
- .unwrap_or((line, ""));
- if text.is_empty() {
- None
- } else {
- Some((text, rest))
- }
-}
-
-/// parses out all digits and returns the remainder
-///
-/// returns [`None`] if the digit part would be empty
-///
-/// Returns a tuple with the digits and the remainder (not trimmed).
-pub fn match_digits(line: &str) -> Option<(&str, &str)> {
- let split_position = line.as_bytes().iter().position(|&b| !b.is_ascii_digit());
-
- let (digits, rest) = match split_position {
- Some(pos) => line.split_at(pos),
- None => (line, ""),
- };
-
- if !digits.is_empty() {
- return Some((digits, rest));
- }
-
- None
-}
-
-/// Separate a `key: value` line, trimming whitespace.
-///
-/// Returns `None` if the `key` would be empty.
-pub fn split_key_value(line: &str) -> Option<(&str, &str)> {
- line.split_once(':')
- .map(|(key, value)| (key.trim(), value.trim()))
-}
-
-/// Parse a boolean.
-///
-/// values that parse as [`false`]: 0, false, off, no
-/// values that parse as [`true`]: 1, true, on, yes
-///
-/// # Examples
-/// ```ignore
-/// assert_eq!(parse_bool("false"), Ok(false));
-/// assert_eq!(parse_bool("on"), Ok(true));
-/// assert!(parse_bool("proxmox").is_err());
-/// ```
-pub fn parse_bool(value: &str) -> Result<bool, Error> {
- Ok(
- if value == "0"
- || value.eq_ignore_ascii_case("false")
- || value.eq_ignore_ascii_case("off")
- || value.eq_ignore_ascii_case("no")
- {
- false
- } else if value == "1"
- || value.eq_ignore_ascii_case("true")
- || value.eq_ignore_ascii_case("on")
- || value.eq_ignore_ascii_case("yes")
- {
- true
- } else {
- bail!("not a boolean: {value:?}");
- },
- )
-}
-
-/// Parse the *remainder* of a section line, that is `<whitespace>NAME] #optional comment`.
-/// The `kind` parameter is used for error messages and should be the section type.
-///
-/// Return the name and the optional comment.
-pub fn parse_named_section_tail<'a>(
- kind: &'static str,
- line: &'a str,
-) -> Result<(&'a str, Option<&'a str>), Error> {
- if line.is_empty() || !line.as_bytes()[0].is_ascii_whitespace() {
- bail!("incomplete {kind} section");
- }
-
- let line = line.trim_start();
- let (name, line) = match_name(line)
- .ok_or_else(|| format_err!("expected a name for the {kind} at {line:?}"))?;
-
- let line = line
- .strip_prefix(']')
- .ok_or_else(|| format_err!("expected closing ']' in {kind} section header"))?
- .trim_start();
-
- Ok(match line.strip_prefix('#') {
- Some(comment) => (name, Some(comment.trim())),
- None if !line.is_empty() => bail!("trailing characters after {kind} section: {line:?}"),
- None => (name, None),
- })
-}
-
-// parses a number from a string OR number
-pub mod serde_option_number {
- use std::fmt;
-
- use serde::de::{Deserializer, Error, Visitor};
-
- pub fn deserialize<'de, D: Deserializer<'de>>(
- deserializer: D,
- ) -> Result<Option<i64>, D::Error> {
- struct V;
-
- impl<'de> Visitor<'de> for V {
- type Value = Option<i64>;
-
- fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str("a numerical value")
- }
-
- fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
- v.parse().map_err(E::custom).map(Some)
- }
-
- fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
- Ok(None)
- }
-
- fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
- where
- D: Deserializer<'de>,
- {
- deserializer.deserialize_any(self)
- }
- }
-
- deserializer.deserialize_any(V)
- }
-}
-
-// parses a bool from a string OR bool
-pub mod serde_option_bool {
- use std::fmt;
-
- use serde::de::{Deserializer, Error, Visitor};
-
- pub fn deserialize<'de, D: Deserializer<'de>>(
- deserializer: D,
- ) -> Result<Option<bool>, D::Error> {
- struct V;
-
- impl<'de> Visitor<'de> for V {
- type Value = Option<bool>;
-
- fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str("a boolean-like value")
- }
-
- fn visit_bool<E: Error>(self, v: bool) -> Result<Self::Value, E> {
- Ok(Some(v))
- }
-
- fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
- super::parse_bool(v).map_err(E::custom).map(Some)
- }
-
- fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
- Ok(None)
- }
-
- fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
- where
- D: Deserializer<'de>,
- {
- deserializer.deserialize_any(self)
- }
- }
-
- deserializer.deserialize_any(V)
- }
-}
-
-// parses a comma_separated list of strings
-pub mod serde_option_conntrack_helpers {
- use std::fmt;
-
- use serde::de::{Deserializer, Error, Visitor};
-
- pub fn deserialize<'de, D: Deserializer<'de>>(
- deserializer: D,
- ) -> Result<Option<Vec<String>>, D::Error> {
- struct V;
-
- impl<'de> Visitor<'de> for V {
- type Value = Option<Vec<String>>;
-
- fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str("A list of conntrack helpers")
- }
-
- fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
- if v.is_empty() {
- return Ok(None);
- }
-
- Ok(Some(v.split(',').map(String::from).collect()))
- }
-
- fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
- Ok(None)
- }
-
- fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
- where
- D: Deserializer<'de>,
- {
- deserializer.deserialize_any(self)
- }
- }
-
- deserializer.deserialize_any(V)
- }
-}
-
-// parses a log_ratelimit string: '[enable=]<1|0> [,burst=<integer>] [,rate=<rate>]'
-pub mod serde_option_log_ratelimit {
- use std::fmt;
-
- use serde::de::{Deserializer, Error, Visitor};
-
- use crate::firewall::types::log::LogRateLimit;
-
- pub fn deserialize<'de, D: Deserializer<'de>>(
- deserializer: D,
- ) -> Result<Option<LogRateLimit>, D::Error> {
- struct V;
-
- impl<'de> Visitor<'de> for V {
- type Value = Option<LogRateLimit>;
-
- fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str("a boolean-like value")
- }
-
- fn visit_str<E: Error>(self, v: &str) -> Result<Self::Value, E> {
- v.parse().map_err(E::custom).map(Some)
- }
-
- fn visit_none<E: Error>(self) -> Result<Self::Value, E> {
- Ok(None)
- }
-
- fn visit_some<D>(self, deserializer: D) -> Result<Self::Value, D::Error>
- where
- D: Deserializer<'de>,
- {
- deserializer.deserialize_any(self)
- }
- }
-
- deserializer.deserialize_any(V)
- }
-}
-
-/// `&str` deserializer which also accepts an `Option`.
-///
-/// Serde's `StringDeserializer` does not.
-#[derive(Clone, Copy, Debug)]
-pub struct SomeStrDeserializer<'a, E>(serde::de::value::StrDeserializer<'a, E>);
-
-impl<'de, 'a, E> serde::de::Deserializer<'de> for SomeStrDeserializer<'a, E>
-where
- E: serde::de::Error,
-{
- type Error = E;
-
- fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- self.0.deserialize_any(visitor)
- }
-
- fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- visitor.visit_some(self.0)
- }
-
- fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- self.0.deserialize_str(visitor)
- }
-
- fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- self.0.deserialize_string(visitor)
- }
-
- fn deserialize_enum<V>(
- self,
- _name: &str,
- _variants: &'static [&'static str],
- visitor: V,
- ) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- visitor.visit_enum(self.0)
- }
-
- serde::forward_to_deserialize_any! {
- bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char
- bytes byte_buf unit unit_struct newtype_struct seq tuple
- tuple_struct map struct identifier ignored_any
- }
-}
-
-/// `&str` wrapper which implements `IntoDeserializer` via `SomeStrDeserializer`.
-#[derive(Clone, Debug)]
-pub struct SomeStr<'a>(pub &'a str);
-
-impl<'a> From<&'a str> for SomeStr<'a> {
- fn from(s: &'a str) -> Self {
- Self(s)
- }
-}
-
-impl<'de, 'a, E> serde::de::IntoDeserializer<'de, E> for SomeStr<'a>
-where
- E: serde::de::Error,
-{
- type Deserializer = SomeStrDeserializer<'a, E>;
-
- fn into_deserializer(self) -> Self::Deserializer {
- SomeStrDeserializer(self.0.into_deserializer())
- }
-}
-
-/// `String` deserializer which also accepts an `Option`.
-///
-/// Serde's `StringDeserializer` does not.
-#[derive(Clone, Debug)]
-pub struct SomeStringDeserializer<E>(serde::de::value::StringDeserializer<E>);
-
-impl<'de, E> serde::de::Deserializer<'de> for SomeStringDeserializer<E>
-where
- E: serde::de::Error,
-{
- type Error = E;
-
- fn deserialize_any<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- self.0.deserialize_any(visitor)
- }
-
- fn deserialize_option<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- visitor.visit_some(self.0)
- }
-
- fn deserialize_str<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- self.0.deserialize_str(visitor)
- }
-
- fn deserialize_string<V>(self, visitor: V) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- self.0.deserialize_string(visitor)
- }
-
- fn deserialize_enum<V>(
- self,
- _name: &str,
- _variants: &'static [&'static str],
- visitor: V,
- ) -> Result<V::Value, Self::Error>
- where
- V: serde::de::Visitor<'de>,
- {
- visitor.visit_enum(self.0)
- }
-
- serde::forward_to_deserialize_any! {
- bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char
- bytes byte_buf unit unit_struct newtype_struct seq tuple
- tuple_struct map struct identifier ignored_any
- }
-}
-
-/// `&str` wrapper which implements `IntoDeserializer` via `SomeStringDeserializer`.
-#[derive(Clone, Debug)]
-pub struct SomeString(pub String);
-
-impl From<&str> for SomeString {
- fn from(s: &str) -> Self {
- Self::from(s.to_string())
- }
-}
-
-impl From<String> for SomeString {
- fn from(s: String) -> Self {
- Self(s)
- }
-}
-
-impl<'de, E> serde::de::IntoDeserializer<'de, E> for SomeString
-where
- E: serde::de::Error,
-{
- type Deserializer = SomeStringDeserializer<E>;
-
- fn into_deserializer(self) -> Self::Deserializer {
- SomeStringDeserializer(self.0.into_deserializer())
- }
-}
-
-#[derive(Debug)]
-pub struct SerdeStringError(String);
-
-impl std::error::Error for SerdeStringError {}
-
-impl fmt::Display for SerdeStringError {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str(&self.0)
- }
-}
-
-impl serde::de::Error for SerdeStringError {
- fn custom<T: fmt::Display>(msg: T) -> Self {
- Self(msg.to_string())
- }
-}
diff --git a/proxmox-ve-config/src/firewall/ports.rs b/proxmox-ve-config/src/firewall/ports.rs
deleted file mode 100644
index 9d5d1be..0000000
--- a/proxmox-ve-config/src/firewall/ports.rs
+++ /dev/null
@@ -1,80 +0,0 @@
-use anyhow::{format_err, Error};
-use std::sync::OnceLock;
-
-#[derive(Default)]
-struct NamedPorts {
- ports: std::collections::HashMap<String, u16>,
-}
-
-impl NamedPorts {
- fn new() -> Self {
- use std::io::BufRead;
-
- log::trace!("loading /etc/services");
-
- let mut this = Self::default();
-
- let file = match std::fs::File::open("/etc/services") {
- Ok(file) => file,
- Err(_) => return this,
- };
-
- for line in std::io::BufReader::new(file).lines() {
- let line = match line {
- Ok(line) => line,
- Err(_) => break,
- };
-
- let line = line.trim_start();
-
- if line.is_empty() || line.starts_with('#') {
- continue;
- }
-
- let mut parts = line.split_ascii_whitespace();
-
- let name = match parts.next() {
- None => continue,
- Some(name) => name.to_string(),
- };
-
- let proto: u16 = match parts.next() {
- None => continue,
- Some(proto) => match proto.split('/').next() {
- None => continue,
- Some(num) => match num.parse() {
- Ok(num) => num,
- Err(_) => continue,
- },
- },
- };
-
- this.ports.insert(name, proto);
- for alias in parts {
- if alias.starts_with('#') {
- break;
- }
- this.ports.insert(alias.to_string(), proto);
- }
- }
-
- this
- }
-
- fn find(&self, name: &str) -> Option<u16> {
- self.ports.get(name).copied()
- }
-}
-
-fn named_ports() -> &'static NamedPorts {
- static NAMED_PORTS: OnceLock<NamedPorts> = OnceLock::new();
-
- NAMED_PORTS.get_or_init(NamedPorts::new)
-}
-
-/// Parse a named port with the help of `/etc/services`.
-pub fn parse_named_port(name: &str) -> Result<u16, Error> {
- named_ports()
- .find(name)
- .ok_or_else(|| format_err!("unknown port name {name:?}"))
-}
diff --git a/proxmox-ve-config/src/firewall/types/address.rs b/proxmox-ve-config/src/firewall/types/address.rs
deleted file mode 100644
index e48ac1b..0000000
--- a/proxmox-ve-config/src/firewall/types/address.rs
+++ /dev/null
@@ -1,615 +0,0 @@
-use std::fmt;
-use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
-use std::ops::Deref;
-
-use anyhow::{bail, format_err, Error};
-use serde_with::DeserializeFromStr;
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq)]
-pub enum Family {
- V4,
- V6,
-}
-
-impl fmt::Display for Family {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Family::V4 => f.write_str("Ipv4"),
- Family::V6 => f.write_str("Ipv6"),
- }
- }
-}
-
-#[derive(Clone, Copy, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum Cidr {
- Ipv4(Ipv4Cidr),
- Ipv6(Ipv6Cidr),
-}
-
-impl Cidr {
- pub fn new_v4(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, Error> {
- Ok(Cidr::Ipv4(Ipv4Cidr::new(addr, mask)?))
- }
-
- pub fn new_v6(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, Error> {
- Ok(Cidr::Ipv6(Ipv6Cidr::new(addr, mask)?))
- }
-
- pub const fn family(&self) -> Family {
- match self {
- Cidr::Ipv4(_) => Family::V4,
- Cidr::Ipv6(_) => Family::V6,
- }
- }
-
- pub fn is_ipv4(&self) -> bool {
- matches!(self, Cidr::Ipv4(_))
- }
-
- pub fn is_ipv6(&self) -> bool {
- matches!(self, Cidr::Ipv6(_))
- }
-}
-
-impl fmt::Display for Cidr {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::Ipv4(ip) => f.write_str(ip.to_string().as_str()),
- Self::Ipv6(ip) => f.write_str(ip.to_string().as_str()),
- }
- }
-}
-
-impl std::str::FromStr for Cidr {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if let Ok(ip) = s.parse::<Ipv4Cidr>() {
- return Ok(Cidr::Ipv4(ip));
- }
-
- if let Ok(ip) = s.parse::<Ipv6Cidr>() {
- return Ok(Cidr::Ipv6(ip));
- }
-
- bail!("invalid ip address or CIDR: {s:?}");
- }
-}
-
-impl From<Ipv4Cidr> for Cidr {
- fn from(cidr: Ipv4Cidr) -> Self {
- Cidr::Ipv4(cidr)
- }
-}
-
-impl From<Ipv6Cidr> for Cidr {
- fn from(cidr: Ipv6Cidr) -> Self {
- Cidr::Ipv6(cidr)
- }
-}
-
-const IPV4_LENGTH: u8 = 32;
-
-#[derive(Clone, Copy, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Ipv4Cidr {
- addr: Ipv4Addr,
- mask: u8,
-}
-
-impl Ipv4Cidr {
- pub fn new(addr: impl Into<Ipv4Addr>, mask: u8) -> Result<Self, Error> {
- if mask > 32 {
- bail!("mask out of range for ipv4 cidr ({mask})");
- }
-
- Ok(Self {
- addr: addr.into(),
- mask,
- })
- }
-
- pub fn contains_address(&self, other: &Ipv4Addr) -> bool {
- let bits = u32::from_be_bytes(self.addr.octets());
- let other_bits = u32::from_be_bytes(other.octets());
-
- let shift_amount: u32 = IPV4_LENGTH.saturating_sub(self.mask).into();
-
- bits.checked_shr(shift_amount).unwrap_or(0)
- == other_bits.checked_shr(shift_amount).unwrap_or(0)
- }
-
- pub fn address(&self) -> &Ipv4Addr {
- &self.addr
- }
-
- pub fn mask(&self) -> u8 {
- self.mask
- }
-}
-
-impl<T: Into<Ipv4Addr>> From<T> for Ipv4Cidr {
- fn from(value: T) -> Self {
- Self {
- addr: value.into(),
- mask: 32,
- }
- }
-}
-
-impl std::str::FromStr for Ipv4Cidr {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- Ok(match s.find('/') {
- None => Self {
- addr: s.parse()?,
- mask: 32,
- },
- Some(pos) => {
- let mask: u8 = s[(pos + 1)..]
- .parse()
- .map_err(|_| format_err!("invalid mask in ipv4 cidr: {s:?}"))?;
-
- Self::new(s[..pos].parse::<Ipv4Addr>()?, mask)?
- }
- })
- }
-}
-
-impl fmt::Display for Ipv4Cidr {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{}/{}", &self.addr, self.mask)
- }
-}
-
-const IPV6_LENGTH: u8 = 128;
-
-#[derive(Clone, Copy, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Ipv6Cidr {
- addr: Ipv6Addr,
- mask: u8,
-}
-
-impl Ipv6Cidr {
- pub fn new(addr: impl Into<Ipv6Addr>, mask: u8) -> Result<Self, Error> {
- if mask > IPV6_LENGTH {
- bail!("mask out of range for ipv6 cidr");
- }
-
- Ok(Self {
- addr: addr.into(),
- mask,
- })
- }
-
- pub fn contains_address(&self, other: &Ipv6Addr) -> bool {
- let bits = u128::from_be_bytes(self.addr.octets());
- let other_bits = u128::from_be_bytes(other.octets());
-
- let shift_amount: u32 = IPV6_LENGTH.saturating_sub(self.mask).into();
-
- bits.checked_shr(shift_amount).unwrap_or(0)
- == other_bits.checked_shr(shift_amount).unwrap_or(0)
- }
-
- pub fn address(&self) -> &Ipv6Addr {
- &self.addr
- }
-
- pub fn mask(&self) -> u8 {
- self.mask
- }
-}
-
-impl std::str::FromStr for Ipv6Cidr {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- Ok(match s.find('/') {
- None => Self {
- addr: s.parse()?,
- mask: 128,
- },
- Some(pos) => {
- let mask: u8 = s[(pos + 1)..]
- .parse()
- .map_err(|_| format_err!("invalid mask in ipv6 cidr: {s:?}"))?;
-
- Self::new(s[..pos].parse::<Ipv6Addr>()?, mask)?
- }
- })
- }
-}
-
-impl fmt::Display for Ipv6Cidr {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- write!(f, "{}/{}", &self.addr, self.mask)
- }
-}
-
-impl<T: Into<Ipv6Addr>> From<T> for Ipv6Cidr {
- fn from(addr: T) -> Self {
- Self {
- addr: addr.into(),
- mask: 128,
- }
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum IpEntry {
- Cidr(Cidr),
- Range(IpAddr, IpAddr),
-}
-
-impl std::str::FromStr for IpEntry {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if s.is_empty() {
- bail!("Empty IP specification!")
- }
-
- let entries: Vec<&str> = s
- .split('-')
- .take(3) // so we can check whether there are too many
- .collect();
-
- match entries.as_slice() {
- [cidr] => Ok(IpEntry::Cidr(cidr.parse()?)),
- [beg, end] => {
- if let Ok(beg) = beg.parse::<Ipv4Addr>() {
- if let Ok(end) = end.parse::<Ipv4Addr>() {
- if beg < end {
- return Ok(IpEntry::Range(beg.into(), end.into()));
- }
-
- bail!("start address is greater than end address!");
- }
- }
-
- if let Ok(beg) = beg.parse::<Ipv6Addr>() {
- if let Ok(end) = end.parse::<Ipv6Addr>() {
- if beg < end {
- return Ok(IpEntry::Range(beg.into(), end.into()));
- }
-
- bail!("start address is greater than end address!");
- }
- }
-
- bail!("start and end are not valid IP addresses of the same type!")
- }
- _ => bail!("Invalid amount of elements in IpEntry!"),
- }
- }
-}
-
-impl fmt::Display for IpEntry {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::Cidr(ip) => write!(f, "{ip}"),
- Self::Range(beg, end) => write!(f, "{beg}-{end}"),
- }
- }
-}
-
-impl IpEntry {
- fn family(&self) -> Family {
- match self {
- Self::Cidr(cidr) => cidr.family(),
- Self::Range(start, end) => {
- if start.is_ipv4() && end.is_ipv4() {
- return Family::V4;
- }
-
- if start.is_ipv6() && end.is_ipv6() {
- return Family::V6;
- }
-
- // should never be reached due to constructors validating that
- // start type == end type
- unreachable!("invalid IP entry")
- }
- }
- }
-}
-
-impl From<Cidr> for IpEntry {
- fn from(value: Cidr) -> Self {
- IpEntry::Cidr(value)
- }
-}
-
-#[derive(Clone, Debug, DeserializeFromStr)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct IpList {
- // guaranteed to have the same family
- entries: Vec<IpEntry>,
- family: Family,
-}
-
-impl Deref for IpList {
- type Target = Vec<IpEntry>;
-
- fn deref(&self) -> &Self::Target {
- &self.entries
- }
-}
-
-impl<T: Into<IpEntry>> From<T> for IpList {
- fn from(value: T) -> Self {
- let entry = value.into();
-
- Self {
- family: entry.family(),
- entries: vec![entry],
- }
- }
-}
-
-impl std::str::FromStr for IpList {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if s.is_empty() {
- bail!("Empty IP specification!")
- }
-
- let mut entries = Vec::new();
- let mut current_family = None;
-
- for element in s.split(',') {
- let entry: IpEntry = element.parse()?;
-
- if let Some(family) = current_family {
- if family != entry.family() {
- bail!("Incompatible families in IPList!")
- }
- } else {
- current_family = Some(entry.family());
- }
-
- entries.push(entry);
- }
-
- if entries.is_empty() {
- bail!("empty ip list")
- }
-
- Ok(IpList {
- entries,
- family: current_family.unwrap(), // must be set due to length check above
- })
- }
-}
-
-impl IpList {
- pub fn new(entries: Vec<IpEntry>) -> Result<Self, Error> {
- let family = entries.iter().try_fold(None, |result, entry| {
- if let Some(family) = result {
- if entry.family() != family {
- bail!("non-matching families in entries list");
- }
-
- Ok(Some(family))
- } else {
- Ok(Some(entry.family()))
- }
- })?;
-
- if let Some(family) = family {
- return Ok(Self { entries, family });
- }
-
- bail!("no elements in ip list entries");
- }
-
- pub fn family(&self) -> Family {
- self.family
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use std::net::{Ipv4Addr, Ipv6Addr};
-
- #[test]
- fn test_v4_cidr() {
- let mut cidr: Ipv4Cidr = "0.0.0.0/0".parse().expect("valid IPv4 CIDR");
-
- assert_eq!(cidr.addr, Ipv4Addr::new(0, 0, 0, 0));
- assert_eq!(cidr.mask, 0);
-
- assert!(cidr.contains_address(&Ipv4Addr::new(0, 0, 0, 0)));
- assert!(cidr.contains_address(&Ipv4Addr::new(255, 255, 255, 255)));
-
- cidr = "192.168.100.1".parse().expect("valid IPv4 CIDR");
-
- assert_eq!(cidr.addr, Ipv4Addr::new(192, 168, 100, 1));
- assert_eq!(cidr.mask, 32);
-
- assert!(cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 1)));
- assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 2)));
- assert!(!cidr.contains_address(&Ipv4Addr::new(192, 168, 100, 0)));
-
- cidr = "10.100.5.0/24".parse().expect("valid IPv4 CIDR");
-
- assert_eq!(cidr.mask, 24);
-
- assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 0)));
- assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 1)));
- assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 100)));
- assert!(cidr.contains_address(&Ipv4Addr::new(10, 100, 5, 255)));
- assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 4, 255)));
- assert!(!cidr.contains_address(&Ipv4Addr::new(10, 100, 6, 0)));
-
- "0.0.0.0/-1".parse::<Ipv4Cidr>().unwrap_err();
- "0.0.0.0/33".parse::<Ipv4Cidr>().unwrap_err();
- "256.256.256.256/10".parse::<Ipv4Cidr>().unwrap_err();
-
- "fe80::1/64".parse::<Ipv4Cidr>().unwrap_err();
- "qweasd".parse::<Ipv4Cidr>().unwrap_err();
- "".parse::<Ipv4Cidr>().unwrap_err();
- }
-
- #[test]
- fn test_v6_cidr() {
- let mut cidr: Ipv6Cidr = "abab::1/64".parse().expect("valid IPv6 CIDR");
-
- assert_eq!(cidr.addr, Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 1));
- assert_eq!(cidr.mask, 64);
-
- assert!(cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 0, 0, 0, 0, 0)));
- assert!(cidr.contains_address(&Ipv6Addr::new(
- 0xABAB, 0, 0, 0, 0xAAAA, 0xAAAA, 0xAAAA, 0xAAAA
- )));
- assert!(cidr.contains_address(&Ipv6Addr::new(
- 0xABAB, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
- )));
- assert!(!cidr.contains_address(&Ipv6Addr::new(0xABAB, 0, 0, 1, 0, 0, 0, 0)));
- assert!(!cidr.contains_address(&Ipv6Addr::new(
- 0xABAA, 0, 0, 0, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
- )));
-
- cidr = "eeee::1".parse().expect("valid IPv6 CIDR");
-
- assert_eq!(cidr.mask, 128);
-
- assert!(cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 1)));
- assert!(!cidr.contains_address(&Ipv6Addr::new(
- 0xEEED, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF
- )));
- assert!(!cidr.contains_address(&Ipv6Addr::new(0xEEEE, 0, 0, 0, 0, 0, 0, 0)));
-
- "eeee::1/-1".parse::<Ipv6Cidr>().unwrap_err();
- "eeee::1/129".parse::<Ipv6Cidr>().unwrap_err();
- "gggg::1/64".parse::<Ipv6Cidr>().unwrap_err();
-
- "192.168.0.1".parse::<Ipv6Cidr>().unwrap_err();
- "qweasd".parse::<Ipv6Cidr>().unwrap_err();
- "".parse::<Ipv6Cidr>().unwrap_err();
- }
-
- #[test]
- fn test_parse_ip_entry() {
- let mut entry: IpEntry = "10.0.0.1".parse().expect("valid IP entry");
-
- assert_eq!(entry, Cidr::new_v4([10, 0, 0, 1], 32).unwrap().into());
-
- entry = "10.0.0.0/16".parse().expect("valid IP entry");
-
- assert_eq!(entry, Cidr::new_v4([10, 0, 0, 0], 16).unwrap().into());
-
- entry = "192.168.0.1-192.168.99.255"
- .parse()
- .expect("valid IP entry");
-
- assert_eq!(
- entry,
- IpEntry::Range([192, 168, 0, 1].into(), [192, 168, 99, 255].into())
- );
-
- entry = "fe80::1".parse().expect("valid IP entry");
-
- assert_eq!(
- entry,
- Cidr::new_v6([0xFE80, 0, 0, 0, 0, 0, 0, 1], 128)
- .unwrap()
- .into()
- );
-
- entry = "fe80::1/48".parse().expect("valid IP entry");
-
- assert_eq!(
- entry,
- Cidr::new_v6([0xFE80, 0, 0, 0, 0, 0, 0, 1], 48)
- .unwrap()
- .into()
- );
-
- entry = "fd80::1-fd80::ffff".parse().expect("valid IP entry");
-
- assert_eq!(
- entry,
- IpEntry::Range(
- [0xFD80, 0, 0, 0, 0, 0, 0, 1].into(),
- [0xFD80, 0, 0, 0, 0, 0, 0, 0xFFFF].into(),
- )
- );
-
- "192.168.100.0-192.168.99.255"
- .parse::<IpEntry>()
- .unwrap_err();
- "192.168.100.0-fe80::1".parse::<IpEntry>().unwrap_err();
- "192.168.100.0-192.168.200.0/16"
- .parse::<IpEntry>()
- .unwrap_err();
- "192.168.100.0-192.168.200.0-192.168.250.0"
- .parse::<IpEntry>()
- .unwrap_err();
- "qweasd".parse::<IpEntry>().unwrap_err();
- }
-
- #[test]
- fn test_parse_ip_list() {
- let mut ip_list: IpList = "192.168.0.1,192.168.100.0/24,172.16.0.0-172.32.255.255"
- .parse()
- .expect("valid IP list");
-
- assert_eq!(
- ip_list,
- IpList {
- entries: vec![
- IpEntry::Cidr(Cidr::new_v4([192, 168, 0, 1], 32).unwrap()),
- IpEntry::Cidr(Cidr::new_v4([192, 168, 100, 0], 24).unwrap()),
- IpEntry::Range([172, 16, 0, 0].into(), [172, 32, 255, 255].into()),
- ],
- family: Family::V4,
- }
- );
-
- ip_list = "fe80::1/64".parse().expect("valid IP list");
-
- assert_eq!(
- ip_list,
- IpList {
- entries: vec![IpEntry::Cidr(
- Cidr::new_v6([0xFE80, 0, 0, 0, 0, 0, 0, 1], 64).unwrap()
- ),],
- family: Family::V6,
- }
- );
-
- "192.168.0.1,fe80::1".parse::<IpList>().unwrap_err();
-
- "".parse::<IpList>().unwrap_err();
- "proxmox".parse::<IpList>().unwrap_err();
- }
-
- #[test]
- fn test_construct_ip_list() {
- let mut ip_list = IpList::new(vec![Cidr::new_v4([10, 0, 0, 0], 8).unwrap().into()])
- .expect("valid ip list");
-
- assert_eq!(ip_list.family(), Family::V4);
-
- ip_list =
- IpList::new(vec![Cidr::new_v6([0x000; 8], 8).unwrap().into()]).expect("valid ip list");
-
- assert_eq!(ip_list.family(), Family::V6);
-
- IpList::new(vec![]).expect_err("empty ip list is invalid");
-
- IpList::new(vec![
- Cidr::new_v4([10, 0, 0, 0], 8).unwrap().into(),
- Cidr::new_v6([0x0000; 8], 8).unwrap().into(),
- ])
- .expect_err("cannot mix ip families in ip list");
- }
-}
diff --git a/proxmox-ve-config/src/firewall/types/alias.rs b/proxmox-ve-config/src/firewall/types/alias.rs
deleted file mode 100644
index e6aa30d..0000000
--- a/proxmox-ve-config/src/firewall/types/alias.rs
+++ /dev/null
@@ -1,174 +0,0 @@
-use std::fmt::Display;
-use std::str::FromStr;
-
-use anyhow::{bail, format_err, Error};
-use serde_with::DeserializeFromStr;
-
-use crate::firewall::parse::{match_name, match_non_whitespace};
-use crate::firewall::types::address::Cidr;
-
-#[derive(Debug, Clone)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum AliasScope {
- Datacenter,
- Guest,
-}
-
-impl FromStr for AliasScope {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(match s {
- "dc" => AliasScope::Datacenter,
- "guest" => AliasScope::Guest,
- _ => bail!("invalid scope for alias: {s}"),
- })
- }
-}
-
-impl Display for AliasScope {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_str(match self {
- AliasScope::Datacenter => "dc",
- AliasScope::Guest => "guest",
- })
- }
-}
-
-#[derive(Debug, Clone, DeserializeFromStr)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct AliasName {
- scope: AliasScope,
- name: String,
-}
-
-impl Display for AliasName {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- f.write_fmt(format_args!("{}/{}", self.scope, self.name))
- }
-}
-
-impl FromStr for AliasName {
- 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 Alias name!")
- }
- }
- }
-}
-
-impl AliasName {
- pub fn new(scope: AliasScope, name: impl Into<String>) -> Self {
- Self {
- scope,
- name: name.into(),
- }
- }
-
- pub fn name(&self) -> &str {
- &self.name
- }
-
- pub fn scope(&self) -> &AliasScope {
- &self.scope
- }
-}
-
-#[derive(Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Alias {
- name: String,
- address: Cidr,
- comment: Option<String>,
-}
-
-impl Alias {
- pub fn new(
- name: impl Into<String>,
- address: impl Into<Cidr>,
- comment: impl Into<Option<String>>,
- ) -> Self {
- Self {
- name: name.into(),
- address: address.into(),
- comment: comment.into(),
- }
- }
-
- pub fn name(&self) -> &str {
- &self.name
- }
-
- pub fn address(&self) -> &Cidr {
- &self.address
- }
-
- pub fn comment(&self) -> Option<&str> {
- self.comment.as_deref()
- }
-}
-
-impl FromStr for Alias {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let (name, line) =
- match_name(s.trim_start()).ok_or_else(|| format_err!("expected an alias name"))?;
-
- let (address, line) = match_non_whitespace(line.trim_start())
- .ok_or_else(|| format_err!("expected a value for alias {name:?}"))?;
-
- let address: Cidr = 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 alias: {line:?}"),
- None => None,
- };
-
- Ok(Alias {
- name: name.to_string(),
- address,
- comment,
- })
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_alias() {
- for alias in [
- "local_network 10.0.0.0/32",
- "test-_123-___-a---- 10.0.0.1/32",
- ] {
- alias.parse::<Alias>().expect("valid alias");
- }
-
- for alias in ["-- 10.0.0.1/32", "0asd 10.0.0.1/32", "__test 10.0.0.0/32"] {
- alias.parse::<Alias>().expect_err("invalid alias");
- }
- }
-
- #[test]
- fn test_parse_alias_name() {
- for name in ["dc/proxmox_123", "guest/proxmox-123"] {
- name.parse::<AliasName>().expect("valid alias name");
- }
-
- for name in ["proxmox/proxmox_123", "guests/proxmox-123", "dc/", "/name"] {
- name.parse::<AliasName>().expect_err("invalid alias name");
- }
- }
-}
diff --git a/proxmox-ve-config/src/firewall/types/group.rs b/proxmox-ve-config/src/firewall/types/group.rs
deleted file mode 100644
index 7455268..0000000
--- a/proxmox-ve-config/src/firewall/types/group.rs
+++ /dev/null
@@ -1,36 +0,0 @@
-use anyhow::Error;
-
-use crate::firewall::types::Rule;
-
-#[derive(Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Group {
- rules: Vec<Rule>,
- comment: Option<String>,
-}
-
-impl Group {
- pub const fn new() -> Self {
- Self {
- rules: Vec::new(),
- comment: None,
- }
- }
-
- pub fn rules(&self) -> &Vec<Rule> {
- &self.rules
- }
-
- pub fn comment(&self) -> Option<&str> {
- self.comment.as_deref()
- }
-
- pub fn set_comment(&mut self, comment: Option<String>) {
- self.comment = comment;
- }
-
- pub(crate) fn parse_entry(&mut self, line: &str) -> Result<(), Error> {
- self.rules.push(line.parse()?);
- Ok(())
- }
-}
diff --git a/proxmox-ve-config/src/firewall/types/ipset.rs b/proxmox-ve-config/src/firewall/types/ipset.rs
deleted file mode 100644
index c1af642..0000000
--- a/proxmox-ve-config/src/firewall/types/ipset.rs
+++ /dev/null
@@ -1,349 +0,0 @@
-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/log.rs b/proxmox-ve-config/src/firewall/types/log.rs
deleted file mode 100644
index 72344e4..0000000
--- a/proxmox-ve-config/src/firewall/types/log.rs
+++ /dev/null
@@ -1,222 +0,0 @@
-use std::fmt;
-use std::str::FromStr;
-
-use crate::firewall::parse::parse_bool;
-use anyhow::{bail, Error};
-use serde::{Deserialize, Serialize};
-
-#[derive(Copy, Clone, Debug, Deserialize, Serialize, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-#[serde(rename_all = "lowercase")]
-pub enum LogRateLimitTimescale {
- #[default]
- Second,
- Minute,
- Hour,
- Day,
-}
-
-impl FromStr for LogRateLimitTimescale {
- type Err = Error;
-
- fn from_str(str: &str) -> Result<Self, Error> {
- match str {
- "second" => Ok(LogRateLimitTimescale::Second),
- "minute" => Ok(LogRateLimitTimescale::Minute),
- "hour" => Ok(LogRateLimitTimescale::Hour),
- "day" => Ok(LogRateLimitTimescale::Day),
- _ => bail!("Invalid time scale provided"),
- }
- }
-}
-
-#[derive(Debug, Deserialize, Clone)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct LogRateLimit {
- enabled: bool,
- rate: i64, // in packets
- per: LogRateLimitTimescale,
- burst: i64, // in packets
-}
-
-impl LogRateLimit {
- pub fn new(enabled: bool, rate: i64, per: LogRateLimitTimescale, burst: i64) -> Self {
- Self {
- enabled,
- rate,
- per,
- burst,
- }
- }
-
- pub fn enabled(&self) -> bool {
- self.enabled
- }
-
- pub fn rate(&self) -> i64 {
- self.rate
- }
-
- pub fn burst(&self) -> i64 {
- self.burst
- }
-
- pub fn per(&self) -> LogRateLimitTimescale {
- self.per
- }
-}
-
-impl Default for LogRateLimit {
- fn default() -> Self {
- Self {
- enabled: true,
- rate: 1,
- burst: 5,
- per: LogRateLimitTimescale::Second,
- }
- }
-}
-
-impl FromStr for LogRateLimit {
- type Err = Error;
-
- fn from_str(str: &str) -> Result<Self, Error> {
- let mut limit = Self::default();
-
- for element in str.split(',') {
- match element.split_once('=') {
- None => {
- limit.enabled = parse_bool(element)?;
- }
- Some((key, value)) if !key.is_empty() && !value.is_empty() => match key {
- "enable" => limit.enabled = parse_bool(value)?,
- "burst" => limit.burst = i64::from_str(value)?,
- "rate" => match value.split_once('/') {
- None => {
- limit.rate = i64::from_str(value)?;
- }
- Some((rate, unit)) => {
- if unit.is_empty() {
- bail!("empty unit specification")
- }
-
- limit.rate = i64::from_str(rate)?;
- limit.per = LogRateLimitTimescale::from_str(unit)?;
- }
- },
- _ => bail!("Invalid value for Key found in log_ratelimit!"),
- },
- _ => bail!("invalid value in log_ratelimit"),
- }
- }
-
- Ok(limit)
- }
-}
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Default)]
-pub enum LogLevel {
- #[default]
- Nolog,
- Emergency,
- Alert,
- Critical,
- Error,
- Warning,
- Notice,
- Info,
- Debug,
-}
-
-impl std::str::FromStr for LogLevel {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- Ok(match s {
- "nolog" => LogLevel::Nolog,
- "emerg" => LogLevel::Emergency,
- "alert" => LogLevel::Alert,
- "crit" => LogLevel::Critical,
- "err" => LogLevel::Error,
- "warn" => LogLevel::Warning,
- "warning" => LogLevel::Warning,
- "notice" => LogLevel::Notice,
- "info" => LogLevel::Info,
- "debug" => LogLevel::Debug,
- _ => bail!("invalid log level {s:?}"),
- })
- }
-}
-
-impl fmt::Display for LogLevel {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- f.write_str(match self {
- LogLevel::Nolog => "nolog",
- LogLevel::Emergency => "emerg",
- LogLevel::Alert => "alert",
- LogLevel::Critical => "crit",
- LogLevel::Error => "err",
- LogLevel::Warning => "warn",
- LogLevel::Notice => "notice",
- LogLevel::Info => "info",
- LogLevel::Debug => "debug",
- })
- }
-}
-
-serde_plain::derive_deserialize_from_fromstr!(LogLevel, "valid log level");
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_rate_limit() {
- let mut parsed_rate_limit = "1,burst=123,rate=44"
- .parse::<LogRateLimit>()
- .expect("valid rate limit");
-
- assert_eq!(
- parsed_rate_limit,
- LogRateLimit {
- enabled: true,
- burst: 123,
- rate: 44,
- per: LogRateLimitTimescale::Second,
- }
- );
-
- parsed_rate_limit = "1".parse::<LogRateLimit>().expect("valid rate limit");
-
- assert_eq!(parsed_rate_limit, LogRateLimit::default());
-
- parsed_rate_limit = "enable=0,rate=123/hour"
- .parse::<LogRateLimit>()
- .expect("valid rate limit");
-
- assert_eq!(
- parsed_rate_limit,
- LogRateLimit {
- enabled: false,
- burst: 5,
- rate: 123,
- per: LogRateLimitTimescale::Hour,
- }
- );
-
- "2".parse::<LogRateLimit>()
- .expect_err("invalid value for enable");
-
- "enabled=0,rate=123"
- .parse::<LogRateLimit>()
- .expect_err("invalid key in log ratelimit");
-
- "enable=0,rate=123,"
- .parse::<LogRateLimit>()
- .expect_err("trailing comma in log rate limit specification");
-
- "enable=0,rate=123/proxmox,"
- .parse::<LogRateLimit>()
- .expect_err("invalid unit for rate");
- }
-}
diff --git a/proxmox-ve-config/src/firewall/types/mod.rs b/proxmox-ve-config/src/firewall/types/mod.rs
deleted file mode 100644
index 8fd551e..0000000
--- a/proxmox-ve-config/src/firewall/types/mod.rs
+++ /dev/null
@@ -1,14 +0,0 @@
-pub mod address;
-pub mod alias;
-pub mod group;
-pub mod ipset;
-pub mod log;
-pub mod port;
-pub mod rule;
-pub mod rule_match;
-
-pub use address::Cidr;
-pub use alias::Alias;
-pub use group::Group;
-pub use ipset::Ipset;
-pub use rule::Rule;
diff --git a/proxmox-ve-config/src/firewall/types/port.rs b/proxmox-ve-config/src/firewall/types/port.rs
deleted file mode 100644
index c1252d9..0000000
--- a/proxmox-ve-config/src/firewall/types/port.rs
+++ /dev/null
@@ -1,181 +0,0 @@
-use std::fmt;
-use std::ops::Deref;
-
-use anyhow::{bail, Error};
-use serde_with::DeserializeFromStr;
-
-use crate::firewall::ports::parse_named_port;
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum PortEntry {
- Port(u16),
- Range(u16, u16),
-}
-
-impl fmt::Display for PortEntry {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Self::Port(p) => write!(f, "{p}"),
- Self::Range(beg, end) => write!(f, "{beg}-{end}"),
- }
- }
-}
-
-fn parse_port(port: &str) -> Result<u16, Error> {
- if let Ok(port) = port.parse::<u16>() {
- return Ok(port);
- }
-
- if let Ok(port) = parse_named_port(port) {
- return Ok(port);
- }
-
- bail!("invalid port specification: {port}")
-}
-
-impl std::str::FromStr for PortEntry {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(match s.trim().split_once(':') {
- None => PortEntry::from(parse_port(s)?),
- Some((first, second)) => {
- PortEntry::try_from((parse_port(first)?, parse_port(second)?))?
- }
- })
- }
-}
-
-impl From<u16> for PortEntry {
- fn from(port: u16) -> Self {
- PortEntry::Port(port)
- }
-}
-
-impl TryFrom<(u16, u16)> for PortEntry {
- type Error = Error;
-
- fn try_from(ports: (u16, u16)) -> Result<Self, Error> {
- if ports.0 > ports.1 {
- bail!("start port is greater than end port!");
- }
-
- Ok(PortEntry::Range(ports.0, ports.1))
- }
-}
-
-#[derive(Clone, Debug, DeserializeFromStr)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct PortList(pub(crate) Vec<PortEntry>);
-
-impl FromIterator<PortEntry> for PortList {
- fn from_iter<T: IntoIterator<Item = PortEntry>>(iter: T) -> Self {
- Self(iter.into_iter().collect())
- }
-}
-
-impl<T: Into<PortEntry>> From<T> for PortList {
- fn from(value: T) -> Self {
- Self(vec![value.into()])
- }
-}
-
-impl Deref for PortList {
- type Target = Vec<PortEntry>;
-
- fn deref(&self) -> &Self::Target {
- &self.0
- }
-}
-
-impl std::str::FromStr for PortList {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if s.is_empty() {
- bail!("empty port specification");
- }
-
- let mut entries = Vec::new();
-
- for entry in s.trim().split(',') {
- entries.push(entry.parse()?);
- }
-
- if entries.is_empty() {
- bail!("invalid empty port list");
- }
-
- Ok(Self(entries))
- }
-}
-
-impl fmt::Display for PortList {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- use fmt::Write;
- if self.0.len() > 1 {
- f.write_char('{')?;
- }
-
- let mut comma = '\0';
- for entry in &self.0 {
- if std::mem::replace(&mut comma, ',') != '\0' {
- f.write_char(comma)?;
- }
- fmt::Display::fmt(entry, f)?;
- }
-
- if self.0.len() > 1 {
- f.write_char('}')?;
- }
-
- Ok(())
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_port_entry() {
- let mut port_entry: PortEntry = "12345".parse().expect("valid port entry");
- assert_eq!(port_entry, PortEntry::from(12345));
-
- port_entry = "0:65535".parse().expect("valid port entry");
- assert_eq!(port_entry, PortEntry::try_from((0, 65535)).unwrap());
-
- "65536".parse::<PortEntry>().unwrap_err();
- "100:100000".parse::<PortEntry>().unwrap_err();
- "qweasd".parse::<PortEntry>().unwrap_err();
- "".parse::<PortEntry>().unwrap_err();
- }
-
- #[test]
- fn test_parse_port_list() {
- let mut port_list: PortList = "12345".parse().expect("valid port list");
- assert_eq!(port_list, PortList::from(12345));
-
- port_list = "12345,0:65535,1337,ssh:80,https"
- .parse()
- .expect("valid port list");
-
- assert_eq!(
- port_list,
- PortList(vec![
- PortEntry::from(12345),
- PortEntry::try_from((0, 65535)).unwrap(),
- PortEntry::from(1337),
- PortEntry::try_from((22, 80)).unwrap(),
- PortEntry::from(443),
- ])
- );
-
- "0::1337".parse::<PortList>().unwrap_err();
- "0:1337,".parse::<PortList>().unwrap_err();
- "70000".parse::<PortList>().unwrap_err();
- "qweasd".parse::<PortList>().unwrap_err();
- "".parse::<PortList>().unwrap_err();
- }
-}
diff --git a/proxmox-ve-config/src/firewall/types/rule.rs b/proxmox-ve-config/src/firewall/types/rule.rs
deleted file mode 100644
index 20deb3a..0000000
--- a/proxmox-ve-config/src/firewall/types/rule.rs
+++ /dev/null
@@ -1,412 +0,0 @@
-use core::fmt::Display;
-use std::fmt;
-use std::str::FromStr;
-
-use anyhow::{bail, ensure, format_err, Error};
-
-use crate::firewall::parse::match_name;
-use crate::firewall::types::rule_match::RuleMatch;
-use crate::firewall::types::rule_match::RuleOptions;
-
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum Direction {
- #[default]
- In,
- Out,
-}
-
-impl std::str::FromStr for Direction {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- for (name, dir) in [("IN", Direction::In), ("OUT", Direction::Out)] {
- if s.eq_ignore_ascii_case(name) {
- return Ok(dir);
- }
- }
-
- bail!("invalid direction: {s:?}, expect 'IN' or 'OUT'");
- }
-}
-
-serde_plain::derive_deserialize_from_fromstr!(Direction, "valid packet direction");
-
-impl fmt::Display for Direction {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Direction::In => f.write_str("in"),
- Direction::Out => f.write_str("out"),
- }
- }
-}
-
-#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
-pub enum Verdict {
- Accept,
- Reject,
- #[default]
- Drop,
-}
-
-impl std::str::FromStr for Verdict {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- for (name, verdict) in [
- ("ACCEPT", Verdict::Accept),
- ("REJECT", Verdict::Reject),
- ("DROP", Verdict::Drop),
- ] {
- if s.eq_ignore_ascii_case(name) {
- return Ok(verdict);
- }
- }
- bail!("invalid verdict {s:?}, expected one of 'ACCEPT', 'REJECT' or 'DROP'");
- }
-}
-
-impl Display for Verdict {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- let string = match self {
- Verdict::Accept => "ACCEPT",
- Verdict::Drop => "DROP",
- Verdict::Reject => "REJECT",
- };
-
- write!(f, "{string}")
- }
-}
-
-serde_plain::derive_deserialize_from_fromstr!(Verdict, "valid verdict");
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Rule {
- pub(crate) disabled: bool,
- pub(crate) kind: Kind,
- pub(crate) comment: Option<String>,
-}
-
-impl std::ops::Deref for Rule {
- type Target = Kind;
-
- fn deref(&self) -> &Self::Target {
- &self.kind
- }
-}
-
-impl std::ops::DerefMut for Rule {
- fn deref_mut(&mut self) -> &mut Self::Target {
- &mut self.kind
- }
-}
-
-impl FromStr for Rule {
- type Err = Error;
-
- fn from_str(input: &str) -> Result<Self, Self::Err> {
- if input.contains(['\n', '\r']) {
- bail!("rule must not contain any newlines!");
- }
-
- let (line, comment) = match input.rsplit_once('#') {
- Some((line, comment)) if !comment.is_empty() => (line.trim(), Some(comment.trim())),
- _ => (input.trim(), None),
- };
-
- let (disabled, line) = match line.strip_prefix('|') {
- Some(line) => (true, line.trim_start()),
- None => (false, line),
- };
-
- // todo: case insensitive?
- let kind = if line.starts_with("GROUP") {
- Kind::from(line.parse::<RuleGroup>()?)
- } else {
- Kind::from(line.parse::<RuleMatch>()?)
- };
-
- Ok(Self {
- disabled,
- comment: comment.map(str::to_string),
- kind,
- })
- }
-}
-
-impl Rule {
- pub fn iface(&self) -> Option<&str> {
- match &self.kind {
- Kind::Group(group) => group.iface(),
- Kind::Match(rule) => rule.iface(),
- }
- }
-
- pub fn disabled(&self) -> bool {
- self.disabled
- }
-
- pub fn kind(&self) -> &Kind {
- &self.kind
- }
-
- pub fn comment(&self) -> Option<&str> {
- self.comment.as_deref()
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum Kind {
- Group(RuleGroup),
- Match(RuleMatch),
-}
-
-impl Kind {
- pub fn is_group(&self) -> bool {
- matches!(self, Kind::Group(_))
- }
-
- pub fn is_match(&self) -> bool {
- matches!(self, Kind::Match(_))
- }
-}
-
-impl From<RuleGroup> for Kind {
- fn from(value: RuleGroup) -> Self {
- Kind::Group(value)
- }
-}
-
-impl From<RuleMatch> for Kind {
- fn from(value: RuleMatch) -> Self {
- Kind::Match(value)
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct RuleGroup {
- pub(crate) group: String,
- pub(crate) iface: Option<String>,
-}
-
-impl RuleGroup {
- pub(crate) fn from_options(group: String, options: RuleOptions) -> Result<Self, Error> {
- ensure!(
- options.proto.is_none()
- && options.dport.is_none()
- && options.sport.is_none()
- && options.dest.is_none()
- && options.source.is_none()
- && options.log.is_none()
- && options.icmp_type.is_none(),
- "only interface parameter is permitted for group rules"
- );
-
- Ok(Self {
- group,
- iface: options.iface,
- })
- }
-
- pub fn group(&self) -> &str {
- &self.group
- }
-
- pub fn iface(&self) -> Option<&str> {
- self.iface.as_deref()
- }
-}
-
-impl FromStr for RuleGroup {
- type Err = Error;
-
- fn from_str(input: &str) -> Result<Self, Self::Err> {
- let (keyword, rest) = match_name(input)
- .ok_or_else(|| format_err!("expected a leading keyword in rule group"))?;
-
- if !keyword.eq_ignore_ascii_case("group") {
- bail!("Expected keyword GROUP")
- }
-
- let (name, rest) =
- match_name(rest.trim()).ok_or_else(|| format_err!("expected a name for rule group"))?;
-
- let options = rest.trim_start().parse()?;
-
- Self::from_options(name.to_string(), options)
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::firewall::types::{
- address::{IpEntry, IpList},
- alias::{AliasName, AliasScope},
- ipset::{IpsetName, IpsetScope},
- log::LogLevel,
- rule_match::{Icmp, IcmpCode, IpAddrMatch, IpMatch, Ports, Protocol, Udp},
- Cidr,
- };
-
- use super::*;
-
- #[test]
- fn test_parse_rule() {
- let mut rule: Rule = "|GROUP tgr -i eth0 # acomm".parse().expect("valid rule");
-
- assert_eq!(
- rule,
- Rule {
- disabled: true,
- comment: Some("acomm".to_string()),
- kind: Kind::Group(RuleGroup {
- group: "tgr".to_string(),
- iface: Some("eth0".to_string()),
- }),
- },
- );
-
- rule = "IN ACCEPT -p udp -dport 33 -sport 22 -log warning"
- .parse()
- .expect("valid rule");
-
- assert_eq!(
- rule,
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Accept,
- proto: Some(Udp::new(Ports::from_u16(22, 33)).into()),
- log: Some(LogLevel::Warning),
- ..Default::default()
- }),
- }
- );
-
- rule = "IN ACCEPT --proto udp -i eth0".parse().expect("valid rule");
-
- assert_eq!(
- rule,
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Accept,
- proto: Some(Udp::new(Ports::new(None, None)).into()),
- iface: Some("eth0".to_string()),
- ..Default::default()
- }),
- }
- );
-
- rule = " OUT DROP \
- -source 10.0.0.0/24 -dest 20.0.0.0-20.255.255.255,192.168.0.0/16 \
- -p icmp -log nolog -icmp-type port-unreachable "
- .parse()
- .expect("valid rule");
-
- assert_eq!(
- rule,
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::Out,
- verdict: Verdict::Drop,
- ip: IpMatch::new(
- IpAddrMatch::Ip(IpList::from(Cidr::new_v4([10, 0, 0, 0], 24).unwrap())),
- IpAddrMatch::Ip(
- IpList::new(vec![
- IpEntry::Range([20, 0, 0, 0].into(), [20, 255, 255, 255].into()),
- IpEntry::Cidr(Cidr::new_v4([192, 168, 0, 0], 16).unwrap()),
- ])
- .unwrap()
- ),
- )
- .ok(),
- proto: Some(Protocol::Icmp(Icmp::new_code(IcmpCode::Named(
- "port-unreachable"
- )))),
- log: Some(LogLevel::Nolog),
- ..Default::default()
- }),
- }
- );
-
- rule = "IN BGP(ACCEPT) --log crit --iface eth0"
- .parse()
- .expect("valid rule");
-
- assert_eq!(
- rule,
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Accept,
- log: Some(LogLevel::Critical),
- fw_macro: Some("BGP".to_string()),
- iface: Some("eth0".to_string()),
- ..Default::default()
- }),
- }
- );
-
- rule = "IN ACCEPT --source dc/test --dest +dc/test"
- .parse()
- .expect("valid rule");
-
- assert_eq!(
- rule,
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Accept,
- ip: Some(
- IpMatch::new(
- IpAddrMatch::Alias(AliasName::new(AliasScope::Datacenter, "test")),
- IpAddrMatch::Set(IpsetName::new(IpsetScope::Datacenter, "test"),),
- )
- .unwrap()
- ),
- ..Default::default()
- }),
- }
- );
-
- rule = "IN REJECT".parse().expect("valid rule");
-
- assert_eq!(
- rule,
- Rule {
- disabled: false,
- comment: None,
- kind: Kind::Match(RuleMatch {
- dir: Direction::In,
- verdict: Verdict::Reject,
- ..Default::default()
- }),
- }
- );
-
- "IN DROP ---log crit"
- .parse::<Rule>()
- .expect_err("too many dashes in option");
-
- "IN DROP --log --iface eth0"
- .parse::<Rule>()
- .expect_err("no value for option");
-
- "IN DROP --log crit --iface"
- .parse::<Rule>()
- .expect_err("no value for option");
- }
-}
diff --git a/proxmox-ve-config/src/firewall/types/rule_match.rs b/proxmox-ve-config/src/firewall/types/rule_match.rs
deleted file mode 100644
index 94d8624..0000000
--- a/proxmox-ve-config/src/firewall/types/rule_match.rs
+++ /dev/null
@@ -1,977 +0,0 @@
-use std::collections::HashMap;
-use std::fmt;
-use std::str::FromStr;
-
-use serde::Deserialize;
-
-use anyhow::{bail, format_err, Error};
-use serde::de::IntoDeserializer;
-
-use proxmox_sortable_macro::sortable;
-
-use crate::firewall::parse::{match_name, match_non_whitespace, SomeStr};
-use crate::firewall::types::address::{Family, IpList};
-use crate::firewall::types::alias::AliasName;
-use crate::firewall::types::ipset::IpsetName;
-use crate::firewall::types::log::LogLevel;
-use crate::firewall::types::port::PortList;
-use crate::firewall::types::rule::{Direction, Verdict};
-
-#[derive(Clone, Debug, Default, Deserialize)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-#[serde(deny_unknown_fields, rename_all = "kebab-case")]
-pub(crate) struct RuleOptions {
- #[serde(alias = "p")]
- pub(crate) proto: Option<String>,
-
- pub(crate) dport: Option<String>,
- pub(crate) sport: Option<String>,
-
- pub(crate) dest: Option<String>,
- pub(crate) source: Option<String>,
-
- #[serde(alias = "i")]
- pub(crate) iface: Option<String>,
-
- pub(crate) log: Option<LogLevel>,
- pub(crate) icmp_type: Option<String>,
-}
-
-impl FromStr for RuleOptions {
- type Err = Error;
-
- fn from_str(mut line: &str) -> Result<Self, Self::Err> {
- let mut options = HashMap::new();
-
- loop {
- line = line.trim_start();
-
- if line.is_empty() {
- break;
- }
-
- line = line
- .strip_prefix('-')
- .ok_or_else(|| format_err!("expected an option starting with '-'"))?;
-
- // second dash is optional
- line = line.strip_prefix('-').unwrap_or(line);
-
- let param;
- (param, line) = match_name(line)
- .ok_or_else(|| format_err!("expected a parameter name after '-'"))?;
-
- let value;
- (value, line) = match_non_whitespace(line.trim_start())
- .ok_or_else(|| format_err!("expected a value for {param:?}"))?;
-
- if options.insert(param, SomeStr(value)).is_some() {
- bail!("duplicate option in rule: {param}")
- }
- }
-
- Ok(RuleOptions::deserialize(IntoDeserializer::<
- '_,
- crate::firewall::parse::SerdeStringError,
- >::into_deserializer(
- options
- ))?)
- }
-}
-
-#[derive(Clone, Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct RuleMatch {
- pub(crate) dir: Direction,
- pub(crate) verdict: Verdict,
- pub(crate) fw_macro: Option<String>,
-
- pub(crate) iface: Option<String>,
- pub(crate) log: Option<LogLevel>,
- pub(crate) ip: Option<IpMatch>,
- pub(crate) proto: Option<Protocol>,
-}
-
-impl RuleMatch {
- pub(crate) fn from_options(
- dir: Direction,
- verdict: Verdict,
- fw_macro: impl Into<Option<String>>,
- options: RuleOptions,
- ) -> Result<Self, Error> {
- if options.dport.is_some() && options.icmp_type.is_some() {
- bail!("dport and icmp-type are mutually exclusive");
- }
-
- let ip = IpMatch::from_options(&options)?;
- let proto = Protocol::from_options(&options)?;
-
- // todo: check protocol & IP Version compatibility
-
- Ok(Self {
- dir,
- verdict,
- fw_macro: fw_macro.into(),
- iface: options.iface,
- log: options.log,
- ip,
- proto,
- })
- }
-
- pub fn direction(&self) -> Direction {
- self.dir
- }
-
- pub fn iface(&self) -> Option<&str> {
- self.iface.as_deref()
- }
-
- pub fn verdict(&self) -> Verdict {
- self.verdict
- }
-
- pub fn fw_macro(&self) -> Option<&str> {
- self.fw_macro.as_deref()
- }
-
- pub fn log(&self) -> Option<LogLevel> {
- self.log
- }
-
- pub fn ip(&self) -> Option<&IpMatch> {
- self.ip.as_ref()
- }
-
- pub fn proto(&self) -> Option<&Protocol> {
- self.proto.as_ref()
- }
-}
-
-/// Returns `(Macro name, Verdict, RestOfTheLine)`.
-fn parse_action(line: &str) -> Result<(Option<&str>, Verdict, &str), Error> {
- let (verdict, line) =
- match_name(line).ok_or_else(|| format_err!("expected a verdict or macro name"))?;
-
- Ok(if let Some(line) = line.strip_prefix('(') {
- // <macro>(<verdict>)
-
- let macro_name = verdict;
- let (verdict, line) = match_name(line).ok_or_else(|| format_err!("expected a verdict"))?;
- let line = line
- .strip_prefix(')')
- .ok_or_else(|| format_err!("expected closing ')' after verdict"))?;
-
- let verdict: Verdict = verdict.parse()?;
-
- (Some(macro_name), verdict, line.trim_start())
- } else {
- (None, verdict.parse()?, line.trim_start())
- })
-}
-
-impl FromStr for RuleMatch {
- type Err = Error;
-
- fn from_str(line: &str) -> Result<Self, Self::Err> {
- let (dir, rest) = match_name(line).ok_or_else(|| format_err!("expected a direction"))?;
-
- let direction: Direction = dir.parse()?;
-
- let (fw_macro, verdict, rest) = parse_action(rest.trim_start())?;
-
- let options: RuleOptions = rest.trim_start().parse()?;
-
- Self::from_options(direction, verdict, fw_macro.map(str::to_string), options)
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct IpMatch {
- pub(crate) src: Option<IpAddrMatch>,
- pub(crate) dst: Option<IpAddrMatch>,
-}
-
-impl IpMatch {
- pub fn new(
- src: impl Into<Option<IpAddrMatch>>,
- dst: impl Into<Option<IpAddrMatch>>,
- ) -> Result<Self, Error> {
- let source = src.into();
- let dest = dst.into();
-
- if source.is_none() && dest.is_none() {
- bail!("either src or dst must be set")
- }
-
- if let (Some(IpAddrMatch::Ip(src)), Some(IpAddrMatch::Ip(dst))) = (&source, &dest) {
- if src.family() != dst.family() {
- bail!("src and dst family must be equal")
- }
- }
-
- let ip_match = Self {
- src: source,
- dst: dest,
- };
-
- Ok(ip_match)
- }
-
- fn from_options(options: &RuleOptions) -> Result<Option<Self>, Error> {
- let src = options
- .source
- .as_ref()
- .map(|elem| elem.parse::<IpAddrMatch>())
- .transpose()?;
-
- let dst = options
- .dest
- .as_ref()
- .map(|elem| elem.parse::<IpAddrMatch>())
- .transpose()?;
-
- if src.is_some() || dst.is_some() {
- Ok(Some(IpMatch::new(src, dst)?))
- } else {
- Ok(None)
- }
- }
-
- pub fn src(&self) -> Option<&IpAddrMatch> {
- self.src.as_ref()
- }
-
- pub fn dst(&self) -> Option<&IpAddrMatch> {
- self.dst.as_ref()
- }
-}
-
-#[derive(Clone, Debug, Deserialize)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum IpAddrMatch {
- Ip(IpList),
- Set(IpsetName),
- Alias(AliasName),
-}
-
-impl IpAddrMatch {
- pub fn family(&self) -> Option<Family> {
- if let IpAddrMatch::Ip(list) = self {
- return Some(list.family());
- }
-
- None
- }
-}
-
-impl FromStr for IpAddrMatch {
- type Err = Error;
-
- fn from_str(value: &str) -> Result<Self, Error> {
- if value.is_empty() {
- bail!("empty IP specification");
- }
-
- if let Ok(ip_list) = value.parse() {
- return Ok(IpAddrMatch::Ip(ip_list));
- }
-
- if let Ok(ipset) = value.parse() {
- return Ok(IpAddrMatch::Set(ipset));
- }
-
- if let Ok(name) = value.parse() {
- return Ok(IpAddrMatch::Alias(name));
- }
-
- bail!("invalid IP specification: {value}")
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum Protocol {
- Dccp(Ports),
- Sctp(Sctp),
- Tcp(Tcp),
- Udp(Udp),
- UdpLite(Ports),
- Icmp(Icmp),
- Icmpv6(Icmpv6),
- Named(String),
- Numeric(u8),
-}
-
-impl Protocol {
- pub(crate) fn from_options(options: &RuleOptions) -> Result<Option<Self>, Error> {
- let proto = match options.proto.as_deref() {
- Some(p) => p,
- None => return Ok(None),
- };
-
- Ok(Some(match proto {
- "dccp" | "33" => Protocol::Dccp(Ports::from_options(options)?),
- "sctp" | "132" => Protocol::Sctp(Sctp::from_options(options)?),
- "tcp" | "6" => Protocol::Tcp(Tcp::from_options(options)?),
- "udp" | "17" => Protocol::Udp(Udp::from_options(options)?),
- "udplite" | "136" => Protocol::UdpLite(Ports::from_options(options)?),
- "icmp" | "1" => Protocol::Icmp(Icmp::from_options(options)?),
- "ipv6-icmp" | "icmpv6" | "58" => Protocol::Icmpv6(Icmpv6::from_options(options)?),
- other => match other.parse::<u8>() {
- Ok(num) => Protocol::Numeric(num),
- Err(_) => Protocol::Named(other.to_string()),
- },
- }))
- }
-
- pub fn family(&self) -> Option<Family> {
- match self {
- Self::Icmp(_) => Some(Family::V4),
- Self::Icmpv6(_) => Some(Family::V6),
- _ => None,
- }
- }
-}
-
-#[derive(Clone, Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Udp {
- ports: Ports,
-}
-
-impl Udp {
- fn from_options(options: &RuleOptions) -> Result<Self, Error> {
- Ok(Self {
- ports: Ports::from_options(options)?,
- })
- }
-
- pub fn new(ports: Ports) -> Self {
- Self { ports }
- }
-
- pub fn ports(&self) -> &Ports {
- &self.ports
- }
-}
-
-impl From<Udp> for Protocol {
- fn from(value: Udp) -> Self {
- Protocol::Udp(value)
- }
-}
-
-#[derive(Clone, Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Ports {
- sport: Option<PortList>,
- dport: Option<PortList>,
-}
-
-impl Ports {
- pub fn new(sport: impl Into<Option<PortList>>, dport: impl Into<Option<PortList>>) -> Self {
- Self {
- sport: sport.into(),
- dport: dport.into(),
- }
- }
-
- fn from_options(options: &RuleOptions) -> Result<Self, Error> {
- Ok(Self {
- sport: options.sport.as_deref().map(|s| s.parse()).transpose()?,
- dport: options.dport.as_deref().map(|s| s.parse()).transpose()?,
- })
- }
-
- pub fn from_u16(sport: impl Into<Option<u16>>, dport: impl Into<Option<u16>>) -> Self {
- Self::new(
- sport.into().map(PortList::from),
- dport.into().map(PortList::from),
- )
- }
-
- pub fn sport(&self) -> Option<&PortList> {
- self.sport.as_ref()
- }
-
- pub fn dport(&self) -> Option<&PortList> {
- self.dport.as_ref()
- }
-}
-
-#[derive(Clone, Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Tcp {
- ports: Ports,
-}
-
-impl Tcp {
- pub fn new(ports: Ports) -> Self {
- Self { ports }
- }
-
- fn from_options(options: &RuleOptions) -> Result<Self, Error> {
- Ok(Self {
- ports: Ports::from_options(options)?,
- })
- }
-
- pub fn ports(&self) -> &Ports {
- &self.ports
- }
-}
-
-impl From<Tcp> for Protocol {
- fn from(value: Tcp) -> Self {
- Protocol::Tcp(value)
- }
-}
-
-#[derive(Clone, Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Sctp {
- ports: Ports,
-}
-
-impl Sctp {
- fn from_options(options: &RuleOptions) -> Result<Self, Error> {
- Ok(Self {
- ports: Ports::from_options(options)?,
- })
- }
-
- pub fn ports(&self) -> &Ports {
- &self.ports
- }
-}
-
-#[derive(Clone, Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Icmp {
- ty: Option<IcmpType>,
- code: Option<IcmpCode>,
-}
-
-impl Icmp {
- pub fn new_ty(ty: IcmpType) -> Self {
- Self {
- ty: Some(ty),
- ..Default::default()
- }
- }
-
- pub fn new_code(code: IcmpCode) -> Self {
- Self {
- code: Some(code),
- ..Default::default()
- }
- }
-
- fn from_options(options: &RuleOptions) -> Result<Self, Error> {
- if let Some(ty) = &options.icmp_type {
- return ty.parse();
- }
-
- Ok(Self::default())
- }
-
- pub fn ty(&self) -> Option<&IcmpType> {
- self.ty.as_ref()
- }
-
- pub fn code(&self) -> Option<&IcmpCode> {
- self.code.as_ref()
- }
-}
-
-impl FromStr for Icmp {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let mut this = Self::default();
-
- if let Ok(ty) = s.parse() {
- this.ty = Some(ty);
- return Ok(this);
- }
-
- if let Ok(code) = s.parse() {
- this.code = Some(code);
- return Ok(this);
- }
-
- bail!("supplied string is neither a valid icmp type nor code");
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum IcmpType {
- Numeric(u8),
- Named(&'static str),
- Any,
-}
-
-#[sortable]
-const ICMP_TYPES: [(&str, u8); 15] = sorted!([
- ("address-mask-reply", 18),
- ("address-mask-request", 17),
- ("destination-unreachable", 3),
- ("echo-reply", 0),
- ("echo-request", 8),
- ("info-reply", 16),
- ("info-request", 15),
- ("parameter-problem", 12),
- ("redirect", 5),
- ("router-advertisement", 9),
- ("router-solicitation", 10),
- ("source-quench", 4),
- ("time-exceeded", 11),
- ("timestamp-reply", 14),
- ("timestamp-request", 13),
-]);
-
-impl std::str::FromStr for IcmpType {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if s.eq_ignore_ascii_case("any") {
- return Ok(Self::Any);
- }
-
- if let Ok(ty) = s.trim().parse::<u8>() {
- return Ok(Self::Numeric(ty));
- }
-
- if let Ok(index) = ICMP_TYPES.binary_search_by(|v| v.0.cmp(s)) {
- return Ok(Self::Named(ICMP_TYPES[index].0));
- }
-
- bail!("{s:?} is not a valid icmp type");
- }
-}
-
-impl fmt::Display for IcmpType {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- IcmpType::Numeric(ty) => write!(f, "{ty}"),
- IcmpType::Named(ty) => write!(f, "{ty}"),
- IcmpType::Any => write!(f, "any"),
- }
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum IcmpCode {
- Numeric(u8),
- Named(&'static str),
-}
-
-#[sortable]
-const ICMP_CODES: [(&str, u8); 7] = sorted!([
- ("admin-prohibited", 13),
- ("host-prohibited", 10),
- ("host-unreachable", 1),
- ("net-prohibited", 9),
- ("net-unreachable", 0),
- ("port-unreachable", 3),
- ("prot-unreachable", 2),
-]);
-
-impl std::str::FromStr for IcmpCode {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if let Ok(code) = s.trim().parse::<u8>() {
- return Ok(Self::Numeric(code));
- }
-
- if let Ok(index) = ICMP_CODES.binary_search_by(|v| v.0.cmp(s)) {
- return Ok(Self::Named(ICMP_CODES[index].0));
- }
-
- bail!("{s:?} is not a valid icmp code");
- }
-}
-
-impl fmt::Display for IcmpCode {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- IcmpCode::Numeric(code) => write!(f, "{code}"),
- IcmpCode::Named(code) => write!(f, "{code}"),
- }
- }
-}
-
-#[derive(Clone, Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct Icmpv6 {
- pub ty: Option<Icmpv6Type>,
- pub code: Option<Icmpv6Code>,
-}
-
-impl Icmpv6 {
- pub fn new_ty(ty: Icmpv6Type) -> Self {
- Self {
- ty: Some(ty),
- ..Default::default()
- }
- }
-
- pub fn new_code(code: Icmpv6Code) -> Self {
- Self {
- code: Some(code),
- ..Default::default()
- }
- }
-
- fn from_options(options: &RuleOptions) -> Result<Self, Error> {
- if let Some(ty) = &options.icmp_type {
- return ty.parse();
- }
-
- Ok(Self::default())
- }
-
- pub fn ty(&self) -> Option<&Icmpv6Type> {
- self.ty.as_ref()
- }
-
- pub fn code(&self) -> Option<&Icmpv6Code> {
- self.code.as_ref()
- }
-}
-
-impl FromStr for Icmpv6 {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let mut this = Self::default();
-
- if let Ok(ty) = s.parse() {
- this.ty = Some(ty);
- return Ok(this);
- }
-
- if let Ok(code) = s.parse() {
- this.code = Some(code);
- return Ok(this);
- }
-
- bail!("supplied string is neither a valid icmpv6 type nor code");
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum Icmpv6Type {
- Numeric(u8),
- Named(&'static str),
- Any,
-}
-
-#[sortable]
-const ICMPV6_TYPES: [(&str, u8); 19] = sorted!([
- ("destination-unreachable", 1),
- ("echo-reply", 129),
- ("echo-request", 128),
- ("ind-neighbor-advert", 142),
- ("ind-neighbor-solicit", 141),
- ("mld-listener-done", 132),
- ("mld-listener-query", 130),
- ("mld-listener-reduction", 132),
- ("mld-listener-report", 131),
- ("mld2-listener-report", 143),
- ("nd-neighbor-advert", 136),
- ("nd-neighbor-solicit", 135),
- ("nd-redirect", 137),
- ("nd-router-advert", 134),
- ("nd-router-solicit", 133),
- ("packet-too-big", 2),
- ("parameter-problem", 4),
- ("router-renumbering", 138),
- ("time-exceeded", 3),
-]);
-
-impl std::str::FromStr for Icmpv6Type {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if s.eq_ignore_ascii_case("any") {
- return Ok(Self::Any);
- }
-
- if let Ok(ty) = s.trim().parse::<u8>() {
- return Ok(Self::Numeric(ty));
- }
-
- if let Ok(index) = ICMPV6_TYPES.binary_search_by(|v| v.0.cmp(s)) {
- return Ok(Self::Named(ICMPV6_TYPES[index].0));
- }
-
- bail!("{s:?} is not a valid icmpv6 type");
- }
-}
-
-impl fmt::Display for Icmpv6Type {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Icmpv6Type::Numeric(ty) => write!(f, "{ty}"),
- Icmpv6Type::Named(ty) => write!(f, "{ty}"),
- Icmpv6Type::Any => write!(f, "any"),
- }
- }
-}
-
-#[derive(Clone, Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum Icmpv6Code {
- Numeric(u8),
- Named(&'static str),
-}
-
-#[sortable]
-const ICMPV6_CODES: [(&str, u8); 6] = sorted!([
- ("addr-unreachable", 3),
- ("admin-prohibited", 1),
- ("no-route", 0),
- ("policy-fail", 5),
- ("port-unreachable", 4),
- ("reject-route", 6),
-]);
-
-impl std::str::FromStr for Icmpv6Code {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Error> {
- if let Ok(code) = s.trim().parse::<u8>() {
- return Ok(Self::Numeric(code));
- }
-
- if let Ok(index) = ICMPV6_CODES.binary_search_by(|v| v.0.cmp(s)) {
- return Ok(Self::Named(ICMPV6_CODES[index].0));
- }
-
- bail!("{s:?} is not a valid icmpv6 code");
- }
-}
-
-impl fmt::Display for Icmpv6Code {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- match self {
- Icmpv6Code::Numeric(code) => write!(f, "{code}"),
- Icmpv6Code::Named(code) => write!(f, "{code}"),
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use crate::firewall::types::{alias::AliasScope::Guest, Cidr};
-
- use super::*;
-
- #[test]
- fn test_parse_action() {
- assert_eq!(parse_action("REJECT").unwrap(), (None, Verdict::Reject, ""));
-
- assert_eq!(
- parse_action("SSH(ACCEPT) qweasd").unwrap(),
- (Some("SSH"), Verdict::Accept, "qweasd")
- );
- }
-
- #[test]
- fn test_parse_ip_addr_match() {
- for input in [
- "10.0.0.0/8",
- "10.0.0.0/8,192.168.0.0-192.168.255.255,172.16.0.1",
- "dc/test",
- "+guest/proxmox",
- ] {
- input.parse::<IpAddrMatch>().expect("valid ip match");
- }
-
- for input in [
- "10.0.0.0/",
- "10.0.0.0/8,192.168.256.0-192.168.255.255,172.16.0.1",
- "dcc/test",
- "+guest/",
- "",
- ] {
- input.parse::<IpAddrMatch>().expect_err("invalid ip match");
- }
- }
-
- #[test]
- fn test_parse_options() {
- let mut options: RuleOptions =
- "-p udp --sport 123 --dport 234 -source 127.0.0.1 --dest 127.0.0.1 -i ens1 --log crit"
- .parse()
- .expect("valid option string");
-
- assert_eq!(
- options,
- RuleOptions {
- proto: Some("udp".to_string()),
- sport: Some("123".to_string()),
- dport: Some("234".to_string()),
- source: Some("127.0.0.1".to_string()),
- dest: Some("127.0.0.1".to_string()),
- iface: Some("ens1".to_string()),
- log: Some(LogLevel::Critical),
- icmp_type: None,
- }
- );
-
- options = "".parse().expect("valid option string");
-
- assert_eq!(options, RuleOptions::default(),);
- }
-
- #[test]
- fn test_construct_ip_match() {
- IpMatch::new(
- IpAddrMatch::Ip(IpList::from(Cidr::new_v4([10, 0, 0, 0], 8).unwrap())),
- IpAddrMatch::Ip(IpList::from(Cidr::new_v4([10, 0, 0, 0], 8).unwrap())),
- )
- .expect("valid ip match");
-
- IpMatch::new(
- IpAddrMatch::Ip(IpList::from(Cidr::new_v4([10, 0, 0, 0], 8).unwrap())),
- IpAddrMatch::Alias(AliasName::new(Guest, "test")),
- )
- .expect("valid ip match");
-
- IpMatch::new(
- IpAddrMatch::Ip(IpList::from(Cidr::new_v4([10, 0, 0, 0], 8).unwrap())),
- IpAddrMatch::Ip(IpList::from(Cidr::new_v6([0x0000; 8], 8).unwrap())),
- )
- .expect_err("cannot mix ip families");
-
- IpMatch::new(None, None).expect_err("at least one ip must be set");
- }
-
- #[test]
- fn test_from_options() {
- let mut options = RuleOptions {
- proto: Some("tcp".to_string()),
- sport: Some("123".to_string()),
- dport: Some("234".to_string()),
- source: Some("192.168.0.1".to_string()),
- dest: Some("10.0.0.1".to_string()),
- iface: Some("eth123".to_string()),
- log: Some(LogLevel::Error),
- ..Default::default()
- };
-
- assert_eq!(
- Protocol::from_options(&options).unwrap().unwrap(),
- Protocol::Tcp(Tcp::new(Ports::from_u16(123, 234))),
- );
-
- assert_eq!(
- IpMatch::from_options(&options).unwrap().unwrap(),
- IpMatch::new(
- IpAddrMatch::Ip(IpList::from(Cidr::new_v4([192, 168, 0, 1], 32).unwrap()),),
- IpAddrMatch::Ip(IpList::from(Cidr::new_v4([10, 0, 0, 1], 32).unwrap()),)
- )
- .unwrap(),
- );
-
- options = RuleOptions::default();
-
- assert_eq!(Protocol::from_options(&options).unwrap(), None,);
-
- assert_eq!(IpMatch::from_options(&options).unwrap(), None,);
-
- options = RuleOptions {
- proto: Some("tcp".to_string()),
- sport: Some("qwe".to_string()),
- source: Some("qwe".to_string()),
- ..Default::default()
- };
-
- Protocol::from_options(&options).expect_err("invalid source port");
-
- IpMatch::from_options(&options).expect_err("invalid source address");
-
- options = RuleOptions {
- icmp_type: Some("port-unreachable".to_string()),
- dport: Some("123".to_string()),
- ..Default::default()
- };
-
- RuleMatch::from_options(Direction::In, Verdict::Drop, None, options)
- .expect_err("cannot mix dport and icmp-type");
- }
-
- #[test]
- fn test_parse_icmp() {
- let mut icmp: Icmp = "info-request".parse().expect("valid icmp type");
-
- assert_eq!(
- icmp,
- Icmp {
- ty: Some(IcmpType::Named("info-request")),
- code: None
- }
- );
-
- icmp = "12".parse().expect("valid icmp type");
-
- assert_eq!(
- icmp,
- Icmp {
- ty: Some(IcmpType::Numeric(12)),
- code: None
- }
- );
-
- icmp = "port-unreachable".parse().expect("valid icmp code");
-
- assert_eq!(
- icmp,
- Icmp {
- ty: None,
- code: Some(IcmpCode::Named("port-unreachable"))
- }
- );
- }
-
- #[test]
- fn test_parse_icmp6() {
- let mut icmp: Icmpv6 = "echo-reply".parse().expect("valid icmpv6 type");
-
- assert_eq!(
- icmp,
- Icmpv6 {
- ty: Some(Icmpv6Type::Named("echo-reply")),
- code: None
- }
- );
-
- icmp = "12".parse().expect("valid icmpv6 type");
-
- assert_eq!(
- icmp,
- Icmpv6 {
- ty: Some(Icmpv6Type::Numeric(12)),
- code: None
- }
- );
-
- icmp = "admin-prohibited".parse().expect("valid icmpv6 code");
-
- assert_eq!(
- icmp,
- Icmpv6 {
- ty: None,
- code: Some(Icmpv6Code::Named("admin-prohibited"))
- }
- );
- }
-}
diff --git a/proxmox-ve-config/src/guest/mod.rs b/proxmox-ve-config/src/guest/mod.rs
deleted file mode 100644
index 74fd8ab..0000000
--- a/proxmox-ve-config/src/guest/mod.rs
+++ /dev/null
@@ -1,115 +0,0 @@
-use core::ops::Deref;
-use std::collections::HashMap;
-
-use anyhow::{Context, Error};
-use serde::Deserialize;
-
-use proxmox_sys::nodename;
-use types::Vmid;
-
-pub mod types;
-pub mod vm;
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, Deserialize)]
-pub enum GuestType {
- #[serde(rename = "qemu")]
- Vm,
- #[serde(rename = "lxc")]
- Ct,
-}
-
-impl GuestType {
- pub fn iface_prefix(self) -> &'static str {
- match self {
- GuestType::Vm => "tap",
- GuestType::Ct => "veth",
- }
- }
-
- fn config_folder(&self) -> &'static str {
- match self {
- GuestType::Vm => "qemu-server",
- GuestType::Ct => "lxc",
- }
- }
-}
-
-#[derive(Deserialize)]
-pub struct GuestEntry {
- node: String,
-
- #[serde(rename = "type")]
- ty: GuestType,
-
- #[serde(rename = "version")]
- _version: usize,
-}
-
-impl GuestEntry {
- pub fn new(node: String, ty: GuestType) -> Self {
- Self {
- node,
- ty,
- _version: Default::default(),
- }
- }
-
- pub fn is_local(&self) -> bool {
- nodename() == self.node
- }
-
- pub fn ty(&self) -> &GuestType {
- &self.ty
- }
-}
-
-const VMLIST_CONFIG_PATH: &str = "/etc/pve/.vmlist";
-
-#[derive(Deserialize)]
-pub struct GuestMap {
- #[serde(rename = "version")]
- _version: usize,
- #[serde(rename = "ids", default)]
- guests: HashMap<Vmid, GuestEntry>,
-}
-
-impl From<HashMap<Vmid, GuestEntry>> for GuestMap {
- fn from(guests: HashMap<Vmid, GuestEntry>) -> Self {
- Self {
- guests,
- _version: Default::default(),
- }
- }
-}
-
-impl Deref for GuestMap {
- type Target = HashMap<Vmid, GuestEntry>;
-
- fn deref(&self) -> &Self::Target {
- &self.guests
- }
-}
-
-impl GuestMap {
- pub fn new() -> Result<Self, Error> {
- let data = std::fs::read(VMLIST_CONFIG_PATH)
- .with_context(|| format!("failed to read guest map from {VMLIST_CONFIG_PATH}"))?;
-
- serde_json::from_slice(&data).with_context(|| "failed to parse guest map".to_owned())
- }
-
- pub fn firewall_config_path(vmid: &Vmid) -> String {
- format!("/etc/pve/firewall/{}.fw", vmid)
- }
-
- /// returns the local configuration path for a given Vmid.
- ///
- /// The caller must ensure that the given Vmid exists and is local to the node
- pub fn config_path(vmid: &Vmid, entry: &GuestEntry) -> String {
- format!(
- "/etc/pve/local/{}/{}.conf",
- entry.ty().config_folder(),
- vmid
- )
- }
-}
diff --git a/proxmox-ve-config/src/guest/types.rs b/proxmox-ve-config/src/guest/types.rs
deleted file mode 100644
index 217c537..0000000
--- a/proxmox-ve-config/src/guest/types.rs
+++ /dev/null
@@ -1,38 +0,0 @@
-use std::fmt;
-use std::str::FromStr;
-
-use anyhow::{format_err, Error};
-
-#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord, Hash)]
-pub struct Vmid(u32);
-
-impl Vmid {
- pub fn new(id: u32) -> Self {
- Vmid(id)
- }
-}
-
-impl From<u32> for Vmid {
- fn from(value: u32) -> Self {
- Self::new(value)
- }
-}
-
-impl fmt::Display for Vmid {
- fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
- fmt::Display::fmt(&self.0, f)
- }
-}
-
-impl FromStr for Vmid {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- Ok(Self(
- s.parse()
- .map_err(|_| format_err!("not a valid vmid: {s:?}"))?,
- ))
- }
-}
-
-serde_plain::derive_deserialize_from_fromstr!(Vmid, "valid vmid");
diff --git a/proxmox-ve-config/src/guest/vm.rs b/proxmox-ve-config/src/guest/vm.rs
deleted file mode 100644
index 5b5866a..0000000
--- a/proxmox-ve-config/src/guest/vm.rs
+++ /dev/null
@@ -1,510 +0,0 @@
-use anyhow::{bail, Error};
-use core::fmt::Display;
-use std::io;
-use std::str::FromStr;
-use std::{collections::HashMap, net::Ipv6Addr};
-
-use proxmox_schema::property_string::PropertyIterator;
-
-use crate::firewall::parse::{match_digits, parse_bool};
-use crate::firewall::types::address::{Ipv4Cidr, Ipv6Cidr};
-
-#[derive(Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct MacAddress([u8; 6]);
-
-static LOCAL_PART: [u8; 8] = [0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00];
-static EUI64_MIDDLE_PART: [u8; 2] = [0xFF, 0xFE];
-
-impl MacAddress {
- /// generates a link local IPv6-address according to RFC 4291 (Appendix A)
- pub fn eui64_link_local_address(&self) -> Ipv6Addr {
- let head = &self.0[..3];
- let tail = &self.0[3..];
-
- let mut eui64_address: Vec<u8> = LOCAL_PART
- .iter()
- .chain(head.iter())
- .chain(EUI64_MIDDLE_PART.iter())
- .chain(tail.iter())
- .copied()
- .collect();
-
- // we need to flip the 7th bit of the first eui64 byte
- eui64_address[8] ^= 0x02;
-
- Ipv6Addr::from(
- TryInto::<[u8; 16]>::try_into(eui64_address).expect("is an u8 array with 16 entries"),
- )
- }
-}
-
-impl FromStr for MacAddress {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let split = s.split(':');
-
- let parsed = split
- .into_iter()
- .map(|elem| u8::from_str_radix(elem, 16))
- .collect::<Result<Vec<u8>, _>>()
- .map_err(Error::msg)?;
-
- if parsed.len() != 6 {
- bail!("Invalid amount of elements in MAC address!");
- }
-
- let address = &parsed.as_slice()[0..6];
- Ok(Self(address.try_into().unwrap()))
- }
-}
-
-impl Display for MacAddress {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(
- f,
- "{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}:{:<02X}",
- self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5]
- )
- }
-}
-
-#[derive(Debug, Clone, Copy)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub enum NetworkDeviceModel {
- VirtIO,
- Veth,
- E1000,
- Vmxnet3,
- RTL8139,
-}
-
-impl FromStr for NetworkDeviceModel {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- match s {
- "virtio" => Ok(NetworkDeviceModel::VirtIO),
- "e1000" => Ok(NetworkDeviceModel::E1000),
- "rtl8139" => Ok(NetworkDeviceModel::RTL8139),
- "vmxnet3" => Ok(NetworkDeviceModel::Vmxnet3),
- "veth" => Ok(NetworkDeviceModel::Veth),
- _ => bail!("Invalid network device model: {s}"),
- }
- }
-}
-
-#[derive(Debug)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct NetworkDevice {
- model: NetworkDeviceModel,
- mac_address: MacAddress,
- firewall: bool,
- ip: Option<Ipv4Cidr>,
- ip6: Option<Ipv6Cidr>,
-}
-
-impl NetworkDevice {
- pub fn model(&self) -> NetworkDeviceModel {
- self.model
- }
-
- pub fn mac_address(&self) -> &MacAddress {
- &self.mac_address
- }
-
- pub fn ip(&self) -> Option<&Ipv4Cidr> {
- self.ip.as_ref()
- }
-
- pub fn ip6(&self) -> Option<&Ipv6Cidr> {
- self.ip6.as_ref()
- }
-
- pub fn has_firewall(&self) -> bool {
- self.firewall
- }
-}
-
-impl FromStr for NetworkDevice {
- type Err = Error;
-
- fn from_str(s: &str) -> Result<Self, Self::Err> {
- let (mut ty, mut hwaddr, mut firewall, mut ip, mut ip6) = (None, None, true, None, None);
-
- for entry in PropertyIterator::new(s) {
- let (key, value) = entry.unwrap();
-
- if let Some(key) = key {
- match key {
- "type" | "model" => {
- ty = Some(NetworkDeviceModel::from_str(&value)?);
- }
- "hwaddr" | "macaddr" => {
- hwaddr = Some(MacAddress::from_str(&value)?);
- }
- "firewall" => {
- firewall = parse_bool(&value)?;
- }
- "ip" => {
- if value == "dhcp" {
- continue;
- }
-
- ip = Some(Ipv4Cidr::from_str(&value)?);
- }
- "ip6" => {
- if value == "dhcp" || value == "auto" {
- continue;
- }
-
- ip6 = Some(Ipv6Cidr::from_str(&value)?);
- }
- _ => {
- if let Ok(model) = NetworkDeviceModel::from_str(key) {
- ty = Some(model);
- hwaddr = Some(MacAddress::from_str(&value)?);
- }
- }
- }
- }
- }
-
- if let (Some(ty), Some(hwaddr)) = (ty, hwaddr) {
- return Ok(NetworkDevice {
- model: ty,
- mac_address: hwaddr,
- firewall,
- ip,
- ip6,
- });
- }
-
- bail!("No valid network device detected in string {s}");
- }
-}
-
-#[derive(Debug, Default)]
-#[cfg_attr(test, derive(Eq, PartialEq))]
-pub struct NetworkConfig {
- network_devices: HashMap<i64, NetworkDevice>,
-}
-
-impl NetworkConfig {
- pub fn new() -> Self {
- Self::default()
- }
-
- pub fn index_from_net_key(key: &str) -> Result<i64, Error> {
- if let Some(digits) = key.strip_prefix("net") {
- if let Some((digits, rest)) = match_digits(digits) {
- let index: i64 = digits.parse()?;
-
- if (0..31).contains(&index) && rest.is_empty() {
- return Ok(index);
- }
- }
- }
-
- bail!("No index found in net key string: {key}")
- }
-
- pub fn network_devices(&self) -> &HashMap<i64, NetworkDevice> {
- &self.network_devices
- }
-
- pub fn parse<R: io::BufRead>(input: R) -> Result<Self, Error> {
- let mut network_devices = HashMap::new();
-
- for line in input.lines() {
- let line = line?;
- let line = line.trim();
-
- if line.is_empty() || line.starts_with('#') {
- continue;
- }
-
- if line.starts_with('[') {
- break;
- }
-
- if line.starts_with("net") {
- log::trace!("parsing net config line: {line}");
-
- if let Some((mut key, mut value)) = line.split_once(':') {
- if key.is_empty() || value.is_empty() {
- continue;
- }
-
- key = key.trim();
- value = value.trim();
-
- if let Ok(index) = Self::index_from_net_key(key) {
- let network_device = NetworkDevice::from_str(value)?;
-
- let exists = network_devices.insert(index, network_device);
-
- if exists.is_some() {
- bail!("Duplicated config key detected: {key}");
- }
- } else {
- bail!("Encountered invalid net key in cfg: {key}");
- }
- }
- }
- }
-
- Ok(Self { network_devices })
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_parse_mac_address() {
- for input in [
- "aa:aa:aa:11:22:33",
- "AA:BB:FF:11:22:33",
- "bc:24:11:AA:bb:Ef",
- ] {
- let mac_address = input.parse::<MacAddress>().expect("valid mac address");
-
- assert_eq!(input.to_uppercase(), mac_address.to_string());
- }
-
- for input in [
- "aa:aa:aa:11:22:33:aa",
- "AA:BB:FF:11:22",
- "AA:BB:GG:11:22:33",
- "AABBGG112233",
- "",
- ] {
- input
- .parse::<MacAddress>()
- .expect_err("invalid mac address");
- }
- }
-
- #[test]
- fn test_eui64_link_local_address() {
- let mac_address: MacAddress = "BC:24:11:49:8D:75".parse().expect("valid MAC address");
-
- let link_local_address =
- Ipv6Addr::from_str("fe80::be24:11ff:fe49:8d75").expect("valid IPv6 address");
-
- assert_eq!(link_local_address, mac_address.eui64_link_local_address());
- }
-
- #[test]
- fn test_parse_network_device() {
- let mut network_device: NetworkDevice =
- "virtio=AA:AA:AA:17:19:81,bridge=public,firewall=1,queues=4"
- .parse()
- .expect("valid network configuration");
-
- assert_eq!(
- network_device,
- NetworkDevice {
- model: NetworkDeviceModel::VirtIO,
- mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0x17, 0x19, 0x81]),
- firewall: true,
- ip: None,
- ip6: None,
- }
- );
-
- network_device = "model=virtio,macaddr=AA:AA:AA:17:19:81,bridge=public,firewall=1,queues=4"
- .parse()
- .expect("valid network configuration");
-
- assert_eq!(
- network_device,
- NetworkDevice {
- model: NetworkDeviceModel::VirtIO,
- mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0x17, 0x19, 0x81]),
- firewall: true,
- ip: None,
- ip6: None,
- }
- );
-
- network_device =
- "name=eth0,bridge=public,firewall=0,hwaddr=AA:AA:AA:E2:3E:24,ip=dhcp,type=veth"
- .parse()
- .expect("valid network configuration");
-
- assert_eq!(
- network_device,
- NetworkDevice {
- model: NetworkDeviceModel::Veth,
- mac_address: MacAddress([0xAA, 0xAA, 0xAA, 0xE2, 0x3E, 0x24]),
- firewall: false,
- ip: None,
- ip6: None,
- }
- );
-
- "model=virtio"
- .parse::<NetworkDevice>()
- .expect_err("invalid network configuration");
-
- "bridge=public,firewall=0"
- .parse::<NetworkDevice>()
- .expect_err("invalid network configuration");
-
- "".parse::<NetworkDevice>()
- .expect_err("invalid network configuration");
-
- "name=eth0,bridge=public,firewall=0,hwaddr=AA:AA:AG:E2:3E:24,ip=dhcp,type=veth"
- .parse::<NetworkDevice>()
- .expect_err("invalid network configuration");
- }
-
- #[test]
- fn test_parse_network_confg() {
- let mut guest_config = "\
-boot: order=scsi0;net0
-cores: 4
-cpu: host
-memory: 8192
-meta: creation-qemu=8.0.2,ctime=1700141675
-name: hoan-sdn
-net0: virtio=AA:BB:CC:F2:FE:75,bridge=public
-numa: 0
-ostype: l26
-parent: uwu
-scsi0: local-lvm:vm-999-disk-0,discard=on,iothread=1,size=32G
-scsihw: virtio-scsi-single
-smbios1: uuid=addb0cc6-0393-4269-a504-1eb46604cb8a
-sockets: 1
-vmgenid: 13bcbb05-3608-4d74-bf4f-d5d20c3538e8
-
-[snapshot]
-boot: order=scsi0;ide2;net0
-cores: 4
-cpu: x86-64-v2-AES
-ide2: NFS-iso:iso/proxmox-ve_8.0-2.iso,media=cdrom,size=1166488K
-memory: 8192
-meta: creation-qemu=8.0.2,ctime=1700141675
-name: test
-net2: virtio=AA:AA:AA:F2:FE:75,bridge=public,firewall=1
-numa: 0
-ostype: l26
-parent: pre-SDN
-scsi0: local-lvm:vm-999-disk-0,discard=on,iothread=1,size=32G
-scsihw: virtio-scsi-single
-smbios1: uuid=addb0cc6-0393-4269-a504-1eb46604cb8a
-snaptime: 1700143513
-sockets: 1
-vmgenid: 706fbe99-d28b-4047-a9cd-3677c859ca8a
-
-[snapshott]
-boot: order=scsi0;ide2;net0
-cores: 4
-cpu: host
-ide2: NFS-iso:iso/proxmox-ve_8.0-2.iso,media=cdrom,size=1166488K
-memory: 8192
-meta: creation-qemu=8.0.2,ctime=1700141675
-name: hoan-sdn
-net0: virtio=AA:AA:FF:F2:FE:75,bridge=public,firewall=0
-numa: 0
-ostype: l26
-parent: SDN
-scsi0: local-lvm:vm-999-disk-0,discard=on,iothread=1,size=32G
-scsihw: virtio-scsi-single
-smbios1: uuid=addb0cc6-0393-4269-a504-1eb46604cb8a
-snaptime: 1700158473
-sockets: 1
-vmgenid: 706fbe99-d28b-4047-a9cd-3677c859ca8a"
- .as_bytes();
-
- let mut network_config: NetworkConfig =
- NetworkConfig::parse(guest_config).expect("valid network configuration");
-
- assert_eq!(network_config.network_devices().len(), 1);
-
- assert_eq!(
- network_config.network_devices()[&0],
- NetworkDevice {
- model: NetworkDeviceModel::VirtIO,
- mac_address: MacAddress([0xAA, 0xBB, 0xCC, 0xF2, 0xFE, 0x75]),
- firewall: true,
- ip: None,
- ip6: None,
- }
- );
-
- guest_config = "\
-arch: amd64
-cores: 1
-features: nesting=1
-hostname: dnsct
-memory: 512
-net0: name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:11,ip=dhcp,type=veth
-net2: name=eth0,bridge=data,firewall=0,hwaddr=BC:24:11:47:83:12,ip=123.123.123.123/24,type=veth
-net5: name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:13,ip6=fd80::1/64,type=veth
-ostype: alpine
-rootfs: local-lvm:vm-10001-disk-0,size=1G
-swap: 512
-unprivileged: 1"
- .as_bytes();
-
- network_config = NetworkConfig::parse(guest_config).expect("valid network configuration");
-
- assert_eq!(network_config.network_devices().len(), 3);
-
- assert_eq!(
- network_config.network_devices()[&0],
- NetworkDevice {
- model: NetworkDeviceModel::Veth,
- mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, 0x11]),
- firewall: true,
- ip: None,
- ip6: None,
- }
- );
-
- assert_eq!(
- network_config.network_devices()[&2],
- NetworkDevice {
- model: NetworkDeviceModel::Veth,
- mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, 0x12]),
- firewall: false,
- ip: Some(Ipv4Cidr::from_str("123.123.123.123/24").expect("valid ipv4")),
- ip6: None,
- }
- );
-
- assert_eq!(
- network_config.network_devices()[&5],
- NetworkDevice {
- model: NetworkDeviceModel::Veth,
- mac_address: MacAddress([0xBC, 0x24, 0x11, 0x47, 0x83, 0x13]),
- firewall: true,
- ip: None,
- ip6: Some(Ipv6Cidr::from_str("fd80::1/64").expect("valid ipv6")),
- }
- );
-
- NetworkConfig::parse(
- "netqwe: name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:11,ip=dhcp,type=veth"
- .as_bytes(),
- )
- .expect_err("invalid net key");
-
- NetworkConfig::parse(
- "net0 name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:11,ip=dhcp,type=veth"
- .as_bytes(),
- )
- .expect_err("invalid net key");
-
- NetworkConfig::parse(
- "net33: name=eth0,bridge=data,firewall=1,hwaddr=BC:24:11:47:83:11,ip=dhcp,type=veth"
- .as_bytes(),
- )
- .expect_err("invalid net key");
- }
-}
diff --git a/proxmox-ve-config/src/host/mod.rs b/proxmox-ve-config/src/host/mod.rs
deleted file mode 100644
index b5614dd..0000000
--- a/proxmox-ve-config/src/host/mod.rs
+++ /dev/null
@@ -1 +0,0 @@
-pub mod utils;
diff --git a/proxmox-ve-config/src/host/utils.rs b/proxmox-ve-config/src/host/utils.rs
deleted file mode 100644
index b1dc8e9..0000000
--- a/proxmox-ve-config/src/host/utils.rs
+++ /dev/null
@@ -1,70 +0,0 @@
-use std::net::{IpAddr, ToSocketAddrs};
-
-use crate::firewall::types::Cidr;
-
-use nix::sys::socket::{AddressFamily, SockaddrLike};
-use proxmox_sys::nodename;
-
-/// gets a list of IPs that the hostname of this node resolves to
-///
-/// panics if the local hostname is not resolvable
-pub fn host_ips() -> Vec<IpAddr> {
- let hostname = nodename();
-
- log::trace!("resolving hostname");
-
- format!("{hostname}:0")
- .to_socket_addrs()
- .expect("local hostname is resolvable")
- .map(|addr| addr.ip())
- .collect()
-}
-
-/// gets a list of all configured CIDRs on all network interfaces of this host
-///
-/// panics if unable to query the current network configuration
-pub fn network_interface_cidrs() -> Vec<Cidr> {
- use nix::ifaddrs::getifaddrs;
-
- log::trace!("reading networking interface list");
-
- let mut cidrs = Vec::new();
-
- let interfaces = getifaddrs().expect("should be able to query network interfaces");
-
- for interface in interfaces {
- if let (Some(address), Some(netmask)) = (interface.address, interface.netmask) {
- match (address.family(), netmask.family()) {
- (Some(AddressFamily::Inet), Some(AddressFamily::Inet)) => {
- let address = address.as_sockaddr_in().expect("is an IPv4 address").ip();
-
- let netmask = netmask
- .as_sockaddr_in()
- .expect("is an IPv4 address")
- .ip()
- .count_ones()
- .try_into()
- .expect("count_ones of u32 is < u8_max");
-
- cidrs.push(Cidr::new_v4(address, netmask).expect("netmask is valid"));
- }
- (Some(AddressFamily::Inet6), Some(AddressFamily::Inet6)) => {
- let address = address.as_sockaddr_in6().expect("is an IPv6 address").ip();
-
- let netmask_address =
- netmask.as_sockaddr_in6().expect("is an IPv6 address").ip();
-
- let netmask = u128::from_be_bytes(netmask_address.octets())
- .count_ones()
- .try_into()
- .expect("count_ones of u128 is < u8_max");
-
- cidrs.push(Cidr::new_v6(address, netmask).expect("netmask is valid"));
- }
- _ => continue,
- }
- }
- }
-
- cidrs
-}
diff --git a/proxmox-ve-config/src/lib.rs b/proxmox-ve-config/src/lib.rs
deleted file mode 100644
index 856b14f..0000000
--- a/proxmox-ve-config/src/lib.rs
+++ /dev/null
@@ -1,3 +0,0 @@
-pub mod firewall;
-pub mod guest;
-pub mod host;
--
2.39.5
_______________________________________________
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-11-12 12:36 UTC|newest]
Thread overview: 26+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-11-12 12:25 [pve-devel] [PATCH docs/firewall/manager/proxmox{-ve-rs, -firewall, -perl-rs} v3 00/24] autogenerate ipsets for sdn objects Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 01/24] debian: add files for packaging Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 02/24] firewall: add sdn scope for ipsets Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 03/24] firewall: add ip range types Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 04/24] firewall: address: use new iprange type for ip entries Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 05/24] ipset: add range variant to addresses Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 06/24] iprange: add methods for converting an ip range to cidrs Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 07/24] ipset: address: add helper methods Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 08/24] firewall: guest: derive traits according to rust api guidelines Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 09/24] common: add allowlist Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 10/24] sdn: add name types Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 11/24] sdn: add ipam module Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 12/24] sdn: ipam: add method for generating ipsets Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 13/24] sdn: add config module Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 14/24] sdn: config: add method for generating ipsets Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 15/24] tests: add sdn config tests Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-ve-rs v3 16/24] tests: add ipam tests Stefan Hanreich
2024-11-12 19:16 ` [pve-devel] partially-applied-series: " Thomas Lamprecht
2024-11-12 12:25 ` Stefan Hanreich [this message]
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-firewall v3 18/24] config: tests: add support for loading sdn and ipam config Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH proxmox-firewall v3 19/24] ipsets: autogenerate ipsets for vnets and ipam Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH pve-firewall v3 20/24] add support for loading sdn firewall configuration Stefan Hanreich
2024-11-12 12:25 ` [pve-devel] [PATCH pve-firewall v3 21/24] api: load sdn ipsets Stefan Hanreich
2024-11-12 12:26 ` [pve-devel] [PATCH proxmox-perl-rs v3 22/24] add PVE::RS::Firewall::SDN module Stefan Hanreich
2024-11-12 12:26 ` [pve-devel] [PATCH pve-manager v3 23/24] firewall: add sdn scope to IPRefSelector Stefan Hanreich
2024-11-12 12:26 ` [pve-devel] [PATCH pve-docs v3 24/24] sdn: add documentation for firewall integration 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=20241112122602.88598-18-s.hanreich@proxmox.com \
--to=s.hanreich@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