Skip to content

ethtool: add RX flow steering (ntuple) bindings - #1206

Open
aarcamp wants to merge 1 commit into
vishvananda:mainfrom
aarcamp:pr/ac/ethtool-ntuple-steering
Open

ethtool: add RX flow steering (ntuple) bindings#1206
aarcamp wants to merge 1 commit into
vishvananda:mainfrom
aarcamp:pr/ac/ethtool-ntuple-steering

Conversation

@aarcamp

@aarcamp aarcamp commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Description

Add bindings for ethtool RX flow classification rules, used to steer a matching flow onto a specific RX queue. RX flow steering is exposed only through the SIOCETHTOOL ioctl; ethtool netlink does not provide messages for these operations.

Add:

  • NetDevRxFlowInsert using ETHTOOL_SRXCLSRLINS
  • NetDevRxFlowDelete using ETHTOOL_SRXCLSRLDEL
  • NetDevRxFlowList using ETHTOOL_GRXCLSRLCNT and ETHTOOL_GRXCLSRLALL

Typed matchers support ETHER_FLOW, TCP_V4_FLOW, and UDP_V4_FLOW. They serialize match values and masks into the 52-byte ethtool flow union, encoding ports, addresses, and EtherTypes in network byte order where required by the UAPI.

Represent ethtool_rxnfc logically and serialize it explicitly into the native UAPI layout. Use the 8-byte-aligned, 192-byte layout on supported Linux architectures except 386, and the 4-byte-aligned, 180-byte layout on 386. Decode ioctl responses using the same layout, including the variable-length rule location array.

Validate matcher values and interface names before issuing the ioctl.

Tests cover both ABI layouts, golden-byte serialization for TCP4 and Ether matchers, rule-location parsing, malformed inputs, and interface name validation. A privileged test against a device without ntuple support confirms that a well-formed request reaches the driver and returns EOPNOTSUPP. A full insert/list/delete round trip requires a set_rxnfc-capable device because netdevsim does not implement rxnfc.

These changes will help Cilium take advantage of the zero-copy networking features added in Linux 7.1—see the upstream queue-leasing merge.

Hardware validation: Intel ICE native and 32-bit compat ioctl paths

I tested this PR on an Intel ICE PF after upgrading a server of mine to Ubuntu's Linux 7.0 HWE kernel.

Environment

  • Host OS: Ubuntu 24.04.3 LTS
  • Kernel: 7.0.0-28-generic
  • Architecture: x86_64
  • Go toolchain: go1.25.0 linux/amd64
  • Interface: enp129s0f0np0
  • Driver: ice, kernel version 7.0.0-28-generic
  • Firmware: 4.40 0x8001ce8e 0.387.49
  • PCI address: 0000:81:00.0
  • Current combined queues: 6 (maximum 24)
  • ntuple-filters: on
  • Target queue: 1
  • Explicit rule location: 100
  • Rule table before testing: empty

The source for the standalone program used for the test is attached as netlink-pr1206-hwe7-smoke.tgz.

What the standalone program checks

For each matcher, the program:

  1. Calls NetDevRxFlowInsert with queue 1 and location 100.
  2. Checks that insertion returned location 100.
  3. Calls NetDevRxFlowList and checks that location 100 is present.
  4. Calls NetDevRxFlowDelete.
  5. Calls NetDevRxFlowList again and checks that the complete location set matches the initial set.

It refuses to run if location 100 is already occupied, uses deferred best-effort deletion if a case fails after insertion, continues through all matcher cases to report every result, performs a final location-set comparison, and exits nonzero if any case fails.

The four rules tested were:

  • TCP/IPv4 destination port 42424, mask 0xffff
  • UDP/IPv4 destination port 42425, mask 0xffff
  • Ethernet destination MAC 02:00:00:12:06:01, mask ff:ff:ff:ff:ff:ff
  • Ethernet EtherType 0x88b5, mask 0xffff

I also ran ethtool -u enp129s0f0np0 immediately before and after each complete binary run to independently verify that the driver started and ended with zero rules.

Build and execution

Native amd64:

cd "$test_dir"
/usr/local/go1.25/bin/go build \
    -o "$test_dir/ntuple-hw-amd64" \
    /tmp/netlink-pr1206-hwe7-smoke.go
sudo ethtool -u enp129s0f0np0
sudo "$test_dir/ntuple-hw-amd64" enp129s0f0np0 100
sudo ethtool -u enp129s0f0np0

