Skip to content

*: remove pkgHandle, remove HandleOptions.NetNS, add NewHandleAtWithOptions - #1197

Open
ti-mo wants to merge 2 commits into
vishvananda:mainfrom
ti-mo:tb/remove-pkghandle
Open

*: remove pkgHandle, remove HandleOptions.NetNS, add NewHandleAtWithOptions#1197
ti-mo wants to merge 2 commits into
vishvananda:mainfrom
ti-mo:tb/remove-pkghandle

Conversation

@ti-mo

@ti-mo ti-mo commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

#1174 as it was merged had some problems. I never really vetted it against Cilium as I had to take a break from work and switch tasks. It had a few conceptual mistakes, mostly stemming from my lack of understanding of pkgHandle. The most problematic being ConfigureHandle() locking all global functions to the netns of the caller of ConfigureHandle, making e.g. LinkList operate in the main netns.

This PR proposes removing pkgHandle outright, as it only serves as a zero placeholder to promote its methods to package-global functions, and its presence being rather confusing. It also never actually holds any sockets, as doing so would break global function calls from other network namespaces, a common pattern throughout the ecosystem. Also, package-global state of this kind is not a good idea.

Summary by CodeRabbit

  • Bug Fixes

    • Improved consistency by standardizing how the active handle is obtained for many networking operations (links, routes, neighbors, filters, bridges, sockets, etc.), including subscription/dump request construction.
    • Corrected several wrapper delegations to call the right operations (e.g., link setup and several VDPA queries).
  • Breaking Changes / Behavior

    • HandleOptions no longer includes a NetNS field; namespace selection is now passed explicitly when creating handles.
  • Tests

    • Updated affected unit tests and retry/handle setup expectations.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR updates the package-level handle flow so wrappers call pkgHandle() instead of using pkgHandle directly, and reworks ConfigureHandle/HandleOptions to build handles on demand from stored options. Several wrapper families and related tests are adjusted, including devlink device-info handling and VDPA call corrections.

Changes

pkgHandle accessor migration

Layer / File(s) Summary
Core handle lifecycle and configuration
handle_linux.go, handle_linux_test.go, handle_retry_linux_test.go, netlink_test.go
ConfigureHandle now stores options for lazy handle creation, pkgHandle() returns a socket-less Handle, HandleOptions.NetNS is removed, constructors pass namespace arguments explicitly, and request creation applies RetryInterrupted when sockets are nil. Related tests and netlink setup code are updated.
Address, link, neighbor, route, and rule wrappers
addr_linux.go, link_linux.go, link_test.go, neigh_linux.go, route_linux.go, route_test.go, rule_linux.go
Package-level address, link, neighbor, route, and rule helpers now delegate through pkgHandle(), including the subscription request paths; LinkSetUp now calls the correct handle method, and the bond-slave test uses the package-level helper.
Bridge and traffic-control wrappers
bridge_linux.go, chain_linux.go, class_linux.go, filter_linux.go, qdisc_linux.go
Bridge VLAN/VNI helpers and traffic-control wrappers now delegate through pkgHandle().
Device and protocol wrappers
conntrack_linux.go, devlink_linux.go, devlink_test.go, fou_linux.go, genetlink_linux.go, gtp_linux.go, netns_linux.go, nexthop_linux.go, protinfo_linux.go, rdma_link_linux.go, socket_linux.go, vdpa_linux.go, ipset_linux.go, xfrm_policy_linux.go, xfrm_state_linux.go
Device/protocol wrapper families now call pkgHandle(), devlink info helpers capture a local handle for getDevlinkInfoMsg, tests switch to pkgHandle(), and VDPA wrappers call the corrected underlying methods where needed.

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

Possibly related PRs

Suggested reviewers: aboch, vishvananda, borkmann

🚥 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 is specific and matches the main handle-refactor changes, including removing HandleOptions.NetNS and adding NewHandleAtWithOptions.
✨ 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.

@ti-mo ti-mo changed the title Tb/remove pkghandle *: remove pkgHandle, remove HandleOptions.NetNS, add NewHandleAtWithOptions Jul 2, 2026
@ti-mo

ti-mo commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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)
handle_linux_test.go (1)

30-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert through pkgHandle() instead of pkgOptions.

This test should cover the actual package-level path introduced by this PR; otherwise ConfigureHandle could store options correctly while pkgHandle() drops them.

🧪 Proposed test tightening
 	assert.NoError(t, ConfigureHandle(HandleOptions{DisableVFInfoCollection: true}))
-	assert.True(t, pkgOptions.DisableVFInfoCollection)
+	assert.True(t, pkgHandle().options.DisableVFInfoCollection)
🤖 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 `@handle_linux_test.go` around lines 30 - 31, The test is asserting against
pkgOptions directly instead of the package-level accessor path, so it does not
verify what ConfigureHandle actually exposes through pkgHandle(). Update the
assertion in handle_linux_test.go to read the stored option from pkgHandle()
after calling ConfigureHandle, and keep the check focused on
DisableVFInfoCollection so the test covers the real package-level behavior.
🤖 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 `@handle_linux.go`:
- Around line 38-49: pkgHandle() reads pkgOptions without synchronization while
ConfigureHandle updates it under configMu, creating a potential race on
package-level calls. Update pkgHandle to read pkgOptions while holding the same
configMu used by ConfigureHandle, and copy the options into a local value before
constructing Handle so concurrent configuration and use stay safe.

In `@handle_retry_linux_test.go`:
- Around line 60-64: Avoid reassigning dumpHandle after registering t.Cleanup,
since the closure captures the variable and will end up closing the newer retry
handle twice while leaking the original one. In handle_retry_linux_test.go, keep
the first handle in a separate variable or register cleanup immediately after
each NewHandleWithOptions call, and make the same adjustment for the second
occurrence noted in the test.

---

Nitpick comments:
In `@handle_linux_test.go`:
- Around line 30-31: The test is asserting against pkgOptions directly instead
of the package-level accessor path, so it does not verify what ConfigureHandle
actually exposes through pkgHandle(). Update the assertion in
handle_linux_test.go to read the stored option from pkgHandle() after calling
ConfigureHandle, and keep the check focused on DisableVFInfoCollection so the
test covers the real package-level behavior.
🪄 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

Run ID: 688a5c97-3c9b-47f2-91d2-7d0a1eeb2bfa

📥 Commits

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

📒 Files selected for processing (31)
  • addr_linux.go
  • bridge_linux.go
  • chain_linux.go
  • class_linux.go
  • conntrack_linux.go
  • devlink_linux.go
  • devlink_test.go
  • filter_linux.go
  • fou_linux.go
  • genetlink_linux.go
  • gtp_linux.go
  • handle_linux.go
  • handle_linux_test.go
  • handle_retry_linux_test.go
  • ipset_linux.go
  • link_linux.go
  • link_test.go
  • neigh_linux.go
  • netlink_test.go
  • netns_linux.go
  • nexthop_linux.go
  • protinfo_linux.go
  • qdisc_linux.go
  • rdma_link_linux.go
  • route_linux.go
  • route_test.go
  • rule_linux.go
  • socket_linux.go
  • vdpa_linux.go
  • xfrm_policy_linux.go
  • xfrm_state_linux.go
💤 Files with no reviewable changes (1)
  • netlink_test.go

Comment thread handle_linux.go Outdated
Comment thread handle_retry_linux_test.go Outdated
@ti-mo
ti-mo force-pushed the tb/remove-pkghandle branch from de084cb to fe91d1c Compare July 7, 2026 10:49
@ti-mo

ti-mo commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@ti-mo
ti-mo marked this pull request as ready for review July 7, 2026 10:59
@ti-mo

ti-mo commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

@aboch PTAL

@aboch

aboch commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

@ti-mo

pkgHandle outright, as it only serves as a zero placeholder to promote its methods to package-global functions

Some context about this 10 year old thing. Initially this library only had pkg level methods. Then @mrjana and I encountered the runaway thread issue while operating across nw namespaces in Docker's libnetwork which causes havoc. Golang did not have yet a fix for it. We resorted to the handle thing which would allow the caller to pass the net ns; the first handle's method call would open the nl socket in the current namespace causing the handle to stick to it.

To retain the original pkg level methods, a pkg level handle was created.
As a, undocumented maybe, feature, as long as the first package level method call is done in the default netns, every other pkg level call from any netns would operate in the default netns.

Now, I do not know the current state and reliability of golang threads stitching to OS threads, but I see your change would generate a new handle each time a pkg level method is called. As a side effect, I see we lose the undocumented behavior above.

People like you who use this library in their everyday work/products are in the best position to tell whether or not retaining that behavior matters.

@ti-mo

ti-mo commented Jul 15, 2026

Copy link
Copy Markdown
Contributor Author

Hi @aboch, thanks for the reply. I was also working on netlink-related stuff during that era. Fortunately, the runtime gave us the necessary tools in 1.10 with runtime.LockOSThread. (looks like the WeaveWorks domain is now being squatted :( Martynas' blog still has a copy: https://lambda.lt/blog/2018/go_netns_issue_fix.html)

As a, undocumented maybe, feature, as long as the first package level method call is done in the default netns, every other pkg level call from any netns would operate in the default netns.

I don't think this has ever been true. At least as far back as 2016, appc/cni was calling package-level API from netns-locked goroutines: cilium/cilium@ffb26f3bc24#diff-110a0dfd47350c0dcaee4f867828908c6943b766eee7b100ab3f38f0bd816545R107-R111:

       err = ns.WithNetNS(hostNS, false, func(_ *os.File) error {
               hostVeth, err := netlink.LinkByName(hostVethName)
               if err != nil {
                       return fmt.Errorf("failed to lookup %q in %q: %v", hostVethName, hostNS.Name(), err)
               }

This requires every call to dial a new socket in order to work.

Looking at the code, pkgHandle has sockets == nil. newNetlinkRequest explicitly checks if h.sockets == nil and returns a plain request with no Sockets map attached. Then, in executeIter, req.Sockets is nil, so it falls through to getNetlinkSocket(sockType) (a brand-new socket opened in the calling thread's current netns) and closes it with defer s.Close() on return. So pkgHandle never holds a socket, and each global function operates in whatever netns the calling goroutine's OS thread happens to be in.

This patch makes this behaviour explicit and removes the ambiguity around having this globally-shared variable that's essentially unused.

ti-mo added 2 commits August 19, 2026 15:59
This commit removes the NetNS field from HandleOptions and adds it as an
argument to newHandle(). Including it in HandleOptions was a mistake, as it
locks all global functions like LinkList to the calling netns of
ConfigureHandle if the latter is used.

Add a NewHandleAtWithOptions to enable creating a handle with options in a
specific netns.

Signed-off-by: Timo Beckers <timo@incline.eu>
This commit removes pkgHandle for two main reasons:

1. It does not, actually, globally cache sockets, unlike its name would suggest.
If that were the case, global functions like LinkList would always operate in
the netns where the netlink socket was initially opened, which is not the case.

At least, this assumption held until I introduced ConfigureHandle in bab08b3,
which actually started dialing sockets on the spot (in the calling netns) to
stuff them into pkgHandle. This resulted in all global netlink functions breaking
when called from other network namespaces after calling ConfigureHandle.

2. Global state, especially of this nature, is widely frowned upon and typically
avoided wherever possible.

The PR introducing bab08b3 was passing CI, but wasn't really finished and hadn't
been vetted properly since I had to take a work break. External pressure caused
maintainers to rush the merge anyway.

Signed-off-by: Timo Beckers <timo@incline.eu>
@ti-mo
ti-mo force-pushed the tb/remove-pkghandle branch from fe91d1c to b9e1b86 Compare August 19, 2026 14:01
@ti-mo

ti-mo commented Aug 19, 2026

Copy link
Copy Markdown
Contributor Author

@aboch Ping, resolved a merge conflict.

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