Skip to main content

EgressRoutePolicy

Diagram showing EgressRoutePolicy selecting outbound candidates from health status and publishing advisory or applied routing state

EgressRoutePolicy selects the route used for outbound traffic. It replaces an earlier WAN route policy Kind. This is a clean break. The old Kind name is not accepted.

The policy watches candidate resources and health checks. It stores the chosen candidate in status. Other resources can refer to that status.

spec.mode decides which controller owns the status. When mode is omitted, the egress-route selector publishes selection-only status and routerd.lan.route.changed events with role: advisory / advisory: true. That status is live controller output, not an apply dry run. When mode is set to priority, mark, or hash, the policy-route controller owns the applied routing/NAT mark state and dependent controllers follow routerd.resource.status.changed instead of the legacy route-changed event.

mode: priority still uses selection: highest-weight-ready. The highest weight ready candidate wins; priority is the tie-breaker and the policy-route rule priority, not a replacement for the selection policy. weighted-ecmp is reserved until implemented and is reported as UnsupportedSelection rather than being silently ignored. enabled: false removes the candidate from selection and from generated policy-route rule/table ownership.

apiVersion: net.routerd.net/v1alpha1
kind: EgressRoutePolicy
metadata:
name: ipv4-default
spec:
family: ipv4
destinationCIDRs:
- 0.0.0.0/0
selection: highest-weight-ready
candidates:
- name: ds-lite
source: DSLiteTunnel/ds-lite
deviceFrom:
resource: DSLiteTunnel/ds-lite
field: interface
gatewaySource: none
weight: 80
healthCheck: internet-tcp443
- name: ix2215
source: Interface/ix2215
deviceFrom:
resource: Interface/ix2215
field: ifname
gatewaySource: static
gateway: 172.17.0.1
weight: 50

destinationCIDRs limits the destinations managed by the policy. If it is omitted, routerd uses 0.0.0.0/0 for IPv4 and ::/0 for IPv6.

gatewaySource describes how the gateway is chosen.

  • none: point-to-point devices such as DS-Lite do not need a gateway.
  • static: gateway contains the next-hop address.
  • dhcpv4 and dhcpv6: reserved for gateway values learned from client leases.

The selected values are exposed as:

  • status.selectedCandidate
  • status.selectedDevice
  • status.selectedGateway
  • status.selectedWeight
  • status.selectedTargets
  • status.destinationCIDRs

When a candidate is gated by VirtualAddress/<name>.role: master, applied mode: priority status also reports HA data-path convergence:

  • status.datapathState (Standby, Converging, or Ready)
  • status.datapathRoleTransitionAt
  • status.datapathReadyAt
  • status.datapathReadyDuration and status.datapathReadyDurationMillis
  • status.newFlowBehavior

During Converging, routerd deliberately selects the next ready candidate for new unmarked flows (datapathFallbackBehavior: select-next-ready-candidate). It does not silently drop those flows. Existing flows with a replicated, non-zero conntrack mark keep that mark and do not enter new-flow selection. The keepalived transition hook wakes routerd immediately; the periodic VRRP observation remains as a recovery path if the notification cannot be delivered.

At startup, the policy chooses the first ready candidate instead of waiting for the highest-weight path forever. If a higher-weight candidate becomes ready later, routerd updates IPv4Route and NAT44Rule through routerd.lan.route.changed. It does not flush conntrack. Existing flows keep the kernel state they already have, while new flows use the new route and NAT direction.

For candidates with multiple targets, routerd hashes new flows into 256 fixed buckets and assigns those buckets with rendezvous hashing. The seed and target identity are stable across reconciliation and restart. If a target becomes unhealthy, only buckets owned by that target move to the next-ready target; a recovered target takes back the same buckets. Adding or removing a target also changes only the buckets whose rendezvous winner changes. Existing flows keep their non-zero conntrack mark and are not rehashed. The fixed bucket table is a small nftables rule-size cost in exchange for bounded remapping and stable source-address affinity.

IPv4Route can use those status fields:

apiVersion: net.routerd.net/v1alpha1
kind: IPv4Route
metadata:
name: default-route
spec:
destination: 0.0.0.0/0
deviceFrom:
resource: EgressRoutePolicy/ipv4-default
field: selectedDevice
gatewayFrom:
resource: EgressRoutePolicy/ipv4-default
field: selectedGateway

Private destinations that must not leave through a DS-Lite (or any) tunnel can be modeled as ordinary routes. Use a WAN-side route for the upstream gateway's private subnet, a dedicated route for any internal 10.0.0.0/8 or 172.16.0.0/12 ranges, and type: blackhole for ranges that should be discarded:

apiVersion: net.routerd.net/v1alpha1
kind: IPv4Route
metadata:
name: private-10-blackhole
spec:
type: blackhole
destination: 10.0.0.0/8

Health Checks

HealthCheck can pin probes to a path.

apiVersion: net.routerd.net/v1alpha1
kind: HealthCheck
metadata:
name: internet-tcp443
spec:
target: 1.1.1.1
protocol: tcp
port: 443

When a HealthCheck is referenced by an EgressRoutePolicy candidate or target, routerd derives the health-check daemon, socket mark, and source binding from that route target automatically. The config describes the probe intent; platform-specific socket mechanics stay inside the controller and renderer.