Skip to content

Support interface IPv6 prefix tracking for RA and DHCPv6#8340

Open
Omoeba wants to merge 3 commits into
AdguardTeam:masterfrom
Omoeba:feat/dhcpv6-interface-prefix-source
Open

Support interface IPv6 prefix tracking for RA and DHCPv6#8340
Omoeba wants to merge 3 commits into
AdguardTeam:masterfrom
Omoeba:feat/dhcpv6-interface-prefix-source

Conversation

@Omoeba

@Omoeba Omoeba commented Apr 12, 2026

Copy link
Copy Markdown

Summary

This PR adds interface-derived IPv6 prefix tracking to AdGuard Home's DHCPv6 / Router Advertisement implementation.

In addition to tests and linters, I've verified both the backend behavior and the UI on a Raspberry Pi 5 running Pi OS Trixie with a delegated /64 from Cox. Any testing on other platforms and ISPs would be much appreciated.

The current static dhcp.dhcpv6.range_start works poorly on networks where the LAN /64 comes from a dynamic delegated prefix that may change over time.

This change adds a new dhcp.dhcpv6.prefix_source setting:

  • static (default): preserve the current behavior
  • interface: derive the current /64 from the configured LAN interface, track prefix changes over time, and use the observed Prefix Information lifetimes instead of the current hardcoded RA prefix lifetimes

This is similar to dnsmasq's constructor:<interface> behavior.

Closes #6637.

Config model

In prefix_source: interface mode, dhcp.dhcpv6.range_start is treated as a host template, not as a fixed prefix.

Example:

  • configured range_start: ::70
  • observed LAN prefix 2001:db8:1234:5678::/64
  • effective DHCPv6 pool start 2001:db8:1234:5678::70

In other words:

  • the upper 64 bits come from the currently observed interface prefix
  • the lower 64 bits come from the configured range_start

For ra_slaac_only: true, no DHCPv6 allocation pool is needed, so range_start is optional in interface mode.

What this changes

  • adds dhcp.dhcpv6.prefix_source

  • keeps static mode fully backward compatible

  • adds a portable IPv6 interface-state observation layer

    • Linux: rtnetlink
    • Darwin / FreeBSD / OpenBSD: ifconfig parsing
  • derives the active RA prefix from live interface state in interface mode

  • uses the observed preferred/valid lifetimes for RA Prefix Information Options

  • tracks active and deprecated prefixes across renumbering events

  • updates the effective DHCPv6 pool when the active renewable prefix changes

  • allows existing dynamic leases on deprecated prefixes to continue until their valid lifetime expires

  • persists renewable / deprecated prefix metadata so renumbering state can survive restart

Behavior

Static mode

prefix_source: static preserves the current behavior.

Interface mode

When prefix_source: interface is enabled:

  • AGH observes IPv6 address state on the configured LAN interface
  • it selects the current active prefix from that observed state
  • it advertises that prefix in RA with the observed preferred/valid lifetimes
  • when the active prefix changes, the previous prefix is moved into a deprecated state
  • new DHCPv6 allocations move to the current active renewable prefix
  • existing leases on deprecated prefixes can still be Confirm/Request/Renew/Rebind'ed while that prefix remains valid

In RA-only mode, AGH continues to send Router Advertisements but does not start the DHCPv6 server.

Design notes

This PR is intentionally modeled after dnsmasq's constructor-based IPv6 behavior, but adapted to AGH's existing configuration and runtime model.

The main semantic difference is that AGH uses:

dhcp:
  dhcpv6:
    prefix_source: interface
    range_start: ::70

instead of dnsmasq's constructor:<interface> syntax.

The intended outcome is the same:

  • derive the network portion from the live interface prefix
  • keep the configured host portion stable
  • follow prefix deprecation during renumbering instead of treating the prefix as permanently static

Limitations

  • Static IPv6 leases are not rewritten when the delegated prefix changes. In interface mode, AGH logs a warning if literal static IPv6 leases are present.
  • The configured lease time is ignored when ra_slaac_only: true and prefix_source: interface.

Implementation overview

Core files touched by this PR:

  • internal/aghnet/prefix.go
  • internal/aghnet/prefix_linux.go
  • internal/aghnet/prefix_darwin.go
  • internal/aghnet/prefix_freebsd.go
  • internal/aghnet/prefix_openbsd.go
  • internal/dhcpd/routeradv_state.go
  • internal/dhcpd/routeradv.go
  • internal/dhcpd/v6_unix.go
  • internal/dhcpd/config.go
  • internal/dhcpd/db.go
  • internal/dhcpd/http_unix.go

At a high level:

  • internal/aghnet/prefix_* provides portable observation of IPv6 interface address state and lifetimes
  • routeradv_state.go manages active / deprecated prefix state
  • routeradv.go rebuilds RA packets from runtime state instead of serializing a single fixed packet at init time
  • v6_unix.go ties the tracked prefix state into DHCPv6 pool selection and lease lifetime behavior
  • db.go persists renewable / deprecated prefix metadata for restart safety

Testing

This PR adds or updates tests for:

  • IPv6 interface-state parsing on all supported platforms
  • RA packet encoding
  • RA state transitions and deprecated-prefix handling
  • interface-mode DHCPv6 pool tracking
  • HTTP/config handling for the new setting
  • DB persistence / restore of IPv6 prefix metadata

UI / API

This PR also includes the corresponding API/OpenAPI/UI updates for the new setting.

If maintainers would prefer a smaller review surface, I can split the UI/API portion into a follow-up PR and keep this PR backend-only.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support get ipv6 prefix from interface

1 participant