all lists on lists.proxmox.com
 help / color / mirror / Atom feed
From: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
To: pve-devel@lists.proxmox.com
Subject: [PATCH pve-firewall 2/2] firewall: ebtables: honor nomatch in ipfilter ipsets
Date: Mon, 12 Oct 2026 11:38:08 +0200	[thread overview]
Message-ID: <20261012093956.285433-3-a.bied-charreton@proxmox.com> (raw)
In-Reply-To: <20261012093956.285433-1-a.bied-charreton@proxmox.com>

The ARP filter turnes every ipfilter-netX entry into a RETURN rule,
including nomatch entries. An address excluded from an allowed network
with "!" is therefore still accepted as an ARP source.

Emit DROP for nomatch entries and order the rules by prefix length. The
rules are sorted most-specific first, and equal prefix lengths keep
their original order.

Signed-off-by: Arthur Bied-Charreton <a.bied-charreton@proxmox.com>
---
 src/PVE/Firewall.pm                | 18 ++++++++++---
 src/test/test_ebtables_ipfilter.pl | 42 ++++++++++++++++++++++++++++--
 2 files changed, 54 insertions(+), 6 deletions(-)

diff --git a/src/PVE/Firewall.pm b/src/PVE/Firewall.pm
index bb3350b..9630d58 100644
--- a/src/PVE/Firewall.pm
+++ b/src/PVE/Firewall.pm
@@ -4834,7 +4834,7 @@ my sub resolve_arpfilter {
         }
 
         next if $ver != 4;
-        push $arpfilter->@*, $ip;
+        push $arpfilter->@*, { cidr => $ip, nomatch => $entry->{nomatch} };
     }
 
     return $arpfilter;
@@ -4916,7 +4916,7 @@ sub compile_ebtables_filter {
                     # want the address here, no network - see #2193
                     $ip =~ s|/(\d+)$||;
                     if ($ip ne 'dhcp') {
-                        push @$arpfilter, $ip;
+                        push $arpfilter->@*, { cidr => $ip };
                     }
                 }
                 generate_tap_layer2filter(
@@ -4958,8 +4958,18 @@ sub generate_tap_layer2filter {
         ruleset_addrule($ruleset, $tapchain, "-p ARP", "-j $arpchain");
         ruleset_create_chain($ruleset, $arpchain);
 
-        foreach my $ip (@{$arpfilter}) {
-            ruleset_addrule($ruleset, $arpchain, "-p ARP --arp-ip-src $ip", '-j RETURN');
+        my $prefix_len = sub { $arpfilter->[$_[0]]->{cidr} =~ m|/(\d+)$| ? $1 : 32 };
+        my @order =
+            sort { $prefix_len->($b) <=> $prefix_len->($a) || $a <=> $b } 0 .. $arpfilter->$#*;
+
+        for my $entry ($arpfilter->@[@order]) {
+            my $target = $entry->{nomatch} ? 'DROP' : 'RETURN';
+            ruleset_addrule(
+                $ruleset,
+                $arpchain,
+                "-p ARP --arp-ip-src $entry->{cidr}",
+                "-j $target",
+            );
         }
         ruleset_addrule($ruleset, $arpchain, '', '-j DROP');
     }
diff --git a/src/test/test_ebtables_ipfilter.pl b/src/test/test_ebtables_ipfilter.pl
index 9716ac2..069ee6c 100755
--- a/src/test/test_ebtables_ipfilter.pl
+++ b/src/test/test_ebtables_ipfilter.pl
@@ -40,9 +40,11 @@ sub compile_arp_sources {
         PVE::Firewall::compile_ebtables_filter($cluster, {}, { 100 => $vmfw_conf }, $vmdata);
 
     ok(exists($ruleset->{'tap100i0-OUT'}), 'guest keeps its layer-2 filter');
-    my @sources = map { /--arp-ip-src (\S+)/ ? $1 : () } ($ruleset->{'tap100i0-OUT-ARP'} // [])->@*;
+    my $arp_rules = $ruleset->{'tap100i0-OUT-ARP'} // [];
+    my @sources = map { /--arp-ip-src (\S+) -j RETURN/ ? $1 : () } $arp_rules->@*;
+    my @rules = map { /--arp-ip-src (\S+) -j (\S+)/ ? "$2 $1" : () } $arp_rules->@*;
 
-    return (\@sources, \@warnings);
+    return (\@sources, \@warnings, \@rules);
 }
 
 subtest 'aliases are resolved' => sub {
@@ -82,4 +84,40 @@ subtest 'unresolvable entries are skipped with context' => sub {
     );
 };
 
+subtest 'nomatch entries are excluded' => sub {
+    my (undef, $warnings, $rules) = compile_arp_sources([
+        { cidr => '192.0.2.0/24' }, { cidr => '192.0.2.5', nomatch => 1 },
+    ]);
+    is_deeply(
+        $rules,
+        ['DROP 192.0.2.5', 'RETURN 192.0.2.0/24'],
+        'excluded address is dropped before the allowed network',
+    );
+    is_deeply($warnings, [], 'no warnings');
+};
+
+subtest 'more specific entries take precedence over nomatch networks' => sub {
+    my (undef, $warnings, $rules) = compile_arp_sources([
+        { cidr => '192.0.2.0/24', nomatch => 1 }, { cidr => '192.0.2.5' },
+    ]);
+    is_deeply(
+        $rules,
+        ['RETURN 192.0.2.5', 'DROP 192.0.2.0/24'],
+        'allowed address is returned before the excluded network',
+    );
+    is_deeply($warnings, [], 'no warnings');
+};
+
+subtest 'entries with the same prefix length keep their order' => sub {
+    my (undef, $warnings, $rules) = compile_arp_sources([
+        { cidr => '192.0.2.20' }, { cidr => '192.0.2.0/24' }, { cidr => '192.0.2.3' },
+    ]);
+    is_deeply(
+        $rules,
+        ['RETURN 192.0.2.20', 'RETURN 192.0.2.3', 'RETURN 192.0.2.0/24'],
+        'ties are not reordered',
+    );
+    is_deeply($warnings, [], 'no warnings');
+};
+
 done_testing();
-- 
2.47.3





      parent reply	other threads:[~2026-10-12  9:40 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-12  9:38 [PATCH pve-firewall 0/2] ebtables: fix ARP filter for alias and nomatch ipfilter entries Arthur Bied-Charreton
2026-10-12  9:38 ` [PATCH pve-firewall 1/2] firewall: ebtables: resolve aliases in ipfilter ipsets Arthur Bied-Charreton
2026-10-12  9:38 ` Arthur Bied-Charreton [this message]

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=20261012093956.285433-3-a.bied-charreton@proxmox.com \
    --to=a.bied-charreton@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.
Service provided by Proxmox Server Solutions GmbH | Privacy | Legal