Fix socket error handling: WSASetLastError on Windows and recv() return value

Corrected two critical issues with socket mock precision:

**1. Windows Socket Error Handling:**
- Socket functions on Windows use WSAGetLastError/WSASetLastError, NOT errno
- Added SET_SOCKET_ERROR macro that calls:
  - WSASetLastError() on Windows
  - errno assignment on Linux
- Mapped all POSIX error codes to WSA equivalents:
  - EWOULDBLOCK → WSAEWOULDBLOCK
  - EAFNOSUPPORT → WSAEAFNOSUPPORT
  - EMFILE → WSAEMFILE
  - EBADF → WSAEBADF
  - ENOTSOCK → WSAENOTSOCK
  - EINVAL → WSAEINVAL
  - EADDRINUSE → WSAEADDRINUSE
  - EDESTADDRREQ → WSAEDESTADDRREQ
  - ECONNABORTED → WSAECONNABORTED
  - EISCONN → WSAEISCONN
  - EINPROGRESS → WSAEINPROGRESS
  - EOPNOTSUPP → WSAEOPNOTSUPP
  - ENOTCONN → WSAENOTCONN
  - ECONNRESET → WSAECONNRESET
  - ENOPROTOOPT → WSAENOPROTOOPT
  - EPIPE → WSAESHUTDOWN (Windows equivalent)

**2. Fixed mock_recv() Return Value:**
- Now returns 0 when peer closes connection (orderly shutdown)
- Previously returned -1 with ECONNRESET
- This matches standard POSIX/Winsock behavior:
  - 0 = peer closed connection gracefully
  - -1 = error occurred (check errno/WSAGetLastError)
  - >0 = bytes received

**Updated Functions:**
- mock_socket, mock_bind, mock_listen, mock_accept
- mock_connect, mock_recv, mock_send
- mock_getsockopt, mock_setsockopt

