From 15b53ae001ed47c6b454ccb9969d88960fe0f10b Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Sun, 15 Oct 2023 12:56:21 +0100 Subject: [PATCH 1/3] Fix handling of FIONREAD socket ioctl. When there are no packets waiting, ioctlsocket() should return success and set *argp to zero. When there are multiple packets waiting, ioctlsocket() should set *argp to the accumulated size of all received payloads. As far as I can tell, there's no way to inspect beyond the first packet queued on a socket, so we must receive and queue all packets from the socket to be able to know how much is available. The recv pipeline has been reworked to queue packets in this manner, and select() will try to emulate the normal behaviour we previously got for free by passing the socket fd straight through in readfds. I don't *think* WSAAsyncSelect() needs any changes since the socket will still raise window messages as appropriate before we have any opportunity to shunt the packet into the receive queue. --- src/ipxwrapper.h | 52 ++++++ src/ipxwrapper_stubs.txt | 2 +- src/winsock.c | 336 ++++++++++++++++++++++++++++++++------- 3 files changed, 332 insertions(+), 58 deletions(-) diff --git a/src/ipxwrapper.h b/src/ipxwrapper.h index ef02c4e..91fe6ab 100644 --- a/src/ipxwrapper.h +++ b/src/ipxwrapper.h @@ -60,6 +60,55 @@ typedef struct ipx_socket ipx_socket; typedef struct ipx_packet ipx_packet; +#define RECV_QUEUE_MAX_PACKETS 32 + +#define IPX_RECV_QUEUE_FREE -1 +#define IPX_RECV_QUEUE_LOCKED -2 + +/* Any AF_IPX IPX socket has an associated recv_queue. + * + * When a recv_pump() operation is running, the sockets lock has to be released + * in case the recv() blocks, which means the socket could be closed before it + * regains the lock. + * + * An ipx_recv_queue isn't destroyed until the refcount reaches zero. The + * ipx_socket holds one reference and each in-progress recv_pump() also holds a + * reference while the sockets lock isn't held. + * + * Access to the refcount is protected by refcount_lock. + * + * data[] holds an array of buffers for each queued packet, the status and size + * of which is indicated by the sizes[] array. + * + * If sizes[x] is IPX_RECV_QUEUE_FREE, the buffer is available to be claimed by + * a recv_pump() operation, which then sets it to IPX_RECV_QUEUE_LOCKED until + * it completes, which prevents a recv_pump() in another thread from trying to + * use the same receive buffer. Once a packet is read in, sizes[x] is set to + * the size of the packet and x is added to the end of the ready[] array. + * + * When a read is requested, the packet will be read from the data[] index + * stored in ready[0], and unless MSG_PEEK was used, that slot will then be + * released (sizes[x] set to IPX_RECV_QUEUE_FREE) and any subsequent slots in + * read[] will be advanced for the next read to pick up from ready[0]. + * + * Access to the ready, n_ready and sizes members is only permitted when a + * thread holds the main sockets lock. +*/ + +struct ipx_recv_queue +{ + CRITICAL_SECTION refcount_lock; + int refcount; + + int ready[RECV_QUEUE_MAX_PACKETS]; + int n_ready; + + unsigned char data[RECV_QUEUE_MAX_PACKETS][MAX_PKT_SIZE]; + int sizes[RECV_QUEUE_MAX_PACKETS]; +}; + +typedef struct ipx_recv_queue ipx_recv_queue; + struct ipx_socket { SOCKET fd; @@ -79,6 +128,8 @@ struct ipx_socket { /* Address used with connect call, only set when IPX_CONNECTED is */ struct sockaddr_ipx remote_addr; + struct ipx_recv_queue *recv_queue; + UT_hash_handle hh; }; @@ -165,5 +216,6 @@ int PASCAL r_getpeername(SOCKET fd, struct sockaddr *addr, int *addrlen); int PASCAL r_listen(SOCKET s, int backlog); SOCKET PASCAL r_accept(SOCKET s, struct sockaddr *addr, int *addrlen); int PASCAL r_WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent); +int WSAAPI r_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const PTIMEVAL timeout); #endif /* !IPXWRAPPER_H */ diff --git a/src/ipxwrapper_stubs.txt b/src/ipxwrapper_stubs.txt index a1eea56..e4acacb 100644 --- a/src/ipxwrapper_stubs.txt +++ b/src/ipxwrapper_stubs.txt @@ -7,7 +7,7 @@ htonl:4 ntohl:4 htons:4 ntohs:4 -select:4 +r_select:4 r_listen:4 r_accept:4 WSACreateEvent:4 diff --git a/src/winsock.c b/src/winsock.c index 2c920f1..e6c17c5 100644 --- a/src/winsock.c +++ b/src/winsock.c @@ -303,6 +303,26 @@ INT WINAPI WSHEnumProtocols(LPINT protocols, LPWSTR ign, LPVOID buf, LPDWORD bsp return do_EnumProtocols(protocols, buf, bsptr, false); } +static int recv_queue_adjust_refcount(ipx_recv_queue *recv_queue, int adj) +{ + EnterCriticalSection(&(recv_queue->refcount_lock)); + int new_refcount = (recv_queue->refcount += adj); + LeaveCriticalSection(&(recv_queue->refcount_lock)); + + return new_refcount; +} + +static void release_recv_queue(ipx_recv_queue *recv_queue) +{ + int new_refcount = recv_queue_adjust_refcount(recv_queue, -1); + + if(new_refcount == 0) + { + DeleteCriticalSection(&(recv_queue->refcount_lock)); + free(recv_queue); + } +} + SOCKET WSAAPI socket(int af, int type, int protocol) { log_printf(LOG_DEBUG, "socket(%d, %d, %d)", af, type, protocol); @@ -318,10 +338,38 @@ SOCKET WSAAPI socket(int af, int type, int protocol) return -1; } + ipx_recv_queue *recv_queue = malloc(sizeof(ipx_recv_queue)); + if(recv_queue == NULL) + { + free(nsock); + + WSASetLastError(ERROR_OUTOFMEMORY); + return -1; + } + + if(!InitializeCriticalSectionAndSpinCount(&(recv_queue->refcount_lock), 0x80000000)) + { + log_printf(LOG_ERROR, "Failed to initialise critical section: %s", w32_error(GetLastError())); + WSASetLastError(GetLastError()); + + free(recv_queue); + free(nsock); + return -1; + } + + recv_queue->refcount = 1; + recv_queue->n_ready = 0; + + for(int i = 0; i < RECV_QUEUE_MAX_PACKETS; ++i) + { + recv_queue->sizes[i] = IPX_RECV_QUEUE_FREE; + } + if((nsock->fd = r_socket(AF_INET, SOCK_DGRAM, 0)) == -1) { log_printf(LOG_ERROR, "Cannot create UDP socket: %s", w32_error(WSAGetLastError())); + release_recv_queue(recv_queue); free(nsock); return -1; } @@ -329,6 +377,8 @@ SOCKET WSAAPI socket(int af, int type, int protocol) nsock->flags = IPX_SEND | IPX_RECV | IPX_RECV_BCAST; nsock->s_ptype = (protocol ? protocol - NSPROTO_IPX : 0); + nsock->recv_queue = recv_queue; + log_printf(LOG_INFO, "IPX socket created (fd = %d)", nsock->fd); lock_sockets(); @@ -384,6 +434,8 @@ SOCKET WSAAPI socket(int af, int type, int protocol) nsock->flags |= IPX_IS_SPXII; } + nsock->recv_queue = NULL; + log_printf(LOG_INFO, "SPX socket created (fd = %d)", nsock->fd); lock_sockets(); @@ -425,6 +477,11 @@ int WSAAPI closesocket(SOCKET sockfd) log_printf(LOG_INFO, "Socket %d (%s) closed", sockfd, (sock->flags & IPX_IS_SPX ? "SPX" : "IPX")); + if(sock->recv_queue != NULL) + { + release_recv_queue(sock->recv_queue); + } + if(sock->flags & IPX_BOUND) { CloseHandle(sock->sock_mut); @@ -687,6 +744,121 @@ int WSAAPI getsockname(SOCKET fd, struct sockaddr *addr, int *addrlen) } } +static BOOL reclaim_socket(ipx_socket *sockptr, int lookup_fd) +{ + /* Reclaim the lock, ensure the socket hasn't been + * closed by the application (naughty!) while we were + * waiting. + */ + + ipx_socket *reclaim_sock = get_socket(lookup_fd); + if(sockptr != reclaim_sock) + { + log_printf(LOG_DEBUG, "Application closed socket while inside a WinSock call!"); + + if(reclaim_sock) + { + unlock_sockets(); + } + + return FALSE; + } + + return TRUE; +} + +static int recv_pump(ipx_socket *sockptr, BOOL block) +{ + int fd = sockptr->fd; + + if(!block) + { + fd_set read_fds; + FD_ZERO(&read_fds); + + FD_SET(fd, &read_fds); + + struct timeval timeout = { 0, 0 }; + + int r = r_select(-1, &read_fds, NULL, NULL, &timeout); + if(r == -1) + { + unlock_sockets(); + return -1; + } + else if(r == 0) + { + /* No packet waiting in underlying recv buffer. */ + return 0; + } + } + + ipx_recv_queue *queue = sockptr->recv_queue; + + int recv_slot = -1; + + for(int i = 0; i < RECV_QUEUE_MAX_PACKETS; ++i) + { + if(queue->sizes[i] == IPX_RECV_QUEUE_FREE) + { + queue->sizes[i] = IPX_RECV_QUEUE_LOCKED; + recv_queue_adjust_refcount(queue, 1); + + recv_slot = i; + break; + } + } + + if(recv_slot < 0) + { + /* No free recv_queue slots. */ + return 0; + } + + unlock_sockets(); + + int r = r_recv(fd, (char*)(queue->data[recv_slot]), MAX_PKT_SIZE, 0); + + if(!reclaim_socket(sockptr, fd)) + { + /* The application closed the socket while we were in the recv() call. + * Just discard our handle, let the queue be destroyed. + */ + + release_recv_queue(queue); + WSASetLastError(WSAENOTSOCK); + return -1; + } + + if(r == -1) + { + queue->sizes[recv_slot] = IPX_RECV_QUEUE_FREE; + release_recv_queue(queue); + unlock_sockets(); + return -1; + } + + struct ipx_packet *packet = (struct ipx_packet*)(queue->data[recv_slot]); + + if(r < sizeof(ipx_packet) - 1 || r != packet->size + sizeof(ipx_packet) - 1) + { + log_printf(LOG_ERROR, "Invalid packet received on loopback port!"); + + queue->sizes[recv_slot] = IPX_RECV_QUEUE_FREE; + release_recv_queue(queue); + + WSASetLastError(WSAEWOULDBLOCK); + unlock_sockets(); + return -1; + } + + queue->sizes[recv_slot] = r; + queue->ready[queue->n_ready] = recv_slot; + ++(queue->n_ready); + + return 1; +} + /* Recieve a packet from an IPX socket * addr must be NULL or a region of memory big enough for a sockaddr_ipx * @@ -694,40 +866,31 @@ int WSAAPI getsockname(SOCKET fd, struct sockaddr *addr, int *addrlen) * The size of the packet will be returned on success, even if it was truncated */ static int recv_packet(ipx_socket *sockptr, char *buf, int bufsize, int flags, struct sockaddr_ipx_ext *addr, int addrlen) { - SOCKET fd = sockptr->fd; - int is_bound = sockptr->flags & IPX_BOUND; - int extended_addr = sockptr->flags & IPX_EXT_ADDR; - - unlock_sockets(); - - if(!is_bound) { + if(!(sockptr->flags & IPX_BOUND)) + { + unlock_sockets(); + WSASetLastError(WSAEINVAL); return -1; } - char *recvbuf = malloc(MAX_PKT_SIZE); - if(!recvbuf) { - WSASetLastError(ERROR_OUTOFMEMORY); - return -1; - } - - struct ipx_packet *packet = (struct ipx_packet*)(recvbuf); - - int rval = r_recv(fd, recvbuf, MAX_PKT_SIZE, flags); - if(rval == -1) { - free(recvbuf); - return -1; - } - - if(rval < sizeof(ipx_packet) - 1 || rval != packet->size + sizeof(ipx_packet) - 1) + /* Loop here in case some crazy application does concurrent recv() calls + * and they race between putting packets on the queue and handling them. + */ + while(sockptr->recv_queue->n_ready < 1) { - log_printf(LOG_ERROR, "Invalid packet received on loopback port!"); - - free(recvbuf); - WSASetLastError(WSAEWOULDBLOCK); - return -1; + if(recv_pump(sockptr, TRUE) < 0) + { + /* Socket closed or recv() error. */ + return -1; + } } + int slot = sockptr->recv_queue->ready[0]; + + struct ipx_packet *packet = (struct ipx_packet*)(sockptr->recv_queue->data[slot]); + assert(sockptr->recv_queue->sizes[slot] >= 0); + if(min_log_level <= LOG_DEBUG) { IPX_STRING_ADDR(addr_s, addr32_in(packet->src_net), addr48_in(packet->src_node), packet->src_socket); @@ -741,7 +904,7 @@ static int recv_packet(ipx_socket *sockptr, char *buf, int bufsize, int flags, s memcpy(addr->sa_nodenum, packet->src_node, 6); addr->sa_socket = packet->src_socket; - if(extended_addr) { + if(sockptr->flags & IPX_EXT_ADDR) { if(addrlen >= sizeof(struct sockaddr_ipx_ext)) { addr->sa_ptype = packet->ptype; addr->sa_flags = 0; @@ -774,8 +937,17 @@ static int recv_packet(ipx_socket *sockptr, char *buf, int bufsize, int flags, s } memcpy(buf, packet->data, packet->size <= bufsize ? packet->size : bufsize); - rval = packet->size; - free(recvbuf); + int rval = packet->size; + + if((flags & MSG_PEEK) == 0) + { + sockptr->recv_queue->sizes[slot] = IPX_RECV_QUEUE_FREE; + + --(sockptr->recv_queue->n_ready); + memmove(&(sockptr->recv_queue->ready[0]), &(sockptr->recv_queue->ready[1]), (sockptr->recv_queue->n_ready * sizeof(int))); + } + + unlock_sockets(); return rval; } @@ -1625,39 +1797,33 @@ int PASCAL ioctlsocket(SOCKET fd, long cmd, u_long *argp) if(cmd == FIONREAD && !(sock->flags & IPX_IS_SPX)) { - /* Test to see if data is waiting. */ - - fd_set fdset; - struct timeval tv = {0,0}; - - FD_ZERO(&fdset); - FD_SET(sock->fd, &fdset); - - int r = select(1, &fdset, NULL, NULL, &tv); - - if(r == -1) + while(1) { - unlock_sockets(); - return -1; - } - else if(r == 0) - { - *(unsigned long*)(argp) = 0; + int r = recv_pump(sock, FALSE); + if(r < 0) + { + /* Error in recv_pump() */ + return -1; + } - unlock_sockets(); - return -1; + if(r == 0) + { + /* No more packets ready to read from underlying socket. */ + break; + } } - /* Get the size of the packet. */ + unsigned long accumulated_packet_data = 0; - char tmp_buf; - - if((r = recv_packet(sock, &tmp_buf, 1, MSG_PEEK, NULL, 0)) == -1) + for(int i = 0; i < sock->recv_queue->n_ready; ++i) { - return -1; + const ipx_packet *packet = (const ipx_packet*)(sock->recv_queue->data[ sock->recv_queue->ready[i] ]); + accumulated_packet_data += packet->size; } - *(unsigned long*)(argp) = r; + unlock_sockets(); + + *(unsigned long*)(argp) = accumulated_packet_data; return 0; } @@ -1898,7 +2064,7 @@ static int _connect_spx(ipx_socket *sock, struct sockaddr_ipx *ipxaddr) .tv_usec = ((wait_until - now) % 1000) * 1000 }; - if(select(1, &fdset, NULL, NULL, &tv) == -1) + if(r_select(1, &fdset, NULL, NULL, &tv) == -1) { closesocket(lookup_fd); free(packet); @@ -2011,7 +2177,7 @@ static int _connect_spx(ipx_socket *sock, struct sockaddr_ipx *ipxaddr) FD_ZERO(&e_fdset); FD_SET(sock->fd, &e_fdset); - if(select(1, NULL, &w_fdset, &e_fdset, NULL) == 1 && FD_ISSET(sock->fd, &w_fdset)) + if(r_select(1, NULL, &w_fdset, &e_fdset, NULL) == 1 && FD_ISSET(sock->fd, &w_fdset)) { goto CONNECTED; } @@ -2493,3 +2659,59 @@ int PASCAL WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent) return r_WSAAsyncSelect(s, hWnd, wMsg, lEvent); } + +int WSAAPI select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, const PTIMEVAL timeout) +{ + const struct timeval TIMEOUT_IMMEDIATE = { 0, 0 }; + const struct timeval *use_timeout = timeout; + + fd_set force_read_fds; + FD_ZERO(&force_read_fds); + + for(unsigned int i = 0; i < readfds->fd_count; ++i) + { + int fd = readfds->fd_array[i]; + + ipx_socket *sockptr = get_socket(fd); + if(sockptr != NULL) + { + if(sockptr->flags & IPX_IS_SPX) + { + unlock_sockets(); + continue; + } + + if(sockptr->recv_queue->n_ready > 0) + { + /* There is data in the receive queue for this socket, but the + * underlying socket isn't necessarily readable, so we reduce the + * select() timeout to zero to ensure it returns immediately and + * inject this fd back into readfds at the end if necessary. + */ + + FD_SET(fd, &force_read_fds); + use_timeout = &TIMEOUT_IMMEDIATE; + } + + unlock_sockets(); + } + } + + int r = r_select(nfds, readfds, writefds, exceptfds, (const PTIMEVAL)(use_timeout)); + + if(r >= 0) + { + for(unsigned int i = 0; i < force_read_fds.fd_count; ++i) + { + int fd = force_read_fds.fd_array[i]; + + if(!FD_ISSET(fd, readfds)) + { + FD_SET(fd, readfds); + ++r; + } + } + } + + return r; +} From e9c45b8ac58933a2bf2fcb2db5a6a25654301ef5 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Wed, 18 Oct 2023 20:24:05 +0100 Subject: [PATCH 2/3] Fix select(), add some tests. --- Makefile | 5 +- src/ipxwrapper.def | 1 + src/winsock.c | 44 +++++++------- src/wsock32_stubs.txt | 2 +- tests/25-fionread.t | 34 +++++++++++ tests/fionread.c | 134 ++++++++++++++++++++++++++++++++++++++++++ tools/tools.h | 2 +- 7 files changed, 199 insertions(+), 23 deletions(-) create mode 100644 tests/25-fionread.t create mode 100644 tests/fionread.c diff --git a/Makefile b/Makefile index ac9a501..494efb0 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ BIN_FILES := $(shell cat manifest.bin.txt) SRC_FILES := $(shell cat manifest.src.txt) # Tests to compile before running the test suite. -TESTS := tests/addr.exe tests/addrcache.exe tests/ethernet.exe +TESTS := tests/addr.exe tests/addrcache.exe tests/ethernet.exe tools/fionread.exe # Tools to compile before running the test suite. TOOLS := tools/socket.exe tools/list-interfaces.exe tools/bind.exe tools/ipx-send.exe \ @@ -178,6 +178,9 @@ tests/%.o: tests/%.c $(CC) $(CFLAGS) $(DEPFLAGS) -I./ -c -o $@ $< $(DEPPOST) +tools/fionread.exe: tests/fionread.o tests/tap/basic.o src/addr.o + $(CC) $(CFLAGS) -o $@ $^ -lwsock32 + tools/%.exe: tools/%.o src/addr.o $(CC) $(CFLAGS) -o $@ $^ -lwsock32 -lole32 -lrpcrt4 diff --git a/src/ipxwrapper.def b/src/ipxwrapper.def index c3f10ab..934c3d8 100644 --- a/src/ipxwrapper.def +++ b/src/ipxwrapper.def @@ -21,3 +21,4 @@ EXPORTS listen accept WSAAsyncSelect + select diff --git a/src/winsock.c b/src/winsock.c index e6c17c5..ed5adf8 100644 --- a/src/winsock.c +++ b/src/winsock.c @@ -2668,32 +2668,36 @@ int WSAAPI select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds fd_set force_read_fds; FD_ZERO(&force_read_fds); - for(unsigned int i = 0; i < readfds->fd_count; ++i) + if(readfds != NULL) { - int fd = readfds->fd_array[i]; - - ipx_socket *sockptr = get_socket(fd); - if(sockptr != NULL) + for(unsigned int i = 0; i < readfds->fd_count; ++i) { - if(sockptr->flags & IPX_IS_SPX) - { - unlock_sockets(); - continue; - } + int fd = readfds->fd_array[i]; - if(sockptr->recv_queue->n_ready > 0) + ipx_socket *sockptr = get_socket(fd); + if(sockptr != NULL) { - /* There is data in the receive queue for this socket, but the - * underlying socket isn't necessarily readable, so we reduce the - * select() timeout to zero to ensure it returns immediately and - * inject this fd back into readfds at the end if necessary. - */ + if(sockptr->flags & IPX_IS_SPX) + { + unlock_sockets(); + continue; + } - FD_SET(fd, &force_read_fds); - use_timeout = &TIMEOUT_IMMEDIATE; + if(sockptr->recv_queue->n_ready > 0) + { + /* There is data in the receive queue for this socket, but + * the underlying socket isn't necessarily readable, so we + * reduce the select() timeout to zero to ensure it returns + * immediately and inject this fd back into readfds at the + * end if necessary. + */ + + FD_SET(fd, &force_read_fds); + use_timeout = &TIMEOUT_IMMEDIATE; + } + + unlock_sockets(); } - - unlock_sockets(); } } diff --git a/src/wsock32_stubs.txt b/src/wsock32_stubs.txt index 75450c4..6131143 100644 --- a/src/wsock32_stubs.txt +++ b/src/wsock32_stubs.txt @@ -15,7 +15,7 @@ ntohl ntohs recv:0 recvfrom:0 -select +select:0 send:0 sendto:0 setsockopt:0 diff --git a/tests/25-fionread.t b/tests/25-fionread.t new file mode 100644 index 0000000..0563dfc --- /dev/null +++ b/tests/25-fionread.t @@ -0,0 +1,34 @@ +# IPXWrapper test suite +# Copyright (C) 2023 Daniel Collins +# +# This program is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License version 2 as published by +# the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +# more details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +use strict; +use warnings; + +use FindBin; +use lib "$FindBin::Bin/lib/"; + +use IPXWrapper::Util; + +require "$FindBin::Bin/config.pm"; +our ($remote_mac_a, $remote_ip_a); + +reg_delete_key($remote_ip_a, "HKCU\\Software\\IPXWrapper"); + +# Unit tests implemented by fionread.exe, so run it on the test system and pass +# the (TAP) output/exit status to our parent. + +system("ssh", $remote_ip_a, "Z:\\tools\\fionread.exe", "00:00:00:01", $remote_mac_a); +exit($? >> 8); diff --git a/tests/fionread.c b/tests/fionread.c new file mode 100644 index 0000000..e5012a9 --- /dev/null +++ b/tests/fionread.c @@ -0,0 +1,134 @@ +/* IPXWrapper test suite + * Copyright (C) 2023 Daniel Collins + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., 51 + * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include +#include + +#include +#include +#include + +#include "tap/basic.h" +#include "../tools/tools.h" + +static char buf[4096]; + +int main(int argc, char **argv) +{ + if(argc != 3) + { + fprintf(stderr, "Usage: %s \n", argv[0]); + return 1; + } + + plan_lazy(); + + struct sockaddr_ipx addr1 = read_sockaddr(argv[1], argv[2], "1234"); + struct sockaddr_ipx addr2 = read_sockaddr(argv[1], argv[2], "1235"); + + WSADATA data; + WSAStartup(MAKEWORD(1, 1), &data); + + int sock1 = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); + assert(sock1 != SOCKET_ERROR); + + assert(bind(sock1, (struct sockaddr*)(&addr1), sizeof(addr1)) == 0); + + int sock2 = socket(AF_IPX, SOCK_DGRAM, NSPROTO_IPX); + assert(sock2 != SOCKET_ERROR); + + assert(bind(sock2, (struct sockaddr*)(&addr2), sizeof(addr2)) == 0); + + fd_set readfds; + struct timeval timeout = { 0, 0 }; + + unsigned long x = 123456789; + int r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when no packets are waiting"); + is_int(0, x, "ioctlsocket(FIONREAD) returns zero bytes when no packets are waiting"); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 0, "select() initially indicates socket is not ready to read"); + ok(!FD_ISSET(sock1, &readfds), "select() initially indicates socket is not ready to read"); + + assert(sendto(sock2, buf, 128, 0, (struct sockaddr*)(&addr1), sizeof(addr1)) == 128); + + /* Just in case there's any async going on */ + Sleep(100); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 1, "select() indicates socket is ready to read before call to ioctlsocket(FIONREAD)"); + ok(FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read before call to ioctlsocket(FIONREAD)"); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when packets are waiting"); + is_int(128, x, "ioctlsocket(FIONREAD) returns payload size when one packet is waiting"); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 1, "select() indicates socket is ready to read after call to ioctlsocket(FIONREAD)"); + ok(FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read after call to ioctlsocket(FIONREAD)"); + + assert(sendto(sock2, buf, 256, 0, (struct sockaddr*)(&addr1), sizeof(addr1)) == 256); + + /* Just in case there's any async going on */ + Sleep(100); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when packets are waiting"); + is_int(384, x, "ioctlsocket(FIONREAD) returns combined payload sizes when multiple packets are waiting"); + + assert(recv(sock1, buf, 4096, 0) == 128); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 1, "select() indicates socket is ready to read after reading first packet"); + ok(FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read after reading first packet"); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when packets are waiting"); + is_int(256, x, "ioctlsocket(FIONREAD) returns payload sizes when one packet is waiting"); + + assert(recv(sock1, buf, 4096, 0) == 256); + + FD_ZERO(&readfds); + FD_SET(sock1, &readfds); + is_int(select(-1, &readfds, NULL, NULL, &timeout), 0, "select() indicates socket is not ready to read after reading second packet"); + ok(!FD_ISSET(sock1, &readfds), "select() indicates socket is ready to read after reading second packet"); + + x = 123456789; + r = ioctlsocket(sock1, FIONREAD, &x); + + is_int(0, r, "ioctlsocket(FIONREAD) succeeds when no packets are waiting"); + is_int(0, x, "ioctlsocket(FIONREAD) returns zero bytes when no packets are waiting"); + + closesocket(sock2); + closesocket(sock1); + + WSACleanup(); + + return 0; +} diff --git a/tools/tools.h b/tools/tools.h index 021b0b3..c455854 100644 --- a/tools/tools.h +++ b/tools/tools.h @@ -8,7 +8,7 @@ #include #include -#include "addr.h" +#include "../src/addr.h" static struct sockaddr_ipx read_sockaddr(const char *net_s, const char *node_s, const char *socket_s) { From 0a2bc5f629d5ef567e43ac10f2adde05b8fce0e8 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Wed, 18 Oct 2023 21:30:11 +0100 Subject: [PATCH 3/3] Updated readme, changelog, manifest. --- changes.txt | 7 +++++++ manifest.src.txt | 24 ++++++++++++++++++++++++ readme.txt | 1 + 3 files changed, 32 insertions(+) diff --git a/changes.txt b/changes.txt index c9b2c65..65c4b44 100644 --- a/changes.txt +++ b/changes.txt @@ -1,3 +1,10 @@ +Version TBA: + Added support for DOSBox IPX servers. + + Fixed implementation of FIONREAD ioctl (needed for Laser Arena). + + Fixed a memory leak. + Version 0.6.1: Added support for LLC and Novell "raw" 802.3 Ethernet frame formats. diff --git a/manifest.src.txt b/manifest.src.txt index 038d034..3f1832f 100644 --- a/manifest.src.txt +++ b/manifest.src.txt @@ -24,6 +24,8 @@ src/dpwsockx_stubs.txt src/firewall.c src/interface.c src/interface.h +src/interface2.c +src/interface2.h src/ipxwrapper.c src/ipxwrapper.def src/ipxwrapper.h @@ -63,6 +65,8 @@ tests/07-ethernet.t tests/10-socket.t tests/15-interfaces.t tests/20-bind.t +tests/25-fionread.t +tests/30-dosbox-ipx.t tests/30-eth-ipx.t tests/30-ip-ipx.t tests/40-ip-spx.t @@ -71,12 +75,15 @@ tests/addr.c tests/addrcache.c tests/config.pm tests/ethernet.c +tests/fionread.c tests/ptype.pm tests/lib/IPXWrapper/Capture/IPX.pm tests/lib/IPXWrapper/Capture/IPXLLC.pm tests/lib/IPXWrapper/Capture/IPXNovell.pm tests/lib/IPXWrapper/Capture/IPXOverUDP.pm +tests/lib/IPXWrapper/DOSBoxClient.pm +tests/lib/IPXWrapper/DOSBoxServer.pm tests/lib/IPXWrapper/SPX.pm tests/lib/IPXWrapper/Tool/Bind.pm tests/lib/IPXWrapper/Tool/DPTool.pm @@ -101,3 +108,20 @@ tools/socket.c tools/spx-client.c tools/spx-server.c tools/tools.h + +winpcap/include/pcap-bpf.h +winpcap/include/Packet32.h +winpcap/include/pcap/vlan.h +winpcap/include/pcap/sll.h +winpcap/include/pcap/bluetooth.h +winpcap/include/pcap/bpf.h +winpcap/include/pcap/usb.h +winpcap/include/pcap/namedb.h +winpcap/include/pcap/pcap.h +winpcap/include/pcap-namedb.h +winpcap/include/pcap-stdinc.h +winpcap/include/Win32-Extensions.h +winpcap/include/bittypes.h +winpcap/include/ip6_misc.h +winpcap/include/remote-ext.h +winpcap/include/pcap.h diff --git a/readme.txt b/readme.txt index c03ed9a..0d32259 100644 --- a/readme.txt +++ b/readme.txt @@ -62,6 +62,7 @@ The following have been reported to work: * Delta Force 2 * Diablo * Heroes of Might and Magic III + * Laser Arena * Need For Speed III - Hot Pursuit * Outlive * Rising Lands