Repository navigation
Let the socket protocols adopt and reset sockets, and report soft errors - #338
Merged
Merged
Conversation
SocketDatagramProtocol and SocketStreamProtocol can now take a socket that's already connected, replace their socket with a new one, and tell the protocol above them about errors that don't end the flow. TCP Fast Open, the source address and the end of a stream connection now behave the way TCP expects. Bugs this fixes: - The read and write sources of each socket share a descriptor of their own. A socket that replaced another used to inherit the old socket's descriptor while the old sources were still cancelling, so nothing watched the new socket and the old one stayed open. - A stream connection ends at most once. The read and write sides could both report the same failure, such as a refused connect, and a write could report the connection as up after it had failed. - With Fast Open, the connection counts as connected once the handshake finishes, so a cancel before then doesn't reset it. A writable event before the handshake starts no longer counts as its end, and SO_SNDLOWAT stays put until the socket connects. - A failed SO_REUSEADDR or SO_REUSEPORT fails setup instead of leaving a socket that can't share its address. - The stream protocol's maximumMessageSize no longer traps where Int has 32 bits. The protocols now: - Take a connected socket with adoptConnectedSocket(_:). The adopted socket skips the options that only shape the connect, such as TCP_CONNECTIONTIMEOUT, and closes when the protocol is done with it, even if setup never runs. - Open a new socket from the same parameters with reset(). The new socket connects from a port the kernel picks. - Report SocketProtocolEvent.softError when a send or receive fails and the flow goes on, and softErrorCleared when the next one in the same direction goes through. - Report connectedLocalEndpoint and connectedInterfaceIndex once connected, and maximumMessageSize, receivesSingleMessage and framesAreMessages. - Connect from the local endpoint's address and port, which connectx binds on Darwin. When the TCP options reset the local port, the connection comes from port 0 instead of setting SO_REUSEPORT. - Follow the IP options' local address preference on an IPv6 socket on Darwin, so a flow can prefer a temporary or a stable source address. The stream protocol now: - Passes the Fast Open connectx flags, sends the first write with the SYN, and holds further writes until the handshake finishes. A read before any write starts the handshake, so a peer that speaks first still gets a connection. - Sets TCP_NOTSENT_LOWAT for reduced buffering once connected, and sets TCP_MAXSEG again then. - Reports a pending SO_ERROR in place of the error from a failed read or write. The datagram protocol now: - Sets multicast loopback for a multicast destination, from the IP options, and SO_BROADCAST for a broadcast destination. It attaches UDP metadata to the datagrams it receives unless the UDP options turn metadata off. - Reports ENOBUFS as flow controlled, then flow allowed once a send goes through. - Drops a datagram whose send fails with an ICMP or policy error, and keeps the flow. - Delivers the datagrams queued with addInitialInboundData(_:ecn:) before anything it reads, and counts them in the transfer snapshot. - Limits maximumMessageSize to the path's largest UDP datagram when that's below 9216 bytes, and follows the path in updatePath(_:). SystemSocket gains init(adopting:), init(rawProtocolFamily:sockType:protocolSubType:nonBlocking:), boundIPAddress() and setSocketOption(level:name:bytes:). connectSocket and System.connectx take a source address and connectx flags, and socket creation reports the errno the call returned.
PayasR
requested review from
agnosticdev,
ekinnear and
tfpauly
as code owners
November 12, 2026 07:57
tfpauly
approved these changes
Nov 13, 2026
PathProperties.scopedInterface returns the override interface when the override scopes the path, in every build. Network.framework also returns the direct interface when NECP policy scopes the path.
PayasR
force-pushed
the
payasr/socket-protocol-lifecycle
branch
from
November 14, 2026 04:08
b55396c to
aa1a09d
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
SocketDatagramProtocol and SocketStreamProtocol can now take a socket that's already connected,
replace their socket with a new one, and tell the protocol above them about errors that don't end
the flow. TCP Fast Open, the source address and the end of a stream connection now behave the way
TCP expects.
Bugs this fixes:
replaced another used to inherit the old socket's descriptor while the old sources were still
cancelling, so nothing watched the new socket and the old one stayed open.
failure, such as a refused connect, and a write could report the connection as up after it had
failed.
before then doesn't reset it. A writable event before the handshake starts no longer counts as
its end, and SO_SNDLOWAT stays put until the socket connects.
its address.
The protocols now:
that only shape the connect, such as TCP_CONNECTIONTIMEOUT, and closes when the protocol is done
with it, even if setup never runs.
the kernel picks.
softErrorCleared when the next one in the same direction goes through.
maximumMessageSize, receivesSingleMessage and framesAreMessages.
TCP options reset the local port, the connection comes from port 0 instead of setting
SO_REUSEPORT.
prefer a temporary or a stable source address.
The stream protocol now:
writes until the handshake finishes. A read before any write starts the handshake, so a peer
that speaks first still gets a connection.
The datagram protocol now:
for a broadcast destination. It attaches UDP metadata to the datagrams it receives unless the
UDP options turn metadata off.
counts them in the transfer snapshot.
follows the path in updatePath(_:).
SystemSocket gains init(adopting:), init(rawProtocolFamily:sockType:protocolSubType:nonBlocking:),
boundIPAddress() and setSocketOption(level:name:bytes:). connectSocket and System.connectx take a
source address and connectx flags, and socket creation reports the errno the call returned.