Summary
The pairwise GRE tunnel collision check in src/conf_mode/interfaces_tunnel.py is more
restrictive than the Kernel when a GRE key is configured. Configuring
parameters ip key currently makes a configuration more likely to be rejected than
leaving it out, which is the opposite of what the check is meant to achieve.
When no key is set, the check correctly requires the remote addresses to match before it
complains (if our_remote == their_remote:, added by T4267). The keyed branch never got
the same treatment — it only compares source-address and key:
if our_key != None: if their_address == our_address and their_key == our_key: raise ConfigError(f'Key "{our_key}" for source-address "{our_address}" ' f'is already used for tunnel "{o_tunnel}"!')
remote and source_interface are not even in scope there — they are only looked up
inside the else: branch. This code is unchanged since T2920 (2020).
Steps to reproduce
Two GRE tunnels from the same source to different destinations, sharing one key:
set interfaces tunnel tun10 encapsulation 'gre' set interfaces tunnel tun10 source-address '192.0.2.1' set interfaces tunnel tun10 remote '203.0.113.1' set interfaces tunnel tun10 parameters ip key '10' set interfaces tunnel tun20 encapsulation 'gre' set interfaces tunnel tun20 source-address '192.0.2.1' set interfaces tunnel tun20 remote '203.0.113.2' set interfaces tunnel tun20 parameters ip key '10'
Expected behaviour
The commit succeeds. The two tunnels differ in remote address, so they are distinct for
the Kernel.
Actual behaviour
Key "10" for source-address "192.0.2.1" is already used for tunnel "tun10"!
The identical configuration without parameters ip key commits fine.
A second variant of the same defect: two tunnels bound to different source-interfaces
sharing one key are rejected, because both have no source-address and None == None
compares equal:
set interfaces tunnel tun10 encapsulation 'gre' set interfaces tunnel tun10 source-interface 'eth0' set interfaces tunnel tun10 parameters ip key '10' set interfaces tunnel tun20 encapsulation 'gre' set interfaces tunnel tun20 source-interface 'eth1' set interfaces tunnel tun20 parameters ip key '10'
Why this is a VyOS restriction and not a Kernel one
ip_tunnel_find() in net/ipv4/ip_tunnel.c treats a tunnel as a duplicate only when
all five of these match:
local == t->parms.iph.saddr && remote == t->parms.iph.daddr && link == READ_ONCE(t->parms.link) && /* the source-interface */ type == t->dev->type && ip_tunnel_key_match(&t->parms, flags, key)
ip_tunnel_newlink() returns -EEXIST only on that full-tuple match. On the receive
side ip_tunnel_lookup() compares local and remote before it looks at the key,
and uses parms.link as a tie-break, so the tunnels also demultiplex unambiguously.
Verified empirically on Linux 6.12 / iproute2 6.15, in a network namespace:
# ip tunnel add gre1 mode gre local 192.0.2.1 remote 192.0.2.10 key 10 -> OK # ip tunnel add gre2 mode gre local 192.0.2.1 remote 192.0.2.11 key 10 -> OK # ip tunnel add gre3 mode gre dev dum0 key 20 -> OK # ip tunnel add gre4 mode gre dev dum1 key 20 -> OK
Both pairs are rejected by VyOS today.
Related, found while testing: 0.0.0.0 is not treated as "unset"
The Kernel stores an unconfigured endpoint as the any address, so remote 0.0.0.0 and
an unset remote are the same tunnel — likewise for source-address. VyOS compares the
raw strings, so this pair is accepted and then fails at commit time with
add tunnel "gre0" failed: File exists:
set interfaces tunnel tun10 encapsulation 'gre' set interfaces tunnel tun10 source-address '192.0.2.1' set interfaces tunnel tun10 source-interface 'eth0' set interfaces tunnel tun10 remote '203.0.113.1' set interfaces tunnel tun10 parameters ip key '10' set interfaces tunnel tun20 encapsulation 'gre' set interfaces tunnel tun20 source-address '0.0.0.0' set interfaces tunnel tun20 source-interface 'eth0' set interfaces tunnel tun20 remote '203.0.113.1' set interfaces tunnel tun20 parameters ip key '10'
Confirmed on 6.12: the second ip tunnel add returns File exists.
Proposed fix
Compare the same tuple the Kernel does: normalise 0.0.0.0/:: to "unset", skip the
peer when the remote addresses differ, and make the keyed branch consider
source-interface as well as source-address.
Deliberately not relaxed:
- same local + same remote + no key, whichever source-interface is used — the Kernel will create these, but they are byte-identical on the wire outbound and ip_tunnel_lookup() resolves them non-deterministically for traffic arriving on a third interface;
- two mGRE tunnels on one source-interface sharing a key (DMVPN) — still rejected;
- gre vs gretap are still compared with each other, although the Kernel keeps them in separate tunnel tables. Worth a separate task.
Compatibility impact
Behaviour change, no migration script required — there are no CLI syntax changes.
The relaxations are purely additive: configurations that were previously rejected now
commit, and nothing that previously worked stops working.
The 0.0.0.0 normalisation is the one case that goes the other way. It newly rejects, at
commit time, two GRE tunnels sharing a source address and key where one sets
remote 0.0.0.0 and the other omits remote. That configuration already failed to apply
with add tunnel "gre0" failed: File exists, so no working configuration changes
behaviour — the failure simply moves from apply time to commit time, where it can be
reported properly.
Version
Reproduced by reading rolling (b361f499e). The keyed branch is unchanged since 2020,
so every release from 1.2 onwards is affected.
This is not a duplicate of T4267
T4267 fixed the keyless branch — "same source-address, different remote, no key" has
committed cleanly since 1.4.0, and test_multiple_gre_tunnel_different_remote covers it.
The keyed branch never received the same treatment, which is what this task is about.
(Note for anyone arriving here from a search: if you hit
Missing required "ip key" parameter ... on the same source-address with no key set
and differing remotes, you are on 1.3.x or older and want T4267 — upgrade to 1.4+.)