Skip to content

Conversation

xzkutor
Copy link

@xzkutor xzkutor commented Aug 21, 2025

Problem

When multiple DHCP clients attempt to install an IPv4 default route into the main table, netlink.RouteAdd can fail with EEXIST. This leads to noisy errors and nondeterministic behavior regarding which interface "owns" the default route.

Summary of changes

  1. internal/server/ip:
  • Made Route.List device-optional: when DevName is empty or unresolved, the OIF filter is cleared and routes across all devices are returned.
  • Introduced a single builder netlinkRoute(mode) to construct netlink.Route for both apply (add/replace/delete) and query (list) paths.
  1. incus/dhcp (DHCPv4 path in main_forknet.go):
  • Introduce a policy to keep a single default route in the main table, owned by the lexicographically smallest interface name among candidates.
  • Inspect current main table routes using ip.Route.List to find the current default route owner.
  • If the new interface name sorts before the current owner (or there is no owner), install/replace the default route via ip.Route.Replace. Otherwise, skip installing the default route for this interface.
  • Mark installed routes with Proto="dhcp".
  • Use Replace rather than Add to make route installation idempotent across renewals and avoid EEXIST.

Behavioral impact

  • Route.List now returns routes across all devices when DevName is empty (previously, it always filtered by device).
  • Add, Replace, Delete, and Flush continue to require a valid device and behave as before.

This PR resolves #2403

* Problem

  When multiple DHCP clients attempt to install an IPv4 default route into the main table,
netlink.RouteAdd can fail with EEXIST. This leads to noisy errors and nondeterministic
behavior regarding which interface "owns" the default route.

* Summary of changes

1. internal/server/ip:
  - Made Route.List device-optional: when DevName is empty or unresolved, the OIF filter
    is cleared and routes across all devices are returned.
  - Introduced a single builder netlinkRoute(mode) to construct netlink.Route for both
    apply (add/replace/delete) and query (list) paths.
2. incus/dhcp (DHCPv4 path in main_forknet.go):
  - Introduce a policy to keep a single default route in the main table, owned by the
    lexicographically smallest interface name among candidates.
  - Inspect current main table routes using ip.Route.List to find the current default
    route owner.
  - If the new interface name sorts before the current owner (or there is no owner),
    install/replace the default route via ip.Route.Replace. Otherwise, skip installing
    the default route for this interface.
  - Mark installed routes with Proto="dhcp".
  - Use Replace rather than Add to make route installation idempotent across renewals
    and avoid EEXIST.

* Behavioral impact:

  - Route.List now returns routes across all devices when DevName is empty (previously, it
    always filtered by device).
  - Add, Replace, Delete, and Flush continue to require a valid device and behave as before.

Signed-off-by: Andrey Mozharovsky <andrii.mozharovskyi@virtuozzo.com>
@stgraber stgraber merged commit 5e171b4 into lxc:main Aug 22, 2025
34 of 38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Default routes and multiple DHCP clients for OCI containers
2 participants