Page MenuHomeVyOS Platform

protocols nhrp: cannot commit a second tunnel with 'redirect' — duplicate nftables meter name breaks firewall rules
Closed, ResolvedPublicBUG

Description

Summary

Committing a second protocols nhrp tunnel that has redirect enabled fails with "Failed to apply NHRP tunnel firewall rules" and the commit is rejected. A single NHRP tunnel commits and works fine.

Root cause is a hardcoded nftables meter name (loglimit-0) in the NHRP firewall template. The redirect chain is rendered in a per-tunnel loop, so the second redirect-enabled tunnel re-declares the same named set and nft aborts the whole (atomic) ruleset load.

Software version

  • 2026.07.21-1151-rolling (also reproduces on current rolling)
  • Regression introduced by the NHRP-to-FRR migration (T2326, commit 5e8307bf3)

Steps to reproduce

set interfaces tunnel tun100 address '172.16.0.1/32'
set interfaces tunnel tun100 encapsulation 'gre'
set interfaces tunnel tun100 source-address '198.51.100.1'
set interfaces tunnel tun100 enable-multicast
set interfaces tunnel tun100 parameters ip key '1'
set protocols nhrp tunnel tun100 network-id '1'
set protocols nhrp tunnel tun100 redirect
set protocols nhrp tunnel tun100 multicast 'dynamic'
commit                      # OK

set interfaces tunnel tun101 address '172.16.1.1/32'
set interfaces tunnel tun101 encapsulation 'gre'
set interfaces tunnel tun101 source-address '198.51.100.1'
set interfaces tunnel tun101 enable-multicast
set interfaces tunnel tun101 parameters ip key '2'
set protocols nhrp tunnel tun101 network-id '2'
set protocols nhrp tunnel tun101 redirect
set protocols nhrp tunnel tun101 multicast 'dynamic'
commit                      # FAILS

Commit output:

[ protocols nhrp ]
Failed to apply NHRP tunnel firewall rules
[[protocols nhrp]] failed
Commit failed

Expected behavior

Multiple NHRP tunnels, each with redirect (and/or multicast) enabled, commit successfully and each gets its own redirect rate-limit/log rule.

Actual behavior

The commit fails as soon as a second redirect-enabled tunnel is added. The trigger is specifically two or more tunnels with redirect. Tunnels that only use multicast are unaffected.

Root cause

apply() in src/conf_mode/protocols_nhrp.py runs nft --file /run/nftables_nhrp.conf and raises on any non-zero return:

nft_rc = run(f'nft --file {nhrp_nftables_conf}')
if nft_rc != 0:
    raise ConfigError('Failed to apply NHRP tunnel firewall rules')

That file is rendered from data/templates/frr/nhrpd_nftables.conf.j2. The redirect chain loops over every tunnel but hardcodes the meter name loglimit-0:

{% for tun, tunnel_conf in tunnel.items() %}
{%   if tunnel_conf.redirect is vyos_defined %}
     iifname "{{ tun }}" oifname "{{ tun }}" meter loglimit-0 size 65535 { ... } counter log group {{ redirect }}
{%   endif %}
{% endfor %}

meter loglimit-0 size 65535 { ... } declares a named dynamic set loglimit-0 in table vyos_nhrp_redirect. With two redirect-enabled tunnels the loop declares it twice. The {{ redirect }} log-group value is a single global constant (1, set in python/vyos/frrender.py), so the two rules are identical except for the interface name — the meter name never varies. The rendered /run/nftables_nhrp.conf becomes:

table ip vyos_nhrp_redirect {
    chain VYOS_NHRP_REDIRECT_FORWARD {
        type filter hook forward priority filter+10; policy accept;
        iifname "tun100" oifname "tun100" meter loglimit-0 size 65535 { ip daddr & 255.255.255.0 . ip saddr & 255.255.255.0 timeout 1m limit rate 4/minute burst 1 packets } counter log group 1
        iifname "tun101" oifname "tun101" meter loglimit-0 size 65535 { ip daddr & 255.255.255.0 . ip saddr & 255.255.255.0 timeout 1m limit rate 4/minute burst 1 packets } counter log group 1
    }
}

nft rejects the second declaration because the named set/meter loglimit-0 already exists in the table, and aborts the atomic load. Reproducing by hand surfaces the exact error:

$ sudo nft --file /run/nftables_nhrp.conf
# fails on the second loglimit-0 line, e.g.:
# Error: Could not process rule: File exists

The multicast chains use plain rules with no named sets, which is why multicast-only tunnels don't collide.

Why it wasn't caught

smoketest/scripts/cli/test_protocols_nhrp.py only ever configures a single tunnel (tun100) and even asserts the literal meter loglimit-0 string, so the multi-tunnel path has no test coverage.

Proposed fix

Make the meter name unique per loop iteration. Using loop.index0 keeps loglimit-0 for the first tunnel (existing smoketest assertion stays valid) and yields loglimit-1, loglimit-2, … for the rest:

--- a/data/templates/frr/nhrpd_nftables.conf.j2
+++ b/data/templates/frr/nhrpd_nftables.conf.j2
@@ -37,7 +37,7 @@
 {%     if tunnel is vyos_defined %}
 {%         for tun, tunnel_conf in tunnel.items() %}
 {%             if tunnel_conf.redirect is vyos_defined %}
-                iifname "{{ tun }}" oifname "{{ tun }}" meter loglimit-0 size 65535 { ip daddr & 255.255.255.0 . ip saddr & 255.255.255.0 timeout 1m limit rate 4/minute burst 1 packets } counter log group {{ redirect }}
+                iifname "{{ tun }}" oifname "{{ tun }}" meter loglimit-{{ loop.index0 }} size 65535 { ip daddr & 255.255.255.0 . ip saddr & 255.255.255.0 timeout 1m limit rate 4/minute burst 1 packets } counter log group {{ redirect }}
 {%             endif %}
 {%         endfor %}
 {%     endif %}

This matches the convention used throughout the firewall templates, where every named set is parameterized per item (RECENT_{{ set_name }}, A_{{ group_name }}, … in data/templates/firewall/nftables-defines.j2). A more self-documenting alternative is loglimit-{{ tun }} (e.g. loglimit-tun100), but it changes the first tunnel's name and would require updating the smoketest assertion.

Regression test

Extend smoketest/scripts/cli/test_protocols_nhrp.py with two tunnels that both enable redirect and multicast, assert the commit succeeds, and assert both loglimit-0 and loglimit-1 rules exist in table ip vyos_nhrp_redirect.

Workaround

Until fixed, only one NHRP tunnel may have redirect enabled at a time.

Details

Version
2026.07.21-1151-rolling
Is it a breaking change?
Perfectly compatible
Issue type
Bug (incorrect behavior)

Related Objects

Event Timeline

Viacheslav changed the task status from Open to In progress.Jul 28 2026, 3:50 PM
Viacheslav assigned this task to lclements0.
Viacheslav triaged this task as Normal priority.
Viacheslav subscribed.
Viacheslav moved this task from Need Triage to Completed on the VyOS Rolling board.