Skip to content

Add support for Resilient Next-hop Groups Operations - #1205

Open
astrobounce wants to merge 8 commits into
vishvananda:mainfrom
astrobounce:feat/resilient-next-hop-groups
Open

Add support for Resilient Next-hop Groups Operations#1205
astrobounce wants to merge 8 commits into
vishvananda:mainfrom
astrobounce:feat/resilient-next-hop-groups

Conversation

@astrobounce

@astrobounce astrobounce commented Jul 29, 2026

Copy link
Copy Markdown

Changelog

This PR brings support for nexthop group and nexthop resilient group

*What existed in upstream

The upstream library previously had only basic nexthop support (from #1139 ):

  • Low-level nexthop primitives in the nl package (nl.Nhmsg, NewNexthopRequest).
  • NexthopAdd / NexthopDel / NexthopList / NexthopReplace APIs.
  • Only single nexthops were supported, serializing NHA_ID, NHA_BLACKHOLE, NHA_OIF, NHA_GATEWAY, and Protocol.
  • NHID field on Route to attach an existing nexthop object to a route.

What this PR brings

  • New API types:
    • NexthopGroupMember{ID, Weight} - a member (nexthop ID + relative weight, 1–256; 0 treated as 1).
    • NexthopResilientGroup{Buckets, IdleTimer, UnbalancedTimer, UnbalancedTime} - resilient-group configuration.
    • Group-type constants NEXTHOP_GRP_TYPE_MPATH and NEXTHOP_GRP_TYPE_RES.
  • Extended Nexthop struct with Group []NexthopGroupMember, GroupType uint16, and ResilientGroup *NexthopResilientGroup.
  • Serialization/deserialization of the new attributes: NHA_GROUP (with weight encoding wire = weight - 1), NHA_GROUP_TYPE, and the nested NHA_RES_GROUP (NHA_RES_GROUP_BUCKETS, NHA_RES_GROUP_IDLE_TIMER, NHA_RES_GROUP_UNBALANCED_TIMER, NHA_RES_GROUP_UNBALANCED_TIME), where timers are converted to/from kernel clock_t units.
  • New nl package constants: NHA_RES_GROUP, NHA_RES_BUCKET, NHA_RES_GROUP_*, NHA_RES_GROUP_PAD.
  • Input validation in prepareNewNexthop (resilient group requires NEXTHOP_GRP_TYPE_RES; non-empty group for a set group type) and family derivation (FAMILY_ALL) for group nexthops.
  • New example
    • examples/resilient-nexthop-group
      • reproducing ip nexthop add id <N> group <members> type res buckets <n> idle_timer <s> unbalanced_timer <s>.
    • examples/resilient-nexthop-group
    • examples/nexthop
    • examples/nexthop-group

Summary by CodeRabbit

  • New Features
    • Added support for weighted multipath and resilient next-hop groups, including configurable buckets and timers.
    • Added retry support for interrupted dump operations.
    • Added runnable examples for standard, multipath, and resilient next-hop configurations, including cleanup and validation options.
  • Documentation
    • Updated the changelog with next-hop group APIs, behavior, retry support, and examples.
  • Tests
    • Added coverage for next-hop group member and resilient-group configuration initialization.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Nexthop definitions now support multipath and resilient groups. Linux netlink encoding, decoding, validation, and NEWNEXTHOP wiring were extended. New examples and documentation cover group operations. Handle.RetryInterrupted enables automatic retries for interrupted dump operations.

Changes

Nexthop group support

Layer / File(s) Summary
Nexthop group data model
nexthop.go, nexthop_test.go
Adds group type constants, multipath member and resilient-group structs, grouping fields to Nexthop, and field initialization tests.
Netlink group encoding and decoding
nexthop_linux.go, nl/nl_linux.go
Adds resilient-group attribute constants and handles multipath and resilient-group serialization, parsing, weight normalization, timer conversion, and masked attribute types.
Group validation and NEWNEXTHOP wiring
nexthop_linux.go
Derives FAMILY_ALL for grouped nexthops, validates group type relationships, and includes group attributes in NEWNEXTHOP operations.
Nexthop group examples
examples/nexthop-group/*, examples/resilient-nexthop-group/*, examples/nexthop/*
Adds Linux command-line examples for standard, multipath, and resilient nexthops with input validation, creation, listing, cleanup, and formatted output.
Group support documentation
CHANGELOG.md
Documents the new APIs, constants, attributes, validation, retry option, and example.

Interrupted dump retries

Layer / File(s) Summary
RetryInterrupted handle option
handle_linux.go
Adds Handle.RetryInterrupted, which enables automatic retries for dump operations interrupted by EINTR.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Nexthop
  participant NexthopAttrHandlers
  participant LinuxNetlink
  Nexthop->>NexthopAttrHandlers: provide group and resilient-group fields
  NexthopAttrHandlers->>LinuxNetlink: encode group attributes
  LinuxNetlink-->>NexthopAttrHandlers: return group attributes
  NexthopAttrHandlers-->>Nexthop: decode members and timer values
Loading

Possibly related PRs

  • vishvananda/netlink#1139: Provides the preceding nexthop implementation extended by these group and resilient-group changes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: support for resilient next-hop group operations.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
nexthop.go (1)

26-32: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document NexthopResGroup and its field units; fix a typo above.

NexthopResGroup has no doc comment and none of its fields are documented, unlike NexthopGroupMpath. Since nexthop_linux.go silently converts IdleTimer/UnbalancedTimer/UnbalancedTime between seconds and kernel clock ticks, callers of this public struct have no indication these fields are expressed in seconds. Also, the NexthopGroupMpath doc comment above has a typo ("nexthtop").

♻️ Proposed doc improvements
-// NexthopGroupMpath represents one member of a nexthtop group
+// NexthopGroupMpath represents one member of a nexthop group
 type NexthopGroupMpath struct {
 	// ID of an existing nexthop to include in the group
 	ID uint32
 	// Relative weight, 1-256. Zero is treated as 1
 	Weight uint16
 }

+// NexthopResGroup represents the parameters of a resilient nexthop group.
 type NexthopResGroup struct {
+	// Number of buckets in the hash table
 	Buckets uint16
+	// Idle timer, in seconds
 	IdleTimer uint32
+	// Unbalanced timer, in seconds
 	UnbalancedTimer uint32
+	// Time out of balance, in seconds (read-only)
 	UnbalancedTime uint64
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nexthop.go` around lines 26 - 32, Document the public NexthopResGroup type
and each field, explicitly stating that IdleTimer, UnbalancedTimer, and
UnbalancedTime use seconds while Buckets is the bucket count; keep the
descriptions accurate to the kernel conversion behavior. Also correct the
“nexthtop” typo in the existing NexthopGroupMpath documentation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@nexthop_linux.go`:
- Around line 357-364: Add the missing fmt import to nexthop_linux.go so
prepareNewNexthop can compile its existing fmt.Errorf validation paths; leave
the validation logic unchanged.

In `@nexthop.go`:
- Around line 18-24: Change NexthopGroupMpath.ID from uint16 to uint32 so it
matches Nexthop.ID and the uint32 encoding/decoding in the nexthop group
serialization logic. Leave Weight and the surrounding NexthopGroupMpath
structure unchanged.

---

Nitpick comments:
In `@nexthop.go`:
- Around line 26-32: Document the public NexthopResGroup type and each field,
explicitly stating that IdleTimer, UnbalancedTimer, and UnbalancedTime use
seconds while Buckets is the bucket count; keep the descriptions accurate to the
kernel conversion behavior. Also correct the “nexthtop” typo in the existing
NexthopGroupMpath documentation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 302c24e9-8d37-4bc0-a541-12db7ab00e4f

📥 Commits

Reviewing files that changed from the base of the PR and between 4e35dc9 and d530221.

📒 Files selected for processing (3)
  • nexthop.go
  • nexthop_linux.go
  • nl/nl_linux.go

Comment thread nexthop_linux.go
Comment thread nexthop.go Outdated
@astrobounce
astrobounce marked this pull request as draft August 2, 2026 08:08
@astrobounce
astrobounce marked this pull request as ready for review August 2, 2026 17:27

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
nexthop_linux.go (2)

247-268: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Resolve the undefined resilient-group test type.

The supplied typecheck reports nexthop_test.go:16:12: undefined: NexthopResilientGroupAttrs. This decoder constructs NexthopResilientGroup. Align the test and callers with the exported type, or add the intended compatibility type. The package cannot build until the names match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nexthop_linux.go` around lines 247 - 268, The resilient-group decoder uses
NexthopResilientGroup, but callers or tests still reference the undefined
NexthopResilientGroupAttrs type. Update those references, including
nexthop_test.go, to use the exported NexthopResilientGroup type, or define an
intentional compatibility alias if that name must remain supported.

Source: Linters/SAST tools


191-198: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Reject partial NHA_GROUP payloads.

The decoder accepts every complete 8-byte prefix and silently ignores a trailing partial member. A malformed attribute can therefore produce a partial nh.Group. Validate that the payload length is divisible by sizeofNexthopGroupMember before decoding, and assign nh.Group only after the complete payload passes validation. (raw.githubusercontent.com)

🛠️ Proposed fix
 		decode: func(nh *Nexthop, attr *nl.RtAttr) {
-			nh.Group = nil
+			if len(attr.Data)%sizeofNexthopGroupMember != 0 {
+				return
+			}
+			group := make([]NexthopGroupMember, 0, len(attr.Data)/sizeofNexthopGroupMember)
 			for off := 0; off+sizeofNexthopGroupMember <= len(attr.Data); off += sizeofNexthopGroupMember {
-				nh.Group = append(nh.Group, NexthopGroupMember{
+				group = append(group, NexthopGroupMember{
 					ID:     native.Uint32(attr.Data[off : off+4]),
 					Weight: uint16(attr.Data[off+4]) + 1,
 				})
 			}
+			nh.Group = group
 		},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nexthop_linux.go` around lines 191 - 198, Update the Nexthop group decoder to
reject payloads whose length is not divisible by sizeofNexthopGroupMember before
iterating. Decode into a temporary group and assign nh.Group only after
validation and complete decoding succeed, preventing partial NHA_GROUP data from
being accepted.
🧹 Nitpick comments (1)
nexthop.go (1)

35-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

String() does not reflect the new group fields.

Nexthop now carries Group, GroupType, and ResilientGroup, but String() (Lines 48-57) still only prints ID, Blackhole, OIF, Gateway, and Protocol. Logging or debugging a group nexthop with String() silently drops its group membership and resilient configuration.

♻️ Proposed extension to `String()`
 func (h *Nexthop) String() string {
 	elems := []string{
 		"ID: " + strconv.FormatUint(uint64(h.ID), 10),
 		"Blackhole: " + strconv.FormatBool(h.Blackhole),
 		"OIF: " + strconv.FormatUint(uint64(h.OIF), 10),
 		"Gateway: " + h.Gateway.String(),
 		"Protocol: " + h.Protocol.String(),
 	}
+	if len(h.Group) > 0 {
+		elems = append(elems, fmt.Sprintf("GroupType: %d", h.GroupType))
+	}
 	return fmt.Sprintf("{%s}", strings.Join(elems, " "))
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nexthop.go` around lines 35 - 46, Update Nexthop.String() to include the
Group, GroupType, and ResilientGroup fields in its formatted output, while
preserving the existing ID, Blackhole, OIF, Gateway, and Protocol details.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 19-22: Update the changelog entries to use the exported API
identifiers from nexthop.go: replace NexthopGroupMpath with NexthopGroupMember,
NexthopResGroup with NexthopResilientGroup, and Nexthop.ResGroup with
Nexthop.ResilientGroup; keep the documented fields and group-type constants
unchanged.

In `@examples/nexthop/main.go`:
- Around line 69-89: Update buildNexthop so the OIF assignment occurs only after
the *blackhole branch returns; blackhole nexthops must contain the ID and
Blackhole fields without setting OIF, while regular gateway nexthops retain the
existing link interface assignment.

In `@nexthop_linux.go`:
- Around line 230-237: Update the resilient timer encoding in the nexthop
attribute construction to multiply IdleTimer and UnbalancedTimer using a wider
integer type, reject values whose product exceeds uint32, and propagate the
resulting error instead of encoding a wrapped value. Add boundary tests covering
42_949_672 as valid and 42_949_673 as rejected for both timer fields.

In `@nexthop_test.go`:
- Around line 14-25: Update TestNexthopGroupMemberAndResilientGroupTypes to
instantiate the existing NexthopResilientGroup type instead of the undefined
NexthopResilientGroupAttrs, preserving the Buckets and IdleTimer assertions.

---

Outside diff comments:
In `@nexthop_linux.go`:
- Around line 247-268: The resilient-group decoder uses NexthopResilientGroup,
but callers or tests still reference the undefined NexthopResilientGroupAttrs
type. Update those references, including nexthop_test.go, to use the exported
NexthopResilientGroup type, or define an intentional compatibility alias if that
name must remain supported.
- Around line 191-198: Update the Nexthop group decoder to reject payloads whose
length is not divisible by sizeofNexthopGroupMember before iterating. Decode
into a temporary group and assign nh.Group only after validation and complete
decoding succeed, preventing partial NHA_GROUP data from being accepted.

---

Nitpick comments:
In `@nexthop.go`:
- Around line 35-46: Update Nexthop.String() to include the Group, GroupType,
and ResilientGroup fields in its formatted output, while preserving the existing
ID, Blackhole, OIF, Gateway, and Protocol details.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cf092ae8-a031-4f75-b071-4fcf6eeedabb

📥 Commits

Reviewing files that changed from the base of the PR and between 685a694 and dd78b43.

⛔ Files ignored due to path filters (3)
  • examples/nexthop-group/go.sum is excluded by !**/*.sum
  • examples/nexthop/go.sum is excluded by !**/*.sum
  • examples/resilient-nexthop-group/go.sum is excluded by !**/*.sum
📒 Files selected for processing (10)
  • CHANGELOG.md
  • examples/nexthop-group/go.mod
  • examples/nexthop-group/main.go
  • examples/nexthop/go.mod
  • examples/nexthop/main.go
  • examples/resilient-nexthop-group/go.mod
  • examples/resilient-nexthop-group/main.go
  • nexthop.go
  • nexthop_linux.go
  • nexthop_test.go

Comment thread CHANGELOG.md Outdated
Comment thread examples/nexthop/main.go
Comment thread nexthop_linux.go
Comment on lines +230 to +237
if nh.ResilientGroup.IdleTimer > 0 {
b := make([]byte, 4)
native.PutUint32(b, nh.ResilientGroup.IdleTimer*nexthopResilientGroupUserHz)
attr.AddRtAttr(nl.NHA_RES_GROUP_IDLE_TIMER, b)
}
if nh.ResilientGroup.UnbalancedTimer > 0 {
b := make([]byte, 4)
native.PutUint32(b, nh.ResilientGroup.UnbalancedTimer*nexthopResilientGroupUserHz)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== files =="
git ls-files | rg '(^|/)nexthop_linux\.go$|nexthop|rtattr|resilient|timer' || true

echo "== nexthop_linux outline =="
ast-grep outline nexthop_linux.go --view expanded || true

echo "== relevant lines =="
sed -n '1,120p' nexthop_linux.go
echo "== 120-330 =="
sed -n '120,330p' nexthop_linux.go

echo "== searches =="
rg -n "nexthopResilientGroupUserHz|IdleTimer|UnbalancedTimer|NHA_RES_GROUP|PutUint32|ResilientGroup" .

Repository: vishvananda/netlink

Length of output: 25770


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== nexthop.go relevant =="
sed -n '1,70p' nexthop.go

echo "== preparation/encoding relevant =="
sed -n '330,390p' nexthop_linux.go
sed -n '360,385p' nexthop_linux.go

echo "== ready-only wrapper behavior probe =="
go version
cat > /tmp/wrap_probe.go <<'EOF'
package main

import (
	"encoding/binary"
	"encoding/hex"
	"fmt"
	"math"
)

const nexthopResilientGroupUserHz = 100

func main() {
	for _, v := range []uint32{42_949_672, 42_949_673, 100_000_000, math.MaxUint32} {
		product := v * nexthopResilientGroupUserHz
		encoded := make([]byte, 4)
		binary.LittleEndian.PutUint32(encoded, product)
		fmt.Printf("sec=%d product_uint32=%d hex=%s\n", v, product, hex.EncodeToString(encoded))
	}
}
EOF
go run /tmp/wrap_probe.go

echo "== kernel source checks online via local shell if reachable =="
for url in \
  "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/include/uapi/linux/rtnetlink.h?h=v6.8" \
  "https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/plain/net/ipv4/fib_nexthops.c?h=v6.8"
do
  echo "--- $url ---"
  curl -fsSL "$url" | rg -n "NHA_RES_GROUP_IDLE_TIMER|NHA_RES_GROUP_UNBALANCED_TIMER|idle_timer|unbalanced_timer|struct nexthop_res_bucket|clock_t" | head -80 || true
done

Repository: vishvananda/netlink

Length of output: 4758


🌐 Web query:

Linux nexthop.c NHA_RES_GROUP_IDLE_TIMER validation u32 clock_t oversized timer

💡 Result:

The attribute NHA_RES_GROUP_IDLE_TIMER in the Linux kernel's nexthop subsystem is used to configure the idle timer for resilient nexthop group buckets, measured in units of clock_t [1][2]. A known issue involving potential integer overflow was identified in the iproute2 user-space tool (the primary interface for configuring these nexthops) regarding the validation of this timer value [3]. Specifically, earlier implementations used the expression idle_timer >= ~0UL / 100 to validate the user-provided input [3]. On 64-bit systems, ~0UL is a 64-bit value, which allowed the validation check to pass for values that, when subsequently multiplied by 100 in the kernel or helper functions, would overflow a 32-bit unsigned integer (as the netlink attribute NHA_RES_GROUP_IDLE_TIMER is defined as a u32) [2][3]. To resolve this, the validation logic was updated to use UINT32_MAX / 100 instead of ~0UL / 100 [3]. This ensures that the provided idle timer value is constrained such that it will not result in an integer overflow after the unit conversion (scaling by 100) before being passed to the kernel [3]. The kernel itself defines the attribute in the netlink policy as NLA_U32, and internal kernel code converts jiffies to clock_t using jiffies_to_clock_t when reporting this value back to user space [4][5][6].

Citations:


Guard the resilient timer conversion against uint32 wraparound.

IdleTimer and UnbalancedTimer are uint32, so value*nexthopResilientGroupUserHz wraps modulo 2^32. Values above 42_949_672 encode a much shorter timer, including 42_949_673 encoding as 0x04. Compute the product in a wider type and return an error if it exceeds uint32; add boundary tests for 42_949_672 and 42_949_673.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@nexthop_linux.go` around lines 230 - 237, Update the resilient timer encoding
in the nexthop attribute construction to multiply IdleTimer and UnbalancedTimer
using a wider integer type, reject values whose product exceeds uint32, and
propagate the resulting error instead of encoding a wrapped value. Add boundary
tests covering 42_949_672 as valid and 42_949_673 as rejected for both timer
fields.

Comment thread nexthop_test.go
Removed unreleased section detailing nexthop support for resilient next-hop groups, including background and new API types.
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.

1 participant