Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set SO_REUSEADDR on epoll tcp listener sockets #4544

Merged
merged 1 commit into from
Sep 11, 2024

Conversation

ThadHouse
Copy link
Contributor

Description

Unix is a bit more strict about TIME_WAIT state, and actually puts any sockets that have had a valid accept() called on them into the TIME_WAIT state.

This makes writing a listener app difficult, as if that ever crashes the bind() will fail for the next few minutes.

Pretty much all other TCP libraries set SO_REUSEADDR (Including libuv, which is what our app has used before). Libuv sets it on all TCP sockets, but its generally less required on client sockets, as they rarely actually specify a local port.

Describe the purpose of and changes within this Pull Request.

Testing

Not really possible to test with our current setup, as a crash is the only way to get a listener socket into TIME_WAIT. A properly shutdown socket doesn't stay in TIME_WAIT.

Documentation

N/A

Unix is a bit more strict about TIME_WAIT state, and actually puts any sockets that have had a valid accept() called on them into the TIME_WAIT state.

This makes writing a listener app difficult, as if that ever crashes the bind() will fail for the next few minutes.

Pretty much all other TCP libraries set SO_REUSEADDR (Including libuv, which is what our app has used before). Libuv sets it on all TCP sockets, but its generally less required on client sockets, as they rarely actually specify a local port.
@ThadHouse ThadHouse requested a review from a team as a code owner September 10, 2024 04:27
@nibanks nibanks added the external Proposed by non-MSFT label Sep 10, 2024
Copy link

codecov bot commented Sep 10, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 86.67%. Comparing base (733da8d) to head (21d8366).
Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4544      +/-   ##
==========================================
+ Coverage   86.37%   86.67%   +0.29%     
==========================================
  Files          56       56              
  Lines       15537    17354    +1817     
==========================================
+ Hits        13420    15041    +1621     
- Misses       2117     2313     +196     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@nibanks nibanks merged commit 6976ecf into microsoft:main Sep 11, 2024
470 of 471 checks passed
} else if (SocketType == CXPLAT_SOCKET_TCP_LISTENER) {
//
// Set SO_REUSEADDR on listener sockets to avoid
// TIME_WAIT state on shutdown.
Copy link
Contributor

Choose a reason for hiding this comment

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

I am a bit confused. The listener socket itself can go from listen state to timewait state? I thought a socket had to go through finwait1 and finwait2 to enter timewait state (which implicitly means only sockets who initiate graceful shutdown).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, on linux a listener socket itself can go into timewait. Its a bit weird. But it was extremely easy to reproduce.

Copy link
Contributor

@csujedihy csujedihy Sep 11, 2024

Choose a reason for hiding this comment

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

Mind sharing how it could be repro'd?

Just create a listener socket and have a connection accepted and kill both the accepted connection and the listener socket itself? Then bind to the same listening port will fail?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup. Have the listener socket accept a connection, and then kill the listener side. The listener side then will not be able to restart due to a bind failure.

Copy link
Contributor

Choose a reason for hiding this comment

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

I am really surprised linux has this weird behavior as it violates RFC793.

image

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wouldn't the listener socket follow the left side of that tree down to TIME_WAIT?

Copy link
Contributor

@csujedihy csujedihy Sep 11, 2024

Choose a reason for hiding this comment

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

The socket that's accepted from the listener socket would follow the left side but the listener socket itself, in my understanding, would not. When you call accept() on a listener socket, you get a new socket (in SynRcvd or ESTAB state) and the listener itself should stay in listening state.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It was a bit confusing to me too. libuv actually sets SO_REUSEADDR for both Client initiated sockets and listener sockets unconditionally, but at least for my use case I didn't need it set on the client side, and thats a little more invasive too.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
external Proposed by non-MSFT
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants