Skip to content

[AI] fix: stop Execute after ACK or Done - #315

Merged
nickgarlis merged 2 commits into
mdlayher:mainfrom
mrIncompetent:fix/execute-terminal-response
Aug 14, 2026
Merged

[AI] fix: stop Execute after ACK or Done#315
nickgarlis merged 2 commits into
mdlayher:mainfrom
mrIncompetent:fix/execute-terminal-response

Conversation

@mrIncompetent

Copy link
Copy Markdown
Contributor

Execute could wait forever when conntrack sent a Multi reply followed by an ACK without Done.

For requests with Acknowledge, keep receiving until ACK or Done, following kernel YNL behavior.

Notes:

  • AI wrote the commit with my steering. Assume the absolute worst.
  • Some tests in github.com/mdlayher/ethtool will fail with this commit as those tests don't mock Error (0) .
  • I ran this commit(all tests) against all kernel versions since v5.0, all pass except the cleanup fails on v5.0 due to a missing bugfix in the kernel release

Execute could wait forever when conntrack sent a Multi reply followed by an ACK without Done.

For requests with Acknowledge, keep receiving until ACK or Done, following kernel YNL behavior.
@nickgarlis

Copy link
Copy Markdown
Collaborator

Hi @mrIncompetent

Thanks for the PR! I will try to review as soon as I can. Some initial questions after looking at it...

Execute could wait forever when conntrack sent a Multi reply followed by an ACK without Done.

Judging from your tests, you get a multi-part reply even though you do not set a Dump flag ? I wonder whether that's expected. What's also surprising is that the ACK terminates the multi-part reply. Have you verified that this is the behavior in other kernel versions ? Could it be a bug with the version that you're running ?

For requests with Acknowledge, keep receiving until ACK or Done, following kernel YNL behavior.

Indeed, they do seem to exit when an ACK is received, I wonder whether that's the reason for it.

Could you provide some more details about the issue ? Are you replicating the exact same requests that conntrack makes ?

Assuming the fix is correct, we should consider having it in the plain receive path as well.

@mrIncompetent

Copy link
Copy Markdown
Contributor Author

Judging from your tests, you get a multi-part reply even though you do not set a Dump flag ?

Yes, when getting a single conntrack entry via github.com/mdlayher/netlink:

	conn, err := ct.Dial(&mdnetlink.Config{NetNS: int(routerNS)})
	if err != nil {
		t.Fatal(err)
	}
	defer conn.Close()

	var f ct.Flow
	f.TupleReply.IP.SourceAddress = netip.MustParseAddr("10.0.4.1")
	f.TupleReply.Proto.SourcePort = 9090
	f.TupleReply.IP.DestinationAddress = netip.MustParseAddr("10.0.4.2")
	f.TupleReply.Proto.DestinationPort = pair.clientPort
	f.TupleReply.Proto.Protocol = unix.IPPROTO_TCP

	got, err := conn.Get(f)
	if err != nil {
		t.Fatal(err)
	}

conn.Get only sets Request and Acknowledge:

	req, err := netfilter.MarshalNetlink(
		netfilter.Header{
			SubsystemID: netfilter.NFSubsysCTNetlink,
			MessageType: netfilter.MessageType(ctGet),
			Family:      pf,
			Flags:       netlink.Request | netlink.Acknowledge,
		}, attrs)

Have you verified that this is the behavior in other kernel versions ? Could it be a bug with the version that you're running ?

The added integration test does exactly that - admittingly the assertion code isn't the most obvious.
The test got executed on all kernels since v5.0 (Only the last patch release).
I can add the AI slop that runs the tests against a range of kernels tomorrow if desired.

Could you provide some more details about the issue ? Are you replicating the exact same requests that conntrack makes ?

Yup, see above snippet.

Assuming the fix is correct, we should consider having it in the plain receive path as well.

I can have a look!

@nickgarlis

Copy link
Copy Markdown
Collaborator

Seems like the kernel sets the NLM_F_MULTI when port_id != 0 for conntrack. It seems like NLM_F_MULTI may mean something else there.

From our side this might be a regression introduced in 5af0e4f. I'll try to understand more about this tomorrow. There might more netfilter subsystems that are affected.

The test got executed on all kernels since v5.0 (Only the last patch release).

👍

I can have a look!

I believe the fix would be to reset more in receiveLockedIter and discard any waitForAck logic.

for {
  more = false
  for m, err := range c.sock.ReceiveIter() {
  ...

Thanks for the detailed explanation!

Receive kept the multipart state set after a datagram containing NLM_F_MULTI. When conntrack returned a single Multi reply followed by an acknowledgement in a separate datagram, it waited indefinitely for another response.

Reset the state before each Socket.ReceiveIter call so completion is determined by the latest datagram, restoring the behavior from before ReceiveIter was introduced.
@mrIncompetent

Copy link
Copy Markdown
Contributor Author

@nickgarlis I'm very sorry for not getting back here!

I've tested the proposed minimal fix (more reset at loop begin) across the entire range of kernels locally & it passes.
It's pushed in 8d848ae

@nickgarlis nickgarlis left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for changing your approach. The tests look good!

@nickgarlis
nickgarlis merged commit ea3320c into mdlayher:main Aug 14, 2026
6 of 10 checks passed
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