Linux i386 compat ABI:

cd "$test_dir"
env GOARCH=386 CGO_ENABLED=0 /usr/local/go1.25/bin/go build \
    -o "$test_dir/ntuple-hw-386" \
    /tmp/netlink-pr1206-hwe7-smoke.go
file "$test_dir/ntuple-hw-386"
sudo ethtool -u enp129s0f0np0
sudo "$test_dir/ntuple-hw-386" enp129s0f0np0 100
sudo ethtool -u enp129s0f0np0

file confirmed that the compat test was an actual statically linked 32-bit executable:

ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked

Native amd64 result

6 RX rings available
Total 0 rules

arch=amd64 dev=enp129s0f0np0 initial-locations=[]
TCP4 insert/list: PASS (locations=[100])
TCP4 delete/cleanup: PASS (locations=[])
UDP4 insert/list: PASS (locations=[100])
UDP4 delete/cleanup: PASS (locations=[])
ETHER_DST_MAC insert/list: PASS (locations=[100])
ETHER_DST_MAC delete/cleanup: PASS (locations=[])
ETHER_PROTO insert/list: PASS (locations=[100])
ETHER_PROTO delete/cleanup: PASS (locations=[])
final-locations=[]
ALL CONTROL-PLANE CASES PASS
6 RX rings available
Total 0 rules

Linux i386 compat result

6 RX rings available
Total 0 rules

arch=386 dev=enp129s0f0np0 initial-locations=[]
TCP4 insert/list: PASS (locations=[100])
TCP4 delete/cleanup: PASS (locations=[])
UDP4 insert/list: PASS (locations=[100])
UDP4 delete/cleanup: PASS (locations=[])
ETHER_DST_MAC insert/list: PASS (locations=[100])
ETHER_DST_MAC delete/cleanup: PASS (locations=[])
ETHER_PROTO insert/list: PASS (locations=[100])
ETHER_PROTO delete/cleanup: PASS (locations=[])
final-locations=[]
ALL CONTROL-PLANE CASES PASS
6 RX rings available
Total 0 rules

This exercises the native 192-byte ethtool_rxnfc representation and, through the 32-bit executable and kernel compat ioctl path, the i386 180-byte representation implemented by this PR.

Existing privileged loopback test

I separately ran the PR's existing TestRxFlowInsertReachesDriver. That test creates a fresh network namespace and deliberately uses loopback, so its expected outcome is EOPNOTSUPP.

=== RUN   TestRxFlowInsertReachesDriver
    ethtool_ntuple_linux_test.go:327: rxnfc insert reached driver and was declined as expected: operation not supported
--- PASS: TestRxFlowInsertReachesDriver (0.00s)
PASS

Scope and limitations

This validates the complete ioctl control plane against the real ICE driver: insertion, returned location, listing, deletion, final cleanup, native ABI, and i386 compat ABI. No matching packets were transmitted, so this does not independently validate data-plane delivery to queue 1. That would require a traffic-generating peer and checking per-queue packet counters.

The final ICE rule table contained zero rules, no relevant ICE/Flow Director errors appeared in the kernel log, and all temporary remote checkouts and binaries were removed.

netlink-pr1206-hwe7-smoke.tgz

Summary by CodeRabbit

  • New Features

    • Added Linux RX flow steering support through ethtool.
    • Added APIs to insert, delete, and list receive classification rules.
    • Added matching support for Ethernet, TCP, and UDP traffic over IPv4 and IPv6.
    • Added configurable rule locations, including automatic placement and boundary locations.
  • Bug Fixes

    • Added validation and error handling for invalid devices, malformed rules, unsupported layouts, and insufficient response data.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds Linux ethtool RX flow steering support. The change defines typed matchers, architecture-specific RXNFC serialization, validation, ioctl operations, rule listing, and Linux tests.

Changes

Linux RX flow steering

