Skip to content

Commit

Permalink
Fix getpeername() bug on Windows
Browse files Browse the repository at this point in the history
The WIN32 getpeername() function returns ENOTCONN when it uses connect()
the SOCK_NONBLOCK way. So we simply store the address, provided earlier.
  • Loading branch information
jart committed Aug 25, 2024
1 parent 908b7a8 commit f3ce684
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 3 deletions.
2 changes: 2 additions & 0 deletions libc/intrin/fds.h
Original file line number Diff line number Diff line change
@@ -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_

Expand Down Expand Up @@ -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 {
Expand Down
9 changes: 8 additions & 1 deletion libc/sock/connect-nt.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -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});
Expand All @@ -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)
Expand Down
10 changes: 8 additions & 2 deletions libc/sock/getsockname.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;
Expand All @@ -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
Expand Down
53 changes: 53 additions & 0 deletions test/libc/sock/connect_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand Down Expand Up @@ -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};
Expand Down

0 comments on commit f3ce684

Please sign in to comment.