public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Dietmar Maurer <dietmar@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [RFC proxmox 11/22] firewall-api-types: add FirewallIcmpType
Date: Mon, 16 Feb 2026 11:43:49 +0100	[thread overview]
Message-ID: <20260216104401.3959270-12-dietmar@proxmox.com> (raw)
In-Reply-To: <20260216104401.3959270-1-dietmar@proxmox.com>

This adds the `FirewallIcmpType` enum, which can represent ICMP types
either as a named variant (using `FirewallIcmpTypeName`) or as a raw
numeric value (u8).

The `FirewallIcmpTypeName` enum covers standard ICMPv4 and ICMPv6 types,
including deprecated ones and those with codes (e.g., `destination-unreachable`).

It provides `ipv4()` and `ipv6()` helper methods to check if a specific
named type is valid for the respective protocol.

Serialization and deserialization are handled via `serde` and `serde_plain`,
allowing for easy conversion to/from strings.

Includes tests for serialization, deserialization, and protocol validity checks.
---
 proxmox-firewall-api-types/src/icmp_type.rs | 555 ++++++++++++++++++++
 proxmox-firewall-api-types/src/lib.rs       |   3 +
 2 files changed, 558 insertions(+)
 create mode 100644 proxmox-firewall-api-types/src/icmp_type.rs