All socket functions now use correct error reporting mechanism
for their platform, improving simulation accuracy.
This commit is contained in:
Claude
2025-11-02 10:33:44 +00:00
parent 14cc295c30
commit aa4be1e203
+79 -40
View File
@@ -153,6 +153,45 @@ static Process *current_process = NULL;
// Simulated clock for deterministic time
static struct timespec simulated_time = {0, 0};
// Helper to set socket errors correctly on Windows vs Linux
#ifdef _WIN32
#define SET_SOCKET_ERROR(err) WSASetLastError(err)
#define SOCKET_ERROR_WOULDBLOCK WSAEWOULDBLOCK
#define SOCKET_ERROR_AFNOSUPPORT WSAEAFNOSUPPORT
#define SOCKET_ERROR_MFILE WSAEMFILE
#define SOCKET_ERROR_BADF WSAEBADF
#define SOCKET_ERROR_NOTSOCK WSAENOTSOCK
#define SOCKET_ERROR_INVAL WSAEINVAL
#define SOCKET_ERROR_ADDRINUSE WSAEADDRINUSE
#define SOCKET_ERROR_DESTADDRREQ WSAEDESTADDRREQ
#define SOCKET_ERROR_CONNABORTED WSAECONNABORTED
#define SOCKET_ERROR_ISCONN WSAEISCONN
#define SOCKET_ERROR_INPROGRESS WSAEINPROGRESS
#define SOCKET_ERROR_OPNOTSUPP WSAEOPNOTSUPP
#define SOCKET_ERROR_NOTCONN WSAENOTCONN
#define SOCKET_ERROR_CONNRESET WSAECONNRESET
#define SOCKET_ERROR_PROTOOPT WSAENOPROTOOPT
#define SOCKET_ERROR_PIPE WSAESHUTDOWN // Closest to EPIPE on Windows
#else
#define SET_SOCKET_ERROR(err) (errno = (err))
#define SOCKET_ERROR_WOULDBLOCK EWOULDBLOCK
#define SOCKET_ERROR_AFNOSUPPORT EAFNOSUPPORT
#define SOCKET_ERROR_MFILE EMFILE
#define SOCKET_ERROR_BADF EBADF
#define SOCKET_ERROR_NOTSOCK ENOTSOCK
#define SOCKET_ERROR_INVAL EINVAL
#define SOCKET_ERROR_ADDRINUSE EADDRINUSE
#define SOCKET_ERROR_DESTADDRREQ EDESTADDRREQ
#define SOCKET_ERROR_CONNABORTED ECONNABORTED
#define SOCKET_ERROR_ISCONN EISCONN
#define SOCKET_ERROR_INPROGRESS EINPROGRESS
#define SOCKET_ERROR_OPNOTSUPP EOPNOTSUPP
#define SOCKET_ERROR_NOTCONN ENOTCONN
#define SOCKET_ERROR_CONNRESET ECONNRESET
#define SOCKET_ERROR_PROTOOPT ENOPROTOOPT
#define SOCKET_ERROR_PIPE EPIPE
#endif
static void process_poll_array(Process *process,
void **contexts, struct pollfd *polled, int num_polled)
{
@@ -525,12 +564,12 @@ int mock_rename(char *oldpath, char *newpath)
SOCKET mock_socket(int domain, int type, int protocol)
{
if (domain != AF_INET || type != SOCK_STREAM || protocol != 0) {
errno = EAFNOSUPPORT; // Address family not supported
SET_SOCKET_ERROR(SOCKET_ERROR_AFNOSUPPORT); // Address family not supported
return INVALID_SOCKET;
}
if (current_process->num_desc == MAX_DESCRIPTORS) {
errno = EMFILE; // Too many open files
SET_SOCKET_ERROR(SOCKET_ERROR_MFILE); // Too many open files
return INVALID_SOCKET;
}
@@ -565,20 +604,20 @@ static DescriptorAddress convert_address(void *addr, size_t addr_len)
int mock_bind(SOCKET fd, void *addr, size_t addr_len)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return -1;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type != DESC_SOCKET) {
errno = ENOTSOCK; // Socket operation on non-socket
SET_SOCKET_ERROR(SOCKET_ERROR_NOTSOCK); // Socket operation on non-socket
return -1;
}
DescriptorAddress address = convert_address(addr, addr_len);
if (address.type == DESC_ADDR_VOID) {
errno = EINVAL; // Invalid argument
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL); // Invalid argument
return -1;
}
@@ -590,7 +629,7 @@ int mock_bind(SOCKET fd, void *addr, size_t addr_len)
if (address.type == DESC_ADDR_IPV4 &&
other->address.ipv4.sin_port == address.ipv4.sin_port &&
other->address.ipv4.sin_addr.s_addr == address.ipv4.sin_addr.s_addr) {
errno = EADDRINUSE; // Address already in use
SET_SOCKET_ERROR(SOCKET_ERROR_ADDRINUSE); // Address already in use
return -1;
}
}
@@ -604,19 +643,19 @@ int mock_bind(SOCKET fd, void *addr, size_t addr_len)
int mock_listen(SOCKET fd, int backlog)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return -1;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type != DESC_SOCKET) {
errno = ENOTSOCK; // Socket operation on non-socket
SET_SOCKET_ERROR(SOCKET_ERROR_NOTSOCK); // Socket operation on non-socket
return -1;
}
if (desc->address.type == DESC_ADDR_VOID) {
errno = EDESTADDRREQ; // Destination address required (socket not bound)
SET_SOCKET_ERROR(SOCKET_ERROR_DESTADDRREQ); // Destination address required (socket not bound)
return -1;
}
@@ -629,20 +668,20 @@ int mock_listen(SOCKET fd, int backlog)
SOCKET mock_accept(SOCKET fd, void *addr, socklen_t *addr_len)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return INVALID_SOCKET;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type != DESC_LISTEN_SOCKET) {
errno = EINVAL; // Invalid argument (not a listening socket)
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL); // Invalid argument (not a listening socket)
return INVALID_SOCKET;
}
DescriptorHandle peer_handle;
if (!accept_queue_pop(&desc->accept_queue, &peer_handle)) {
errno = EWOULDBLOCK; // Would block (no pending connections)
SET_SOCKET_ERROR(SOCKET_ERROR_WOULDBLOCK); // Would block (no pending connections)
return INVALID_SOCKET;
}
@@ -650,12 +689,12 @@ SOCKET mock_accept(SOCKET fd, void *addr, socklen_t *addr_len)
if (peer == NULL) {
// Peer closed without removing itself from the accept queue!
// Try next connection in queue
errno = ECONNABORTED; // Connection aborted
SET_SOCKET_ERROR(SOCKET_ERROR_CONNABORTED); // Connection aborted
return INVALID_SOCKET;
}
if (current_process->num_desc == MAX_DESCRIPTORS) {
errno = EMFILE; // Too many open files
SET_SOCKET_ERROR(SOCKET_ERROR_MFILE); // Too many open files
return INVALID_SOCKET;
}
int new_idx = 0;
@@ -683,27 +722,27 @@ SOCKET mock_accept(SOCKET fd, void *addr, socklen_t *addr_len)
int mock_getsockopt(SOCKET fd, int level, int optname, void *optval, socklen_t *optlen)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return -1;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type == DESC_EMPTY) {
errno = EBADF;
SET_SOCKET_ERROR(SOCKET_ERROR_BADF);
return -1;
}
// Only support SOL_SOCKET level for now
if (level != SOL_SOCKET) {
errno = ENOPROTOOPT; // Protocol not available
SET_SOCKET_ERROR(SOCKET_ERROR_PROTOOPT); // Protocol not available
return -1;
}
// Support SO_ERROR option
if (optname == SO_ERROR) {
if (*optlen < sizeof(int)) {
errno = EINVAL;
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL);
return -1;
}
*(int*)optval = 0; // No error
@@ -711,27 +750,27 @@ int mock_getsockopt(SOCKET fd, int level, int optname, void *optval, socklen_t *
return 0;
}
errno = ENOPROTOOPT;
SET_SOCKET_ERROR(SOCKET_ERROR_PROTOOPT);
return -1;
}
int mock_setsockopt(SOCKET fd, int level, int optname, void *optval, socklen_t optlen)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return -1;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type == DESC_EMPTY) {
errno = EBADF;
SET_SOCKET_ERROR(SOCKET_ERROR_BADF);
return -1;
}
// Only support SOL_SOCKET level for now
if (level != SOL_SOCKET) {
errno = ENOPROTOOPT; // Protocol not available
SET_SOCKET_ERROR(SOCKET_ERROR_PROTOOPT); // Protocol not available
return -1;
}
@@ -747,31 +786,31 @@ int mock_setsockopt(SOCKET fd, int level, int optname, void *optval, socklen_t o
int mock_recv(SOCKET fd, void *dst, int len, int flags)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return -1;
}
if (flags != 0) {
errno = EOPNOTSUPP; // Operation not supported
SET_SOCKET_ERROR(SOCKET_ERROR_OPNOTSUPP); // Operation not supported
return -1;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type != DESC_CONNECTION_SOCKET) {
errno = ENOTCONN; // Transport endpoint is not connected
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN); // Transport endpoint is not connected
return -1;
}
if (desc->connection_state != CONNECTION_ESTABLISHED) {
errno = ENOTCONN;
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN);
return -1;
}
Descriptor *peer = handle_to_desc(desc->connection_peer);
if (peer == NULL) {
errno = ECONNRESET; // Connection reset by peer
return -1;
// Peer closed - return 0 to indicate orderly shutdown
return 0;
}
DataQueue *input_data = &peer->output_data;
@@ -779,7 +818,7 @@ int mock_recv(SOCKET fd, void *dst, int len, int flags)
// If no data available, would block
if (bytes_read == 0) {
errno = EWOULDBLOCK;
SET_SOCKET_ERROR(SOCKET_ERROR_WOULDBLOCK);
return -1;
}
@@ -789,31 +828,31 @@ int mock_recv(SOCKET fd, void *dst, int len, int flags)
int mock_send(SOCKET fd, void *src, int len, int flags)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return -1;
}
if (flags != 0) {
errno = EOPNOTSUPP; // Operation not supported
SET_SOCKET_ERROR(SOCKET_ERROR_OPNOTSUPP); // Operation not supported
return -1;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type != DESC_CONNECTION_SOCKET) {
errno = ENOTCONN; // Transport endpoint is not connected
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN); // Transport endpoint is not connected
return -1;
}
if (desc->connection_state != CONNECTION_ESTABLISHED) {
errno = ENOTCONN;
SET_SOCKET_ERROR(SOCKET_ERROR_NOTCONN);
return -1;
}
// Check if peer is still connected
Descriptor *peer = handle_to_desc(desc->connection_peer);
if (peer == NULL) {
errno = EPIPE; // Broken pipe
SET_SOCKET_ERROR(SOCKET_ERROR_PIPE); // Broken pipe / connection shutdown
return -1;
}
@@ -822,7 +861,7 @@ int mock_send(SOCKET fd, void *src, int len, int flags)
// If queue is full, we would block
if (bytes_written < len) {
errno = EWOULDBLOCK;
SET_SOCKET_ERROR(SOCKET_ERROR_WOULDBLOCK);
return bytes_written > 0 ? bytes_written : -1;
}
@@ -832,14 +871,14 @@ int mock_send(SOCKET fd, void *src, int len, int flags)
int mock_connect(SOCKET fd, void *addr, size_t addr_len)
{
if (fd == INVALID_SOCKET || (int)fd < 0 || (int)fd >= MAX_DESCRIPTORS) {
errno = EBADF; // Bad file descriptor
SET_SOCKET_ERROR(SOCKET_ERROR_BADF); // Bad file descriptor
return -1;
}
int idx = (int) fd;
Descriptor *desc = &current_process->desc[idx];
if (desc->type != DESC_SOCKET) {
errno = EISCONN; // Transport endpoint is already connected
SET_SOCKET_ERROR(SOCKET_ERROR_ISCONN); // Transport endpoint is already connected
return -1;
}
@@ -847,12 +886,12 @@ int mock_connect(SOCKET fd, void *addr, size_t addr_len)
desc->connection_state = CONNECTION_DELAYED;
desc->connect_address = convert_address(addr, addr_len);
if (desc->connect_address.type == DESC_ADDR_VOID) {
errno = EINVAL; // Invalid argument
SET_SOCKET_ERROR(SOCKET_ERROR_INVAL); // Invalid argument
return -1;
}
// Return EINPROGRESS to indicate non-blocking connection in progress
errno = EINPROGRESS;
// Return EINPROGRESS/WSAEWOULDBLOCK to indicate non-blocking connection in progress
SET_SOCKET_ERROR(SOCKET_ERROR_INPROGRESS);
return -1;
}