From e9c45b8ac58933a2bf2fcb2db5a6a25654301ef5 Mon Sep 17 00:00:00 2001 From: Daniel Collins Date: Wed, 18 Oct 2023 20:24:05 +0100 Subject: [PATCH] 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) {