Layer / File(s) Summary
Flow constants and matchers
ethtool_ntuple_linux.go
Defines RX classification commands, flow types, rule locations, and typed Ethernet, TCP4, and UDP4 matchers.
RXNFC ABI serialization
ethtool_ntuple_linux.go, ethtool_ntuple_linux_test.go
Adds 32-bit and 64-bit layouts with serialization, deserialization, size checks, and golden-byte tests.
Validation and ethtool operations
ethtool_ntuple_linux.go, ethtool_ntuple_linux_test.go
Adds matcher and device-name validation, insert/delete/list APIs, ioctl handling, rule-location parsing, and integration coverage.

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

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant NetDevRxFlowInsert
  participant UDPsocket
  participant Linuxkernel
  Caller->>NetDevRxFlowInsert: submit device and flow
  NetDevRxFlowInsert->>NetDevRxFlowInsert: validate and serialize matcher
  NetDevRxFlowInsert->>UDPsocket: open control socket
  NetDevRxFlowInsert->>Linuxkernel: SIOCETHTOOL insert request
  Linuxkernel-->>NetDevRxFlowInsert: selected rule location or error
  NetDevRxFlowInsert-->>Caller: location or error
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the addition of ethtool RX flow steering bindings, which is the main change.
✨ 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.

Add bindings for ethtool RX flow classification rules, used to steer a
matching flow onto a specific RX queue. RX flow steering is exposed only
through the SIOCETHTOOL ioctl; ethtool netlink does not provide messages
for these operations.

Add:

- NetDevRxFlowInsert using ETHTOOL_SRXCLSRLINS
- NetDevRxFlowDelete using ETHTOOL_SRXCLSRLDEL
- NetDevRxFlowList using ETHTOOL_GRXCLSRLCNT and ETHTOOL_GRXCLSRLALL

Typed matchers support ETHER_FLOW, TCP_V4_FLOW, and UDP_V4_FLOW. They
serialize match values and masks into the 52-byte ethtool flow union,
encoding ports, addresses, and EtherTypes in network byte order where
required by the UAPI.

Represent ethtool_rxnfc logically and serialize it explicitly into the
native UAPI layout. Use the 8-byte-aligned, 192-byte layout on supported
Linux architectures except 386, and the 4-byte-aligned, 180-byte layout
on 386. Decode ioctl responses using the same layout, including the
variable-length rule location array.

Validate matcher values and interface names before issuing the ioctl.

Tests cover both ABI layouts, golden-byte serialization for TCP4 and
Ether matchers, rule-location parsing, malformed inputs, and interface
name validation. A privileged test against a device without ntuple
support confirms that a well-formed request reaches the driver and
returns EOPNOTSUPP. A full insert/list/delete round trip requires a
set_rxnfc-capable device because netdevsim does not implement rxnfc.

Signed-off-by: Aaron Campbell <aaron@monkey.org>
@aarcamp
aarcamp force-pushed the pr/ac/ethtool-ntuple-steering branch from 51004b4 to 4c62d0f Compare August 4, 2026 01:20
@aarcamp
aarcamp marked this pull request as ready for review August 4, 2026 12:58

@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: 1

🧹 Nitpick comments (2)
ethtool_ntuple_linux.go (2)

148-154: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the two offset bases in ethtoolRxnfcLayout.

ringCookieOffset and locationOffset are relative to the start of fs. ruleCntOrRssCtxOffset and ruleLocsOffset are relative to the start of the whole ethtool_rxnfc buffer. The values are correct, but the mixed bases are implicit. A new field added with the wrong base would produce a silently malformed ioctl payload.

♻️ Suggested documentation, or rename the fields to encode the base
 type ethtoolRxnfcLayout struct {
-	size                  int
-	ringCookieOffset      int
-	locationOffset        int
-	ruleCntOrRssCtxOffset int
-	ruleLocsOffset        int
+	// size is the total size of struct ethtool_rxnfc.
+	size int
+	// ringCookieOffset and locationOffset are relative to the start of
+	// the embedded struct ethtool_rx_flow_spec (ethtoolRxnfcFlowSpecOffset).
+	ringCookieOffset int
+	locationOffset   int
+	// ruleCntOrRssCtxOffset and ruleLocsOffset are relative to the start of
+	// struct ethtool_rxnfc.
+	ruleCntOrRssCtxOffset int
+	ruleLocsOffset        int
 }
🤖 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 `@ethtool_ntuple_linux.go` around lines 148 - 154, Document the offset bases in
ethtoolRxnfcLayout: clarify that ringCookieOffset and locationOffset are
relative to the start of fs, while ruleCntOrRssCtxOffset and ruleLocsOffset are
relative to the complete ethtool_rxnfc buffer. Keep the existing values and
layout unchanged.

374-397: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Make NetDevRxFlowList tolerate a rule count that grows between the two ioctls.

The count comes from ETHTOOL_GRXCLSRLCNT and the buffer is sized from it. If another process inserts a rule before ETHTOOL_GRXCLSRLALL runs, the driver either returns EMSGSIZE or reports a larger rule_cnt, and the call fails. Add a bounded retry so a concurrent insert does not turn a read into a hard error.

♻️ Suggested bounded retry
-	layout := nativeEthtoolRxnfcLayout()
-	nfc := ethtoolRxnfc{
-		cmd:             ETHTOOL_GRXCLSRLALL,
-		ruleCntOrRssCtx: n,
-	}
-	buf, err := serializeEthtoolRxnfc(&nfc, layout, n)
-	if err != nil {
-		return nil, err
-	}
-	if err := ethtoolIoctl(dev, unsafe.Pointer(&buf[0])); err != nil {
-		return nil, err
-	}
-	return parseNetDevRxFlowLocations(buf, layout, n)
+	layout := nativeEthtoolRxnfcLayout()
+	var lastErr error
+	for attempt := 0; attempt < 3; attempt++ {
+		nfc := ethtoolRxnfc{
+			cmd:             ETHTOOL_GRXCLSRLALL,
+			ruleCntOrRssCtx: n,
+		}
+		buf, err := serializeEthtoolRxnfc(&nfc, layout, n)
+		if err != nil {
+			return nil, err
+		}
+		if err := ethtoolIoctl(dev, unsafe.Pointer(&buf[0])); err != nil {
+			if !errors.Is(err, unix.EMSGSIZE) {
+				return nil, err
+			}
+			lastErr = err
+			n *= 2
+			continue
+		}
+		locs, err := parseNetDevRxFlowLocations(buf, layout, n)
+		if err == nil {
+			return locs, nil
+		}
+		lastErr = err
+		n *= 2
+	}
+	return nil, lastErr

This diff needs errors in the import block.

🤖 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 `@ethtool_ntuple_linux.go` around lines 374 - 397, Update NetDevRxFlowList to
retry the count-and-list ioctl sequence when ETHTOOL_GRXCLSRLALL reports
EMSGSIZE or a larger rule_cnt, reusing the refreshed count to resize the buffer.
Add the errors import needed for matching the ioctl error, bound the retries,
and preserve existing error returns for other failures.
🤖 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 `@ethtool_ntuple_linux.go`:
- Around line 419-452: Update serializeEthtoolRxnfc and deserializeEthtoolRxnfc
to use encoding/binary.NativeEndian for native-order fields and
encoding/binary.BigEndian for network-order fields, replacing the undeclared
native and networkOrder identifiers. Add or reuse the required encoding/binary
import without changing the payload layout or ioctl flow.

---

Nitpick comments:
In `@ethtool_ntuple_linux.go`:
- Around line 148-154: Document the offset bases in ethtoolRxnfcLayout: clarify
that ringCookieOffset and locationOffset are relative to the start of fs, while
ruleCntOrRssCtxOffset and ruleLocsOffset are relative to the complete
ethtool_rxnfc buffer. Keep the existing values and layout unchanged.
- Around line 374-397: Update NetDevRxFlowList to retry the count-and-list ioctl
sequence when ETHTOOL_GRXCLSRLALL reports EMSGSIZE or a larger rule_cnt, reusing
the refreshed count to resize the buffer. Add the errors import needed for
matching the ioctl error, bound the retries, and preserve existing error returns
for other failures.
🪄 Autofix

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: 08dad887-1dc7-4597-afa3-47312ffaef97

📥 Commits

Reviewing files that changed from the base of the PR and between 156a440 and 4c62d0f.

📒 Files selected for processing (2)
  • ethtool_ntuple_linux.go
  • ethtool_ntuple_linux_test.go

Comment thread ethtool_ntuple_linux.go
@acamatcisco

acamatcisco commented Aug 4, 2026

Copy link
Copy Markdown

Document the two offset bases in ethtoolRxnfcLayout.

Refactored offset handling to render this feedback moot.

Make NetDevRxFlowList tolerate a rule count that grows between the two ioctls.

Declining this suggestion. NetDevRxFlowList intentionally matches ethtool’s two-ioctl behavior: if the table grows between calls, it returns the kernel error. A bounded retry would reduce, but not eliminate, the race.

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.

2 participants