Page MenuHomeVyOS Platform

GeoIP source/destination nftables set collision
Open, Requires assessmentPublicBUG

Description

Summary: GeoIP source and destination match on the same firewall/policy-route rule silently collapse onto a single shared nftables set, so one side's country/ASN list is discarded.

What were you attempting to achieve?

Configure a firewall rule that restricts traffic based on GeoIP independently on both the source and destination address — e.g. "source must be in the US, destination must be in China."

Configuration used

set firewall ipv4 name WAN-IN default-action drop
set firewall ipv4 name WAN-IN rule 10 action accept
set firewall ipv4 name WAN-IN rule 10 source geoip country-code us
set firewall ipv4 name WAN-IN rule 10 destination geoip country-code cn
set interfaces ethernet eth0 firewall in name WAN-IN
commit

What did you expect to happen?

Two independent nftables sets, one per side, each populated with the correct country's IP ranges, and the generated rule matching ip saddr @<us-set> ip daddr @<cn-set>.

What actually happens

Both sides compute the identical nftables set name, GEOIP_CC_name_WAN-IN_10 (see root cause below). Only one side's IP ranges end up in that set — whichever is processed last by geoip_update() — and the generated rule ends up matching ip saddr @GEOIP_CC_name_WAN-IN_10 ip daddr @GEOIP_CC_name_WAN-IN_10, i.e. both source and destination are checked against the same single country's ranges, silently discarding the other side's intended restriction. There is no error at commit time or at runtime; the misconfiguration is invisible from the CLI.

Verify with:

show configuration commands | match geoip
sudo nft list ruleset | grep -A5 'set GEOIP_CC_name_WAN-IN_10'
— only one country's ranges will be present in the set body, and the rule references that same set for both saddr and daddr.

Root cause

GEOIP_{CC|ASN}{6}_{hook}_{name}_{rule} set names, built independently in python/vyos/firewall.py:262 (rule match expression) and python/vyos/geoip.py (geoip_update(), lines 269/275/285/291 — the actual set contents), never include a source/destination discriminator. The sibling FQDN-match code three lines earlier in firewall.py (line 239) does append a _{prefix} (s/d) suffix for exactly this reason — the GeoIP branch appears to have been copy-pasted from it and the suffix dropped. See patch.md for the full trace and a proposed fix.

Environment

VyOS version: rolling (any recent build — verified by direct source inspection of the rolling branch, not tied to a specific dated build)
Reproduces on both ipv4 and ipv6 firewall rulesets, and on policy route / policy route6 rules (shares the same parse_rule() code path)

Prior art (checked on vyos.dev before filing)

No existing task describes this collision (searched ~30 GeoIP-related tasks). T7552 (closed, Invalid — a case-sensitivity mistake) confirms there's no existing validation that would prevent configuring GeoIP on both sides of one rule, so this is genuinely reachable today.

Details

Version
Rolling
Is it a breaking change?
Stricter validation
Issue type
Bug (incorrect behavior)