diff --git a/proxmox-firewall-api-types/src/icmp_type.rs b/proxmox-firewall-api-types/src/icmp_type.rs
new file mode 100644
index 00000000..b45c1505
--- /dev/null
+++ b/proxmox-firewall-api-types/src/icmp_type.rs
@@ -0,0 +1,555 @@
+use std::fmt;
+
+use anyhow::{bail, Error};
+use serde::{Deserialize, Serialize};
+
+#[cfg(feature = "enum-fallback")]
+use proxmox_fixed_string::FixedString;
+
+use proxmox_schema::{ApiStringFormat, ApiType, Schema, StringSchema};
+
+#[derive(Debug, Copy, Clone, PartialEq)]
+/// ICMP type, either named or numeric.
+pub enum FirewallIcmpType {
+    /// Named ICMP type, e.g. "echo-request"
+    Named(FirewallIcmpTypeName),
+    /// Numeric ICMP type, e.g. "8"
+    Numeric(u8),
+}
+
+impl ApiType for FirewallIcmpType {
+    const API_SCHEMA: Schema = StringSchema::new(
+        r#"ICMP type, either named or numeric.
+        Only valid if proto equals 'icmp' or 'icmpv6'/'ipv6-icmp'."#,
+    )
+    .format(&ApiStringFormat::VerifyFn(verify_firewall_icmp_type))
+    .schema();
+}
+
+fn verify_firewall_icmp_type(value: &str) -> Result<(), Error> {
+    value.parse::<FirewallIcmpType>().map(|_| ())
+}
+
+impl std::str::FromStr for FirewallIcmpType {
+    type Err = Error;
+
+    fn from_str(s: &str) -> Result<Self, Error> {
+        let s = s.trim();
+
+        if let Ok(ty) = s.parse::<u8>() {
+            return Ok(Self::Numeric(ty));
+        }
+
+        if let Ok(named) = serde_plain::from_str::<FirewallIcmpTypeName>(s) {
+            return Ok(Self::Named(named));
+        }
+
+        bail!("{s:?} is not a valid icmp type");
+    }
+}
+
+impl fmt::Display for FirewallIcmpType {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        match self {
+            FirewallIcmpType::Numeric(ty) => write!(f, "{ty}"),
+            FirewallIcmpType::Named(ty) => write!(f, "{ty}"),
+        }
+    }
+}
+
+serde_plain::derive_deserialize_from_fromstr!(FirewallIcmpType, "valid icmp type name or number");
+serde_plain::derive_serialize_from_display!(FirewallIcmpType);
+
+#[derive(Serialize, Deserialize, Debug, Copy, Clone, PartialEq)]
+#[serde(rename_all = "kebab-case")]
+/// Named ICMP type, e.g. "echo-request".
+pub enum FirewallIcmpTypeName {
+    Any,
+
+    // IPv4 specific
+    HostUnreachable,     // 3:1 (ICMP-TYPE, ICMP-CODE)
+    ProtocolUnreachable, // 3:2
+    PortUnreachable,     // 3:3
+    FragmentationNeeded, // 3:4
+    SourceRouteFailed,   // 3:5
+    NetworkUnknown,      // 3:6
+    HostUnknown,         // 3:7
+    NetworkProhibited,   // 3:9
+    HostProhibited,      // 3:10
+    #[serde(rename = "TOS-network-unreachable")]
+    TOSNetworkUnreachable, // 3:11
+    #[serde(rename = "TOS-host-unreachable")]
+    TOSHostUnreachable, // 3:12
+    CommunicationProhibited, // 3:13
+    HostPrecedenceViolation, // 3:14
+    PrecedenceCutoff,    // 3:15
+    SourceQuench,        // 4
+    NetworkRedirect,     // 5:0
+    HostRedirect,        // 5:1
+    #[serde(rename = "TOS-network-redirect")]
+    TOSNetworkRedirect, // 5:2
+    #[serde(rename = "TOS-host-redirect")]
+    TOSHostRedirect, // 5:3
+    TimestampRequest,    // 13
+    TimestampReply,      // 14
+    AddressMaskRequest,  // 17
+    AddressMaskReply,    // 18
+    RouterAdvertisement, // 9
+    RouterSolicitation,  // 10
+    IpHeaderBad,         // 12:0
+    RequiredOptionMissing, // 12:1
+
+    // IPv6 specific
+    NoRoute,                // 1:0
+    BeyondScope,            // 1:2
+    AddressUnreachable,     // 1:3
+    FailedPolicy,           // 1:5
+    RejectRoute,            // 1:6
+    PacketTooBig,           // 2
+    BadHeader,              // 4:0
+    UnknownHeaderType,      // 4:1
+    UnknownOption,          // 4:2
+    NeighborSolicitation,   // 135
+    NeighbourSolicitation,  // 135
+    NeighborAdvertisement,  // 136
+    NeighbourAdvertisement, // 136
+
+    // Common
+    DestinationUnreachable,  // 3:0 (v4), 1:0 (v6) ? v6 has no-route
+    NetworkUnreachable,      // 3:0
+    EchoReply,               // 0 (v4), 129 (v6)
+    EchoRequest,             // 8 (v4), 128 (v6)
+    TimeExceeded,            // 11 (v4), 3 (v6)
+    TtlZeroDuringTransit,    // 11:0 (v4), 3:0 (v6)
+    TtlZeroDuringReassembly, // 11:1 (v4), 3:1 (v6)
+    ParameterProblem,        // 12 (v4), 4 (v6)
+    Redirect,                // 5 (v4), 137 (v6)
+
+    #[cfg(feature = "enum-fallback")]
+    #[serde(untagged)]
+    /// Unknwon
+    UnknownEnumValue(FixedString),
+}
+
+serde_plain::derive_display_from_serialize!(FirewallIcmpTypeName);
+serde_plain::derive_fromstr_from_deserialize!(FirewallIcmpTypeName);
+
+impl FirewallIcmpTypeName {
+    pub const fn ipv4(&self) -> bool {
+        match self {
+            FirewallIcmpTypeName::Any => true,
+            FirewallIcmpTypeName::EchoReply => true,
+            FirewallIcmpTypeName::DestinationUnreachable => true,
+            FirewallIcmpTypeName::NetworkUnreachable => true,
+            FirewallIcmpTypeName::HostUnreachable => true,
+            FirewallIcmpTypeName::ProtocolUnreachable => true,
+            FirewallIcmpTypeName::PortUnreachable => true,
+            FirewallIcmpTypeName::FragmentationNeeded => true,
+            FirewallIcmpTypeName::SourceRouteFailed => true,
+            FirewallIcmpTypeName::NetworkUnknown => true,
+            FirewallIcmpTypeName::HostUnknown => true,
+            FirewallIcmpTypeName::NetworkProhibited => true,
+            FirewallIcmpTypeName::HostProhibited => true,
+            FirewallIcmpTypeName::TOSNetworkUnreachable => true,
+            FirewallIcmpTypeName::TOSHostUnreachable => true,
+            FirewallIcmpTypeName::CommunicationProhibited => true,
+            FirewallIcmpTypeName::HostPrecedenceViolation => true,
+            FirewallIcmpTypeName::PrecedenceCutoff => true,
+            FirewallIcmpTypeName::SourceQuench => true,
+            FirewallIcmpTypeName::Redirect => true,
+            FirewallIcmpTypeName::NetworkRedirect => true,
+            FirewallIcmpTypeName::HostRedirect => true,
+            FirewallIcmpTypeName::TOSNetworkRedirect => true,
+            FirewallIcmpTypeName::TOSHostRedirect => true,
+            FirewallIcmpTypeName::EchoRequest => true,
+            FirewallIcmpTypeName::RouterAdvertisement => true,
+            FirewallIcmpTypeName::RouterSolicitation => true,
+            FirewallIcmpTypeName::TimeExceeded => true,
+            FirewallIcmpTypeName::TtlZeroDuringTransit => true,
+            FirewallIcmpTypeName::TtlZeroDuringReassembly => true,
+            FirewallIcmpTypeName::ParameterProblem => true,
+            FirewallIcmpTypeName::IpHeaderBad => true,
+            FirewallIcmpTypeName::RequiredOptionMissing => true,
+            FirewallIcmpTypeName::TimestampRequest => true,
+            FirewallIcmpTypeName::TimestampReply => true,
+            FirewallIcmpTypeName::AddressMaskRequest => true,
+            FirewallIcmpTypeName::AddressMaskReply => true,
+            _ => false,
+        }
+    }
+
+    pub const fn ipv6(&self) -> bool {
+        match self {
+            FirewallIcmpTypeName::Any => true,
+            FirewallIcmpTypeName::EchoReply => true,
+            FirewallIcmpTypeName::DestinationUnreachable => true,
+            FirewallIcmpTypeName::PacketTooBig => true,
+            FirewallIcmpTypeName::TimeExceeded => true,
+            FirewallIcmpTypeName::ParameterProblem => true,
+            FirewallIcmpTypeName::EchoRequest => true,
+            FirewallIcmpTypeName::RouterSolicitation => true,
+            FirewallIcmpTypeName::RouterAdvertisement => true,
+            FirewallIcmpTypeName::NeighborSolicitation => true,
+            FirewallIcmpTypeName::NeighbourSolicitation => true,
+            FirewallIcmpTypeName::NeighborAdvertisement => true,
+            FirewallIcmpTypeName::NeighbourAdvertisement => true,
+            FirewallIcmpTypeName::Redirect => true,
+            FirewallIcmpTypeName::NoRoute => true,
+            FirewallIcmpTypeName::CommunicationProhibited => true,
+            FirewallIcmpTypeName::BeyondScope => true,
+            FirewallIcmpTypeName::AddressUnreachable => true,
+            FirewallIcmpTypeName::PortUnreachable => true,
+            FirewallIcmpTypeName::FailedPolicy => true,
+            FirewallIcmpTypeName::RejectRoute => true,
+            FirewallIcmpTypeName::TtlZeroDuringTransit => true,
+            FirewallIcmpTypeName::TtlZeroDuringReassembly => true,
+            FirewallIcmpTypeName::BadHeader => true,
+            FirewallIcmpTypeName::UnknownHeaderType => true,
+            FirewallIcmpTypeName::UnknownOption => true,
+            _ => false,
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+
+    fn test_icmp_types() {
+        // (name, variant, ipv4, ipv6)
+        let tests = [
+            ("any", FirewallIcmpTypeName::Any, true, true),
+            ("echo-reply", FirewallIcmpTypeName::EchoReply, true, true),
+            (
+                "destination-unreachable",
+                FirewallIcmpTypeName::DestinationUnreachable,
+                true,
+                true,
+            ),
+            (
+                "network-unreachable",
+                FirewallIcmpTypeName::NetworkUnreachable,
+                true,
+                false,
+            ),
+            (
+                "host-unreachable",
+                FirewallIcmpTypeName::HostUnreachable,
+                true,
+                false,
+            ),
+            (
+                "protocol-unreachable",
+                FirewallIcmpTypeName::ProtocolUnreachable,
+                true,
+                false,
+            ),
+            (
+                "port-unreachable",
+                FirewallIcmpTypeName::PortUnreachable,
+                true,
+                true,
+            ),
+            (
+                "fragmentation-needed",
+                FirewallIcmpTypeName::FragmentationNeeded,
+                true,
+                false,
+            ),
+            (
+                "source-route-failed",
+                FirewallIcmpTypeName::SourceRouteFailed,
+                true,
+                false,
+            ),
+            (
+                "network-unknown",
+                FirewallIcmpTypeName::NetworkUnknown,
+                true,
+                false,
+            ),
+            (
+                "host-unknown",
+                FirewallIcmpTypeName::HostUnknown,
+                true,
+                false,
+            ),
+            (
+                "network-prohibited",
+                FirewallIcmpTypeName::NetworkProhibited,
+                true,
+                false,
+            ),
+            (
+                "host-prohibited",
+                FirewallIcmpTypeName::HostProhibited,
+                true,
+                false,
+            ),
+            (
+                "TOS-network-unreachable",
+                FirewallIcmpTypeName::TOSNetworkUnreachable,
+                true,
+                false,
+            ),
+            (
+                "TOS-host-unreachable",
+                FirewallIcmpTypeName::TOSHostUnreachable,
+                true,
+                false,
+            ),
+            (
+                "communication-prohibited",
+                FirewallIcmpTypeName::CommunicationProhibited,
+                true,
+                true,
+            ),
+            (
+                "host-precedence-violation",
+                FirewallIcmpTypeName::HostPrecedenceViolation,
+                true,
+                false,
+            ),
+            (
+                "precedence-cutoff",
+                FirewallIcmpTypeName::PrecedenceCutoff,
+                true,
+                false,
+            ),
+            (
+                "source-quench",
+                FirewallIcmpTypeName::SourceQuench,
+                true,
+                false,
+            ),
+            ("redirect", FirewallIcmpTypeName::Redirect, true, true),
+            (
+                "network-redirect",
+                FirewallIcmpTypeName::NetworkRedirect,
+                true,
+                false,
+            ),
+            (
+                "host-redirect",
+                FirewallIcmpTypeName::HostRedirect,
+                true,
+                false,
+            ),
+            (
+                "TOS-network-redirect",
+                FirewallIcmpTypeName::TOSNetworkRedirect,
+                true,
+                false,
+            ),
+            (
+                "TOS-host-redirect",
+                FirewallIcmpTypeName::TOSHostRedirect,
+                true,
+                false,
+            ),
+            (
+                "echo-request",
+                FirewallIcmpTypeName::EchoRequest,
+                true,
+                true,
+            ),
+            (
+                "router-advertisement",
+                FirewallIcmpTypeName::RouterAdvertisement,
+                true,
+                true,
+            ),
+            (
+                "router-solicitation",
+                FirewallIcmpTypeName::RouterSolicitation,
+                true,
+                true,
+            ),
+            (
+                "time-exceeded",
+                FirewallIcmpTypeName::TimeExceeded,
+                true,
+                true,
+            ),
+            (
+                "ttl-zero-during-transit",
+                FirewallIcmpTypeName::TtlZeroDuringTransit,
+                true,
+                true,
+            ),
+            (
+                "ttl-zero-during-reassembly",
+                FirewallIcmpTypeName::TtlZeroDuringReassembly,
+                true,
+                true,
+            ),
+            (
+                "parameter-problem",
+                FirewallIcmpTypeName::ParameterProblem,
+                true,
+                true,
+            ),
+            (
+                "ip-header-bad",
+                FirewallIcmpTypeName::IpHeaderBad,
+                true,
+                false,
+            ),
+            (
+                "required-option-missing",
+                FirewallIcmpTypeName::RequiredOptionMissing,
+                true,
+                false,
+            ),
+            (
+                "timestamp-request",
+                FirewallIcmpTypeName::TimestampRequest,
+                true,
+                false,
+            ),
+            (
+                "timestamp-reply",
+                FirewallIcmpTypeName::TimestampReply,
+                true,
+                false,
+            ),
+            (
+                "address-mask-request",
+                FirewallIcmpTypeName::AddressMaskRequest,
+                true,
+                false,
+            ),
+            (
+                "address-mask-reply",
+                FirewallIcmpTypeName::AddressMaskReply,
+                true,
+                false,
+            ),
+            ("no-route", FirewallIcmpTypeName::NoRoute, false, true),
+            (
+                "beyond-scope",
+                FirewallIcmpTypeName::BeyondScope,
+                false,
+                true,
+            ),
+            (
+                "address-unreachable",
+                FirewallIcmpTypeName::AddressUnreachable,
+                false,
+                true,
+            ),
+            (
+                "failed-policy",
+                FirewallIcmpTypeName::FailedPolicy,
+                false,
+                true,
+            ),
+            (
+                "reject-route",
+                FirewallIcmpTypeName::RejectRoute,
+                false,
+                true,
+            ),
+            (
+                "packet-too-big",
+                FirewallIcmpTypeName::PacketTooBig,
+                false,
+                true,
+            ),
+            ("bad-header", FirewallIcmpTypeName::BadHeader, false, true),
+            (
+                "unknown-header-type",
+                FirewallIcmpTypeName::UnknownHeaderType,
+                false,
+                true,
+            ),
+            (
+                "unknown-option",
+                FirewallIcmpTypeName::UnknownOption,
+                false,
+                true,
+            ),
+            (
+                "neighbor-solicitation",
+                FirewallIcmpTypeName::NeighborSolicitation,
+                false,
+                true,
+            ),
+            (
+                "neighbour-solicitation",
+                FirewallIcmpTypeName::NeighbourSolicitation,
+                false,
+                true,
+            ),
+            (
+                "neighbor-advertisement",
+                FirewallIcmpTypeName::NeighborAdvertisement,
+                false,
+                true,
+            ),
+            (
+                "neighbour-advertisement",
+                FirewallIcmpTypeName::NeighbourAdvertisement,
+                false,
+                true,
+            ),
+        ];
+
+        for (input, expected, v4, v6) in tests {
+            let deserialized: FirewallIcmpTypeName =
+                serde_plain::from_str(input).expect("deserialize");
+            assert_eq!(deserialized, expected);
+            let serialized = serde_plain::to_string(&deserialized).expect("serialize");
+            assert_eq!(serialized, input);
+
+            assert_eq!(deserialized.ipv4(), v4, "ipv4 check failed for {}", input);
+            assert_eq!(deserialized.ipv6(), v6, "ipv6 check failed for {}", input);
+        }
+    }
+
+    #[test]
+    fn test_firewall_icmp_type_enum() {
+        // Numeric
+        for (input, output) in [
+            ("0", FirewallIcmpType::Numeric(0)),
+            ("10", FirewallIcmpType::Numeric(10)),
+            ("255", FirewallIcmpType::Numeric(255)),
+        ] {
+            let ty: FirewallIcmpType = input.parse().expect("valid numeric icmp type");
+            assert_eq!(ty, output);
+            assert_eq!(ty.to_string(), input);
+        }
+
+        // Named
+        for (input, output) in [
+            ("echo-request", FirewallIcmpTypeName::EchoRequest),
+            ("any", FirewallIcmpTypeName::Any),
+        ] {
+            let ty: FirewallIcmpType = input.parse().expect("valid named icmp type");
+            assert_eq!(ty, FirewallIcmpType::Named(output));
+            assert_eq!(ty.to_string(), input);
+        }
+
+        // Invalid
+        #[cfg(not(feature = "enum-fallback"))]
+        for input in ["echo-reques", "an", "256", "-1", "foo"] {
+            input
+                .parse::<FirewallIcmpType>()
+                .expect_err("invalid icmp type");
+        }
+
+        // Invalid, but with enum fallback enabled, should be parsed as unknown
+        #[cfg(feature = "enum-fallback")]
+        for input in ["echo-reques", "an", "256", "-1", "foo"] {
+            let ty = input.parse::<FirewallIcmpType>().expect("valid icmp type");
+            assert_eq!(
+                ty,
+                FirewallIcmpType::Named(FirewallIcmpTypeName::UnknownEnumValue(
+                    FixedString::new(input).unwrap(),
+                )),
+            );
+        }
+    }
+}
diff --git a/proxmox-firewall-api-types/src/lib.rs b/proxmox-firewall-api-types/src/lib.rs
index b099be0c..610282bb 100644
--- a/proxmox-firewall-api-types/src/lib.rs
+++ b/proxmox-firewall-api-types/src/lib.rs
@@ -1,6 +1,9 @@
 mod conntrack;
 pub use conntrack::FirewallConntrackHelper;
 
+mod icmp_type;
+pub use icmp_type::{FirewallIcmpType, FirewallIcmpTypeName};
+
 mod log;
 pub use log::{
     FirewallLogLevel, FirewallLogRateLimit, FirewallPacketRate, FirewallPacketRateTimescale,
-- 
2.47.3




  parent reply	other threads:[~2026-02-16 10:46 UTC|newest]

Thread overview: 26+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-02-16 10:43 [RFC proxmox 00/22] New crate for firewall api types Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 01/22] firewall-api-types: add new " Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 02/22] firewall-api-types: add README.md Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 03/22] firewall-api-types: add firewall policy types Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 04/22] firewall-api-types: add logging types Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 05/22] firewall-api-types: add FirewallClusterOptions Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 06/22] firewall-api-types: add FirewallGuestOptions Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 07/22] firewall-api-types: add FirewallConntrackHelper enum Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 08/22] firewall-api-types: add FirewallNodeOptions struct Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 09/22] firewall-api-types: add FirewallRef type Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 10/22] firewall-api-types: add FirewallPortList types Dietmar Maurer
2026-02-16 10:43 ` Dietmar Maurer [this message]
2026-02-16 10:43 ` [RFC proxmox 12/22] firewall-api-types: add FirewallIpsetReference type Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 13/22] firewall-api-types: add FirewallAliasReference type Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 14/22] firewall-api-types: add firewall address types Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 15/22] firewall-api-types: add FirewallRule type Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 16/22] firewall-api-types: use ConfigDigest from proxmox-config-digest crate Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 17/22] firewall-api-types: use COMMENT_SCHEMA from proxmox-schema crate Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 18/22] firewall-api-types: add FirewallRuleUpdater type Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 19/22] firewall-api-types: refactor FirewallRule and add FirewallRuleListEntry Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 20/22] firewall-api-types: add DeletableFirewallRuleProperty enum Dietmar Maurer
2026-02-16 10:43 ` [RFC proxmox 21/22] firewall-api-types: add FirewallAliasEntry API type Dietmar Maurer
2026-02-16 10:44 ` [RFC proxmox 22/22] firewall-api-types: add FirewallIpsetListEntry and FirewallIpsetEntry api types Dietmar Maurer
2026-02-17  6:17 ` [RFC proxmox 00/22] New crate for firewall " Hannes Laimer
2026-02-17  6:39   ` Dietmar Maurer
2026-02-17  8:17     ` Hannes Laimer

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260216104401.3959270-12-dietmar@proxmox.com \
    --to=dietmar@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
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal