Skip to content

fix(libkernel): count a dissolved datagram association as disconnected on macOS - #10544

Open
maximilianfv wants to merge 1 commit into
boykopovar:mainfrom
maximilianfv:fix/libkernel-macos-datagram-disconnect
Open

maximilianfv wants to merge 1 commit into
boykopovar:mainfrom
maximilianfv:fix/libkernel-macos-datagram-disconnect

Conversation

@maximilianfv

Copy link
Copy Markdown
Contributor

What

On macOS, connect to AF_UNSPEC on a connected datagram socket dissolves the association but returns -1: EAFNOSUPPORT (47) for IPv4 and EINVAL (22) for IPv6. Disconnect in libkernel/Socket/src/Udp.cpp took that as a failure. It is used by connect to port 0 on a datagram socket (ZeroPortConnect, added in #9105) and by a guest connect to AF_UNSPEC, so on macOS connect to port 0 on a connected datagram socket failed with 47 instead of EADDRNOTAVAIL (49), and guest_socket_port_zero failed.

This adds an __APPLE__ branch to Disconnect: when the host connect(AF_UNSPEC) fails but getpeername then fails with ENOTCONN, the association is gone and Disconnect goes on as on Linux; otherwise it fails with the connect error as before. Linux and Windows code is unchanged.

Host behaviour, from standalone C probes (x86_64 under Rosetta 2):

  • connected UDP socket, connect(AF_UNSPEC): IPv4 returns -1 with errno 47, IPv6 -1 with errno 22; in both cases getpeername then fails with ENOTCONN (57) and the local port is kept.
  • after the dissolve, macOS resets the local address to 0.0.0.0 and keeps the port, for an explicitly and an implicitly bound socket. That is what FreeBSD 11.4's udp_disconnect() does, as the existing "connect to AF_UNSPEC" entry in docs/dev/debt/libkernel.md describes, so the debt file is unchanged.
  • a guest connect to AF_UNSPEC already returned 47 on macOS IPv4, because Disconnect failed with the host's 47; with this change it follows the Linux path.

Prior art: the whole-port PR #7201 by @marion909 has the same change (commit c31e82f, 2026-10-16): the same check (after the failed connect, getpeername must fail with ENOTCONN), nested as #if defined(__APPLE__) inside the non-Windows branch and using a separate local for the peer address. I found the bug independently with the probes above, but #7201 had the fix first, so marion909 is credited as co-author on the commit. #7201 also changes Disconnect, so whichever merges second will conflict there; this PR carries only this fix so it can land on its own. #8658 and the PRs split from it (#10362, #10419) do not touch Disconnect.

One guest-visible result changes away from FreeBSD on macOS: a guest connect to AF_UNSPEC with a 16-byte address on a connected IPv6 datagram socket returned EINVAL (22) before this change, by way of the host's error, and now returns EAFNOSUPPORT (47), which Linux and Windows already return. FreeBSD 11.4 returns 22 there (in6_pcbladdr checks the address length before the family). With a 28-byte address the result changes from 22 to 47, which matches FreeBSD. The address-length checks are a separate gap (#10299 reworks them).

Possible follow-up, not part of this PR: libSceNet/Export.cpp has its own copy of this disconnect code (from #4669) with the same pattern. It is not changed here, and I make no claim that it is broken: guest_sce_net stops earlier on macOS (GuestSceNet.cpp:175, CheckSocketHostErrors) with and without this change, so its datagram-disconnect check is not reached.

Tested

Apple M1 Pro, macOS 27.0.1, x86_64 build under Rosetta 2. main alone does not build on macOS, so this was tested in a tree made of mugurc's macOS series (27e9689) merged with main at 1e20650, plus the macOS build parts of #10419 and other unopened macOS fixes, toggling only this change.

ctest -R '^(guest_socket_.*|guest_udp|guest_poll|guest_refused_connect|guest_datagram_.*|net_dontwait_.*|guest_sce_net|guest_inet_address)$' (33 tests):

  • Without the change: 28 pass. guest_socket_port_zero fails with connect to port 0 on a connected datagram socket must fail with EADDRNOTAVAIL (49) (errno 47). guest_sce_net, guest_poll and guest_socket_pair abort; guest_socket_read_write aborted in that run.
  • With the change: 29 pass. guest_socket_port_zero passes. guest_sce_net, guest_poll and guest_socket_pair still abort; guest_refused_connect aborted in that run.
  • guest_refused_connect and guest_socket_read_write are flaky on this machine whether or not the change is applied. Over 5 repeated runs each (counted by hand, not kept as logs), guest_refused_connect passed 1/5 with and 1/5 without the change, and guest_socket_read_write passed 5/5 with and 4/5 without.
  • guest_sce_net stops at GuestSceNet.cpp:175 (found with lldb on a -O0 rebuild), before it reaches the datagram disconnect check. Where guest_poll and guest_socket_pair stop was not found.

Not tested on Linux or Windows; their code is unchanged, as the new branch is __APPLE__ only.

Checklist

  • Based on current main; no other open PR implements the same functions (except Macos port #7201, the whole macOS port, see What)
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw (NotImplemented_nid_no_patch); silent stubs are listed under Silent stubs in their library's technical debt file
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • Fixes: none
  • Unblocks: none
  • GPUs tested: n/a
  • AI-assisted: yes

🤖 Generated with Claude Code

…d on macOS

On macOS, connect to AF_UNSPEC on a connected datagram socket dissolves
the association but returns -1, with EAFNOSUPPORT for IPv4 and EINVAL
for IPv6. Disconnect took that as a failure, so connect to port 0 on a
connected datagram socket failed with 47 instead of EADDRNOTAVAIL (49).
Treat the call as successful when the socket is no longer connected.

Co-authored-by: marion909 <103169397+marion909@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7088 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7201 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 7511 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 8605 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 9509 core/libs/prx/libkernel/Socket/src/Udp.cpp
PR 9948 core/libs/prx/libkernel/Socket/src/Udp.cpp

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

The macOS behavior appears correct from the probes and targeted guest_socket_port_zero result, but this PR duplicates an exact, still-open change already present in #7201. I verified #7201 is open and its current Udp.cpp hunk has the same failed-connect / getpeername / ENOTCONN logic. The repository contribution rule asks contributors to check that no open PR already implements the same change. Please coordinate with the #7201 author/maintainers on which PR carries this hunk and establish the sequencing so only one open change implements it before this can be approved. This is a duplicate-work concern, not a request to close PRs because of queue size. Also consider adding a guest regression for the direct AF_UNSPEC path whose result changes here; the current test evidence covers the port-zero caller.

@maximilianfv maximilianfv mentioned this pull request Oct 24, 2026
8 of 11 tasks
@Gonic0

Gonic0 commented Oct 24, 2026

Copy link
Copy Markdown

Repeated your probe on an M5 Pro, macOS 27.2 (26B5086k), built both x86_64 (Rosetta 2) and arm64. Both give the same results, for explicitly bound and implicitly bound sockets:

connect(AF_UNSPEC) getpeername after local address after port
IPv4 -1, errno 47 -1, errno 57 (ENOTCONN) 0.0.0.0 kept
IPv6 -1, errno 22 -1, errno 57 (ENOTCONN) :: kept

I also checked that the socket stays usable after the dissolve: a second connect to the old peer returns 0, and a send then goes through. So treating the failed connect plus ENOTCONN as a successful disconnect holds on 27.2 as well.

libkernel/Socket/src/Udp.cpp at 5cc1ea83ca compiles here (Apple clang 21.0.0, x86_64 Release), with no warnings in the changed lines under -Wall -Wextra. I didn't run guest_socket_port_zero.

AI-assisted: yes

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants