From f3ce684aef3735be6b4fe27dd09cedf64fe6ebf6 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 25 Aug 2024 11:26:21 -0700 Subject: [PATCH] Fix getpeername() bug on Windows The WIN32 getpeername() function returns ENOTCONN when it uses connect() the SOCK_NONBLOCK way. So we simply store the address, provided earlier. --- libc/intrin/fds.h | 2 ++ libc/sock/connect-nt.c | 9 +++++- libc/sock/getsockname.c | 10 +++++-- test/libc/sock/connect_test.c | 53 +++++++++++++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 3 deletions(-) diff --git a/libc/intrin/fds.h b/libc/intrin/fds.h index b9d0f490a21..a2b7e228b5f 100644 --- a/libc/intrin/fds.h +++ b/libc/intrin/fds.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_ #define COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_ +#include "libc/sock/struct/sockaddr.h" #include "libc/thread/thread.h" COSMOPOLITAN_C_START_ @@ -37,6 +38,7 @@ struct Fd { unsigned sndtimeo; /* millis; 0 means wait forever */ void *connect_op; struct Cursor *cursor; + struct sockaddr_storage peer; }; struct Fds { diff --git a/libc/sock/connect-nt.c b/libc/sock/connect-nt.c index 6fbd14937a3..1bcc1ced183 100644 --- a/libc/sock/connect-nt.c +++ b/libc/sock/connect-nt.c @@ -18,10 +18,11 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" #include "libc/atomic.h" -#include "libc/intrin/fds.h" #include "libc/calls/struct/sigset.internal.h" #include "libc/cosmo.h" #include "libc/errno.h" +#include "libc/intrin/fds.h" +#include "libc/macros.h" #include "libc/mem/mem.h" #include "libc/nt/enum/wsaid.h" #include "libc/nt/errors.h" @@ -34,6 +35,7 @@ #include "libc/sock/struct/sockaddr.h" #include "libc/sock/syscall_fd.internal.h" #include "libc/sock/wsaid.internal.h" +#include "libc/sysv/consts/af.h" #include "libc/sysv/consts/o.h" #include "libc/sysv/consts/sol.h" #include "libc/sysv/errfuns.h" @@ -109,6 +111,7 @@ static textwindows int sys_connect_nt_impl(struct Fd *f, const void *addr, // perform normal connect if (!(f->flags & O_NONBLOCK)) { + f->peer.ss_family = AF_UNSPEC; ssize_t rc = __winsock_block(f->handle, 0, false, f->sndtimeo, mask, sys_connect_nt_start, &(struct ConnectArgs){addr, addrsize}); @@ -122,6 +125,10 @@ static textwindows int sys_connect_nt_impl(struct Fd *f, const void *addr, return rc; } + // win32 getpeername() stops working in non-blocking connect mode + if (addrsize) + memcpy(&f->peer, addr, MIN(addrsize, sizeof(struct sockaddr_storage))); + // perform nonblocking connect(), i.e. // 1. connect(O_NONBLOCK) → EINPROGRESS // 2. poll(POLLOUT) diff --git a/libc/sock/getsockname.c b/libc/sock/getsockname.c index b09543f2ff5..cc596d4d031 100644 --- a/libc/sock/getsockname.c +++ b/libc/sock/getsockname.c @@ -17,8 +17,8 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/calls/internal.h" -#include "libc/intrin/fds.h" #include "libc/dce.h" +#include "libc/intrin/fds.h" #include "libc/intrin/strace.h" #include "libc/nt/errors.h" #include "libc/nt/thunk/msabi.h" @@ -28,6 +28,7 @@ #include "libc/sock/struct/sockaddr.h" #include "libc/sock/struct/sockaddr.internal.h" #include "libc/sock/syscall_fd.internal.h" +#include "libc/sysv/consts/af.h" #include "libc/sysv/errfuns.h" __msabi extern typeof(__sys_getsockname_nt) *const __imp_getsockname; @@ -45,7 +46,12 @@ static int __getsockpeername(int fd, struct sockaddr *out_addr, if (IsWindows()) { if (__isfdkind(fd, kFdSocket)) { if ((rc = impl_win32(g_fds.p[fd].handle, &ss, &size))) { - if (impl_win32 == __imp_getsockname && WSAGetLastError() == WSAEINVAL) { + if (impl_win32 == __imp_getpeername && + g_fds.p[fd].peer.ss_family != AF_UNSPEC) { + ss = g_fds.p[fd].peer; + rc = 0; + } else if (impl_win32 == __imp_getsockname && + WSAGetLastError() == WSAEINVAL) { // The socket has not been bound to an address with bind, or // ADDR_ANY is specified in bind but connection has not yet // occurred. -MSDN diff --git a/test/libc/sock/connect_test.c b/test/libc/sock/connect_test.c index 806961eb001..60bca4bb842 100644 --- a/test/libc/sock/connect_test.c +++ b/test/libc/sock/connect_test.c @@ -34,6 +34,55 @@ #include "libc/testlib/testlib.h" #include "libc/thread/thread.h" +TEST(connect, blocking) { + char buf[16] = {0}; + atomic_uint *sem = _mapshared(sizeof(unsigned)); + uint32_t addrsize = sizeof(struct sockaddr_in); + struct sockaddr_in addr = { + .sin_family = AF_INET, + .sin_addr.s_addr = htonl(0x7f000001), + }; + ASSERT_SYS(0, 3, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + ASSERT_SYS(0, 0, bind(3, (struct sockaddr *)&addr, sizeof(addr))); + ASSERT_SYS(0, 0, getsockname(3, (struct sockaddr *)&addr, &addrsize)); + ASSERT_SYS(0, 0, listen(3, SOMAXCONN)); + SPAWN(fork); + while (!*sem) + pthread_yield(); + ASSERT_SYS(0, 4, accept(3, (struct sockaddr *)&addr, &addrsize)); + ASSERT_SYS(0, 2, read(4, buf, 16)); // hi + ASSERT_SYS(0, 5, write(4, "hello", 5)); + ASSERT_SYS(0, 3, read(4, buf, 16)); // bye + PARENT(); + ASSERT_SYS(0, 0, close(3)); + ASSERT_SYS(0, 3, socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)); + ASSERT_SYS(0, 0, connect(3, (struct sockaddr *)&addr, sizeof(addr))); + *sem = 1; + { // wait until connected + struct pollfd pfd = {3, POLLOUT}; + ASSERT_SYS(0, 1, poll(&pfd, 1, -1)); + ASSERT_TRUE(!!(POLLOUT & pfd.revents)); + } + struct sockaddr_in peer; + uint32_t sz = sizeof(peer); + ASSERT_SYS(0, 0, getsockname(3, (struct sockaddr *)&peer, &sz)); + ASSERT_EQ(htonl(0x7f000001), peer.sin_addr.s_addr); + ASSERT_SYS(0, 0, getpeername(3, (struct sockaddr *)&peer, &sz)); + ASSERT_EQ(htonl(0x7f000001), peer.sin_addr.s_addr); + ASSERT_SYS(0, 2, write(3, "hi", 2)); + { // wait for other process to send us stuff + struct pollfd pfd = {3, POLLIN}; + ASSERT_SYS(0, 1, poll(&pfd, 1, -1)); + ASSERT_TRUE(!!(POLLIN & pfd.revents)); + } + ASSERT_SYS(0, 5, read(3, buf, 16)); + ASSERT_STREQ("hello", buf); + ASSERT_SYS(0, 3, write(3, "bye", 3)); + ASSERT_SYS(0, 0, close(3)); + WAIT(exit, 0); + munmap(sem, sizeof(unsigned)); +} + TEST(connect, nonblocking) { if (IsFreebsd()) return; // TODO(jart): why did this start flaking? @@ -74,6 +123,10 @@ TEST(connect, nonblocking) { ASSERT_SYS(0, 1, poll(&pfd, 1, -1)); ASSERT_TRUE(!!(POLLOUT & pfd.revents)); } + struct sockaddr_in peer; + uint32_t sz = sizeof(peer); + ASSERT_SYS(0, 0, getpeername(3, (struct sockaddr *)&peer, &sz)); + ASSERT_EQ(htonl(0x7f000001), peer.sin_addr.s_addr); ASSERT_SYS(0, 2, write(3, "hi", 2)); { // wait for other process to send us stuff struct pollfd pfd = {3, POLLIN};