Skip to content

vrf: IPv6 default route not moved to VRF table when ECMP route exists on primary interface #1253

@Haven456

Description

@Haven456

Plugin: vrf
Version: 1.9.1
Affected: IPv6 only (IPv4 is not affected)

Description

When an IPAM plugin creates routes before the vrf plugin runs, the VRF plugin fails to correctly move an IPv6 default route (::/0) into the VRF routing table.

The suspected root cause is that the VRF plugin's route migration logic only handles routes with a single nexthop and does not implement the ECMP (Equal-Cost Multi-Path) case.

In a typical pod setup, eth0 already has an IPv6 default route in the main table. When a macvlan interface (net1) is attached via a NetworkAttachmentDefinition, the IPAM plugin adds a second IPv6 default route via net1. Since a ::/0 route already exists, the kernel requires this to be expressed as an ECMP route with two nexthops. The VRF plugin does not handle this case and fails to move the route into the VRF table.

The issue does not occur with split default routes (::/1 and 8000::/1), which do not conflict with the existing eth0 default route and therefore do not trigger the ECMP path.

IPv4 is not affected.

Steps to Reproduce

  1. Have a pod with a primary interface (eth0) that has IPv6 configured — an IPv6 default route (::/0) via eth0 exists in the main routing table.
  2. Configure a multus NetworkAttachmentDefinition using an IPAM plugin (e.g. static or dhcp) that adds an IPv6 default route (::/0) via a macvlan interface (net1), chained with the vrf plugin.
  3. Attach the network to a pod and inspect routing tables inside the pod.

Expected Behavior

The IPv6 default route via net1 should be present in the VRF routing table (e.g. table 1001):

$ ip -6 route show table 1001
::/0 via 2001:db8:1::1 dev net1 metric 1024 pref medium
2001:db8:1::/64 dev net1 proto kernel metric 256 pref medium

Actual Behavior

The default route via net1 is merged with the existing eth0 default route into an ECMP route in the main routing table and is not added to the VRF table:

$ ip route show table all
default metric 1024 pref medium
nexthop via fe80::1 dev eth0 weight 1
nexthop via 2001:db8:1::1 dev net1 weight 1

$ ip -6 route show table 1001
local 2001:db8:1::11 dev net1 proto kernel metric 0 pref medium
2001:db8:1::/64 dev net1 proto kernel metric 256 pref medium
local fe80::1 dev net1 proto kernel metric 0 pref medium
fe80::/64 dev net1 proto kernel metric 256 pref medium
multicast ff00::/8 dev net1 proto kernel metric 256 pref medium

Workaround

Use split default routes instead of a single ::/0 route:

$ ip -6 route show table 1001
::/1 via 2001:db8:1::1 dev net1 metric 1024 pref medium
8000::/1 via 2001:db8:1::1 dev net1 metric 1024 pref medium

These prefixes do not conflict with the existing eth0 ::/0 route, so no ECMP handling is required and the routes are correctly placed in the VRF table.

Root Cause (suspected)

The VRF plugin's route migration logic appears to only handle routes with a single nexthop. The failure sequence is:

  1. The pod has a primary interface (eth0) with IPv6 configured — an IPv6 default route (::/0) via eth0 already exists in the main routing table.
  2. The IPAM plugin adds a second IPv6 default route (::/0) via the macvlan interface (net1).
  3. Since a default route already exists, the kernel requires this to be added as an ECMP route (multipath, two nexthops on the same ::/0 prefix).
  4. The VRF plugin then attempts to move the net1 nexthop into the VRF table, but its implementation does not handle the ECMP case — it only supports adding a route with a single nexthop.
  5. The default route via net1 is never added to the VRF table. Both nexthops (eth0 and net1) remain as an ECMP route in the main table.

To fix this, the VRF plugin would need to detect when a route being moved is part of an ECMP set, extract only the nexthop(s) belonging to the target interface, and add them correctly into the VRF table using the RTA_MULTIPATH netlink attribute or equivalent.

Environment

  • CNI Plugins version: 1.9.1

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions