Skip to content

Fix mirrored mode port tracking for implicit binds resulting from accept() calls#40287

Merged
FetoiuCatalin merged 15 commits into
masterfrom
user/cfetoiu/accept_porttracking
May 12, 2026
Merged

Fix mirrored mode port tracking for implicit binds resulting from accept() calls#40287
FetoiuCatalin merged 15 commits into
masterfrom
user/cfetoiu/accept_porttracking

Conversation

@FetoiuCatalin

@FetoiuCatalin FetoiuCatalin commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Summary of the Pull Request

The scenario of opening a listen socket on port X, accepting a connection on the listen socket (which will generate a connection socket that also uses source port X, but with an implicit bind() done by the OS), then closing the listen socket would lead to port X to be incorrectly deallocated for the guest, even though the connection socket is still using it

The Linux port tracker already has logic that queries the existing active sockets and checks what source ports they are using. This logic already sees the connection socket resulting from accept(). Then the list of currently tracked bound ports is matched against the list of sockets and if a port is not used by any socket, it is deallocated. This logic was assuming the address on which the original bind was done needs to match exactly with the address used by the socket, which is not true here (e.g. the listen socket was bound to 0.0.0.0, while the connection socket will use a non-zero source address)

Fix: In mirrored mode, Only match on protocol and port, ignoring address and family
This is consistent with how port allocations are done in GuestNetworkService::OnPortAllocationRequest, which uses just the port and protocol and uses a reference count to bundle together multiple port allocation requests, even if they have a different address or family.

PR Checklist

  • Closes: Link to issue #xxx
  • Communication: I've discussed this with core contributors already. If work hasn't been agreed, this work might be rejected
  • Tests: Added/updated if needed and all pass
  • Localization: All end user facing strings can be localized
  • Dev docs: Added/updated if needed
  • Documentation updated: If checked, please file a pull request on our docs repo and link it here: #xxx

Detailed Description of the Pull Request / Additional comments

Validation Steps Performed

Add automated test for the listen + accept scenario that is fixed
Add automated test for mirrored mode for multiple binds reusing the same port
Run existing NetworkTests, which include multiple port tracking testcases
Verified using log in GnsPortTracker constructor that the networking mode is correctly plumbed to it in both mirrored and virtio modes

Copilot AI review requested due to automatic review settings April 23, 2026 17:45

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates the Linux-side GnsPortTracker port refresh logic so that tracked port allocations are not deallocated while any active socket is still using the same (protocol, port), including sockets that implicitly “inherit” the local port via accept().

Changes:

  • Adjust OnRefreshAllocatedPorts to consider any active socket using the same protocol+port as a match (not requiring an exact PortAllocation match).
  • Add explanatory comments clarifying why implicit binds (e.g., accepted sockets) must keep allocations alive.

Comment thread src/linux/init/GnsPortTracker.cpp Outdated
Comment thread src/linux/init/GnsPortTracker.cpp Outdated
Comment thread src/linux/init/GnsPortTracker.cpp Outdated
@FetoiuCatalin

Copy link
Copy Markdown
Contributor Author

todo add automated tests for the scenario of opening listen socket, accepting a connection on that socket, closing the listen socket, verify the traffic still flows on the connection socket

Copilot AI review requested due to automatic review settings April 28, 2026 20:53

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.

Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
@FetoiuCatalin FetoiuCatalin marked this pull request as ready for review April 28, 2026 22:07
@FetoiuCatalin FetoiuCatalin requested a review from a team as a code owner April 28, 2026 22:07
@FetoiuCatalin FetoiuCatalin requested a review from OneBlue April 28, 2026 22:08
@benhillis benhillis requested a review from Copilot April 29, 2026 17:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread src/linux/init/GnsPortTracker.cpp Outdated
Comment thread src/linux/init/GnsPortTracker.cpp Outdated
Copilot AI review requested due to automatic review settings April 30, 2026 18:00

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.

Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread test/windows/NetworkTests.cpp
Comment thread src/linux/init/GnsPortTracker.h
keith-horton
keith-horton previously approved these changes Apr 30, 2026
Comment thread src/linux/init/GnsPortTracker.cpp Outdated
Copilot AI review requested due to automatic review settings May 8, 2026 00:55
@FetoiuCatalin FetoiuCatalin requested a review from a team as a code owner May 8, 2026 00:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread src/linux/init/GnsPortTracker.cpp
Comment thread src/linux/init/localhost.cpp
Comment thread test/windows/NetworkTests.cpp
@FetoiuCatalin FetoiuCatalin changed the title Fix port tracking for implicit binds resulting from accept() calls Fix mirrored mode port tracking for implicit binds resulting from accept() calls May 8, 2026
keith-horton
keith-horton previously approved these changes May 8, 2026
Copilot AI review requested due to automatic review settings May 8, 2026 17:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comment thread test/windows/NetworkTests.cpp
keith-horton
keith-horton previously approved these changes May 8, 2026
OneBlue
OneBlue previously approved these changes May 11, 2026

@OneBlue OneBlue 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.

I think this is OK as a short-term solution, even though I don't love forking the port tracking behavior between networking modes.

Long term, I think we should look into something like using cgroup_sock bind & unbind notifications.

This should allow us track when sockets are bound without having to loop with a timeout, which should remove a lot of pain points from the current implementation


// Wait > 60 seconds so that the port tracker's deallocation logic kicks in.
// See c_bind_timeout_seconds in GnsPortTracker.cpp
std::this_thread::sleep_for(std::chrono::seconds(90));

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.

I don't love the idea of waiting 1.5 minutes in the tests, since this will add a big delay in the execution.

I'm not sure what's the best way to fix this, maybe by making this configurable via wslconfig. Any thoughts on this @benhillis ?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah waiting 1.5 minutes isn't great.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree this waiting is not ideal. looking at the comments in GnsPortTracker::OnRefreshAllocatedPorts, the reasoning for this deallocation timeout is because we can't know for sure when a bind() finished and don't want to deallocate too early.

we could try lowering c_bind_timeout_seconds below 60 seconds as that is probably more than enough.

thoughts?

// Because there's no way to get notified when the bind() call actually completes, it' possible
// that this method is called before the bind() completion and so the port allocation may not be visible yet.
// To avoid deallocating ports that simply haven't been done allocating yet, m_allocatedPorts stores a timeout
// that prevents deallocating the port unless:
//
// - The port has been seen to be allocated (if so, then the timeout is empty)
// - The timeout has expired

Comment thread src/linux/init/localhost.cpp Outdated
@benhillis

Copy link
Copy Markdown
Member

I think this is OK as a short-term solution, even though I don't love forking the port tracking behavior between networking modes.

Long term, I think we should look into something like using cgroup_sock bind & unbind notifications.

This should allow us track when sockets are bound without having to loop with a timeout, which should remove a lot of pain points from the current implementation

Agreed, I really don't like diverging in behavior between the two modes.

@keith-horton

Copy link
Copy Markdown
Member

I think this is OK as a short-term solution, even though I don't love forking the port tracking behavior between networking modes.
Long term, I think we should look into something like using cgroup_sock bind & unbind notifications.
This should allow us track when sockets are bound without having to loop with a timeout, which should remove a lot of pain points from the current implementation

Agreed, I really don't like diverging in behavior between the two modes.

The 2 modes have clearly different requirements for port tracking. Thus there is required divergence to implement each set of requirements.
Unless virtio needs to support the broader socket usage in Linux that exposed this issue in mirrored mode.

e.g., the listen socket binds, but that can create N # of accept sockets, where the local addresses for the accepted socket can be different from the listen socket

@FetoiuCatalin FetoiuCatalin dismissed stale reviews from OneBlue and keith-horton via 08e49ac May 12, 2026 00:50
@benhillis

Copy link
Copy Markdown
Member

@FetoiuCatalin Thanks for the fix — the bug analysis is solid. One thing I'd like to explore before merging: rather than branching on networking mode, could we just always match by port+protocol in OnRefreshAllocatedPorts for all modes?

The allocation side (OnPortAllocationRequest) already ignores address/family and ref-counts by port+protocol, so having the deallocation side do the same would be more consistent. It would also fix this bug without introducing a mode-specific code path.

The tradeoff is that ports might be held slightly longer in non-mirrored modes, but I think that's acceptable given the allocation side already works this way. What do you think?

@FetoiuCatalin FetoiuCatalin merged commit ee92475 into master May 12, 2026
11 checks passed
@FetoiuCatalin FetoiuCatalin deleted the user/cfetoiu/accept_porttracking branch May 12, 2026 16:06
@FetoiuCatalin

FetoiuCatalin commented May 12, 2026

Copy link
Copy Markdown
Contributor Author

@FetoiuCatalin Thanks for the fix — the bug analysis is solid. One thing I'd like to explore before merging: rather than branching on networking mode, could we just always match by port+protocol in OnRefreshAllocatedPorts for all modes?

The allocation side (OnPortAllocationRequest) already ignores address/family and ref-counts by port+protocol, so having the deallocation side do the same would be more consistent. It would also fix this bug without introducing a mode-specific code path.

The tradeoff is that ports might be held slightly longer in non-mirrored modes, but I think that's acceptable given the allocation side already works this way. What do you think?

@benhillis sorry, I missed your comment before hitting merge.

the allocation method you mentioned (OnPortAllocationRequest) is specific to mirrored mode. My understanding from the code and from chat with Pierre is that virtio mode uses a different path to handle port allocations (VirtioNetworking::HandlePortNotification, VirtioNetworking::ModifyOpenPorts) which does not appear to use refcounting, which suggests allocations and deallocations need to be done individually there instead of delaying until all Linux sockets that use the port have closed.

cc @OneBlue @keith-horton

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.

6 participants