public inbox for pve-devel@lists.proxmox.com
 help / color / mirror / Atom feed
From: Hannes Laimer <h.laimer@proxmox.com>
To: Stefan Hanreich <s.hanreich@proxmox.com>, pve-devel@lists.proxmox.com
Subject: Re: [PATCH proxmox-firewall] nftables: move protocol match rendering into the lib
Date: Wed, 23 Sep 2026 13:18:55 +0200	[thread overview]
Message-ID: <4b5b353b-fca6-4e17-9cce-6351a6bc49ac@proxmox.com> (raw)
In-Reply-To: <bf2de1de-a487-4212-b0cc-9f14545be1d1@proxmox.com>

thanks for taking a look!
two comments inline, will prepare a v2

On 2026-09-23 12:00, Stefan Hanreich wrote:
> 
> 
> On 8/27/26 3:15 PM, Hannes Laimer wrote:
>> The statements selecting a transport protocol, its ports or an ICMP type
>> and code were generated in proxmox-firewall, although which statements
>> select a protocol is nftables knowledge and does not depend on the
>> firewall config. Move the rendering into proxmox-nftables, working on
>> plain expressions, and add an entry point taking the firewall's protocol
>> type to the config extension. The firewall keeps its IP family pinning
>> on top.
>>
>> No functional change intended.
>>
>> Signed-off-by: Hannes Laimer <h.laimer@proxmox.com>
>> ---

..

>> -
>> -impl ToNftRules for Ports {
>> -    fn to_nft_rules(&self, rules: &mut Vec<NftRule>, _env: &NftRuleEnv) -> Result<(), Error> {
>> -        log::trace!("applying ports: {self:?}");
>> -
>> -        for rule in rules {
>> -            if let Some(sport) = self.sport() {
>> -                log::trace!("applying sport: {sport:?}");
>> -
>> -                rule.push(
>> -                    Match::new_eq(
>> -                        Expression::from(Payload::field("th", "sport")),
>> -                        Expression::from(sport),
>> -                    )
>> -                    .into(),
>> -                )
>> +            for statement in protocol::matches(self) {
>> +                rule.push(statement);
>>              }
> 
> nit: could be extend?
> 
> generally the whole function reads a bit awkward to me, but I cant think of anything
> better on the spot.
> 


yes, something like

```
         for rule in rules.iter_mut() {
-            if family
-                .zip(rule.family())
-                .is_some_and(|(wanted, pinned)| wanted != pinned)
-            {
-                continue;
-            }
-            for statement in protocol::matches(self) {
-                rule.push(statement);
-            }
-            if let Some(family) = family {
+            if let Some(family) = self.family() {
+                if rule.family().is_some_and(|pinned| pinned != family) {
+                    continue;
+                }
                 rule.set_family(family);
             }
+            rule.extend(protocol::matches(self));
         }
```
seems better


>> -
>> -            if let Some(dport) = self.dport() {
>> -                log::trace!("applying dport: {dport:?}");
>> -
>> -                rule.push(
>> -                    Match::new_eq(
>> -                        Expression::from(Payload::field("th", "dport")),
>> -                        Expression::from(dport),
>> -                    )
>> -                    .into(),
>> -                )
>> +            if let Some(family) = family {
>> +                rule.set_family(family);
>>              }
>>          }
>>  

..

>> +
>> +/// Selects an ICMP flavour by type and code. Matching either already implies the protocol, so
>> +/// only a bare match needs the explicit `l4proto` test.
>> +pub fn icmp(protocol: &str, ty: Option<Expression>, code: Option<Expression>) -> Vec<Statement> {
>> +    if ty.is_none() && code.is_none() {
>> +        return vec![l4proto(protocol)];
>> +    }
> 
> nit: is it necessary to short circuit rather than just do this and have the two if statements below?
> 

with `meta l4proto` present nft doesnt add l3 checks that it would
otherwise for an `icmpv6` payload, basically
```
nft add rule inet t c meta l4proto icmpv6 icmpv6 code 4
nft add rule inet t c icmpv6 code 4
```
and `nft --debug=netlink list ruleset` shows the diff

I guess emitting `meta nfproto ipv6` ourself could work as well(?),
but for that we'd have to know the table family here..

> let mut statements = vec![l4proto(protocol)];
> 
> iirc, nftables simplifies this when creating the rule anyway, so it shouldn't be less efficient in the
> ruleset:
> 
> $ nft 'add rule inet test test-chain meta l4proto icmpv6 icmpv6 code 4' 
> $ nft 'list chain inet test test-chain'
> 
>         chain test-chain {
>                 icmpv6 code 4
>         }
> 
> 
> 
>> +    let mut statements = Vec::new();
>> +    if let Some(ty) = ty {
>> +        statements.push(Match::new_eq(Payload::field(protocol, "type"), ty).into());
>> +    }
>> +    if let Some(code) = code {
>> +        statements.push(Match::new_eq(Payload::field(protocol, "code"), code).into());
>> +    }
>> +    statements
>> +}
>> +
>> +/// The statements selecting `protocol`, in evaluation order.
>> +#[cfg(feature = "config-ext")]
>> +pub fn matches(protocol: &Protocol) -> Vec<Statement> {
>> +    match protocol {
>> +        Protocol::Tcp(tcp) => with_ports("tcp", tcp.ports()),
>> +        Protocol::Udp(udp) => with_ports("udp", udp.ports()),
>> +        Protocol::Sctp(sctp) => with_ports("sctp", sctp.ports()),
>> +        Protocol::Dccp(config) => with_ports("dccp", config),
>> +        Protocol::UdpLite(config) => with_ports("udplite", config),
>> +        Protocol::Icmp(config) => icmp(
>> +            "icmp",
>> +            config.ty().map(Expression::from),
>> +            config.code().map(Expression::from),
>> +        ),
>> +        Protocol::Icmpv6(config) => icmp(
>> +            "icmpv6",
>> +            config.ty().map(Expression::from),
>> +            config.code().map(Expression::from),
>> +        ),
>> +        Protocol::Named(name) => vec![l4proto(name.as_str())],
>> +        Protocol::Numeric(id) => vec![l4proto(*id)],
>> +    }
>> +}
>> +
>> +#[cfg(feature = "config-ext")]
>> +fn with_ports(protocol: &str, config: &Ports) -> Vec<Statement> {
>> +    let mut statements = vec![l4proto(protocol)];
>> +    statements.extend(ports(
>> +        config.sport().map(Expression::from),
>> +        config.dport().map(Expression::from),
>> +    ));
>> +    statements
>> +}
> 
> 
> 
> 
> 





  reply	other threads:[~2026-09-23 11:19 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-27 13:15 [PATCH proxmox-firewall] nftables: move protocol match rendering into the lib Hannes Laimer
2026-09-23 10:00 ` Stefan Hanreich
2026-09-23 11:18   ` Hannes Laimer [this message]
2026-09-23 19:15 ` applied: " Thomas Lamprecht

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=4b5b353b-fca6-4e17-9cce-6351a6bc49ac@proxmox.com \
    --to=h.laimer@proxmox.com \
    --cc=pve-devel@lists.proxmox.com \
    --cc=s.hanreich@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