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.
This commit is contained in:
Justine Tunney 2024-08-25 11:26:21 -07:00
parent 908b7a82ca
commit f3ce684aef
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
4 changed files with 71 additions and 3 deletions

View file

@ -1,5 +1,6 @@
#ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_ #ifndef COSMOPOLITAN_LIBC_CALLS_STRUCT_FD_INTERNAL_H_
#define 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" #include "libc/thread/thread.h"
COSMOPOLITAN_C_START_ COSMOPOLITAN_C_START_
@ -37,6 +38,7 @@ struct Fd {
unsigned sndtimeo; /* millis; 0 means wait forever */ unsigned sndtimeo; /* millis; 0 means wait forever */
void *connect_op; void *connect_op;
struct Cursor *cursor; struct Cursor *cursor;
struct sockaddr_storage peer;
}; };
struct Fds { struct Fds {

View file

@ -18,10 +18,11 @@
*/ */
#include "libc/assert.h" #include "libc/assert.h"
#include "libc/atomic.h" #include "libc/atomic.h"
#include "libc/intrin/fds.h"
#include "libc/calls/struct/sigset.internal.h" #include "libc/calls/struct/sigset.internal.h"
#include "libc/cosmo.h" #include "libc/cosmo.h"
#include "libc/errno.h" #include "libc/errno.h"
#include "libc/intrin/fds.h"
#include "libc/macros.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/nt/enum/wsaid.h" #include "libc/nt/enum/wsaid.h"
#include "libc/nt/errors.h" #include "libc/nt/errors.h"
@ -34,6 +35,7 @@
#include "libc/sock/struct/sockaddr.h" #include "libc/sock/struct/sockaddr.h"
#include "libc/sock/syscall_fd.internal.h" #include "libc/sock/syscall_fd.internal.h"
#include "libc/sock/wsaid.internal.h" #include "libc/sock/wsaid.internal.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/consts/o.h" #include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/sol.h" #include "libc/sysv/consts/sol.h"
#include "libc/sysv/errfuns.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 // perform normal connect
if (!(f->flags & O_NONBLOCK)) { if (!(f->flags & O_NONBLOCK)) {
f->peer.ss_family = AF_UNSPEC;
ssize_t rc = __winsock_block(f->handle, 0, false, f->sndtimeo, mask, ssize_t rc = __winsock_block(f->handle, 0, false, f->sndtimeo, mask,
sys_connect_nt_start, sys_connect_nt_start,
&(struct ConnectArgs){addr, addrsize}); &(struct ConnectArgs){addr, addrsize});
@ -122,6 +125,10 @@ static textwindows int sys_connect_nt_impl(struct Fd *f, const void *addr,
return rc; 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. // perform nonblocking connect(), i.e.
// 1. connect(O_NONBLOCK) → EINPROGRESS // 1. connect(O_NONBLOCK) → EINPROGRESS
// 2. poll(POLLOUT) // 2. poll(POLLOUT)

View file

@ -17,8 +17,8 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/calls/internal.h" #include "libc/calls/internal.h"
#include "libc/intrin/fds.h"
#include "libc/dce.h" #include "libc/dce.h"
#include "libc/intrin/fds.h"
#include "libc/intrin/strace.h" #include "libc/intrin/strace.h"
#include "libc/nt/errors.h" #include "libc/nt/errors.h"
#include "libc/nt/thunk/msabi.h" #include "libc/nt/thunk/msabi.h"
@ -28,6 +28,7 @@
#include "libc/sock/struct/sockaddr.h" #include "libc/sock/struct/sockaddr.h"
#include "libc/sock/struct/sockaddr.internal.h" #include "libc/sock/struct/sockaddr.internal.h"
#include "libc/sock/syscall_fd.internal.h" #include "libc/sock/syscall_fd.internal.h"
#include "libc/sysv/consts/af.h"
#include "libc/sysv/errfuns.h" #include "libc/sysv/errfuns.h"
__msabi extern typeof(__sys_getsockname_nt) *const __imp_getsockname; __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 (IsWindows()) {
if (__isfdkind(fd, kFdSocket)) { if (__isfdkind(fd, kFdSocket)) {
if ((rc = impl_win32(g_fds.p[fd].handle, &ss, &size))) { 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 // The socket has not been bound to an address with bind, or
// ADDR_ANY is specified in bind but connection has not yet // ADDR_ANY is specified in bind but connection has not yet
// occurred. -MSDN // occurred. -MSDN

View file

@ -34,6 +34,55 @@
#include "libc/testlib/testlib.h" #include "libc/testlib/testlib.h"
#include "libc/thread/thread.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) { TEST(connect, nonblocking) {
if (IsFreebsd()) if (IsFreebsd())
return; // TODO(jart): why did this start flaking? return; // TODO(jart): why did this start flaking?
@ -74,6 +123,10 @@ TEST(connect, nonblocking) {
ASSERT_SYS(0, 1, poll(&pfd, 1, -1)); ASSERT_SYS(0, 1, poll(&pfd, 1, -1));
ASSERT_TRUE(!!(POLLOUT & pfd.revents)); 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)); ASSERT_SYS(0, 2, write(3, "hi", 2));
{ // wait for other process to send us stuff { // wait for other process to send us stuff
struct pollfd pfd = {3, POLLIN}; struct pollfd pfd = {3, POLLIN};