Skip to content

Commit 4758107

Browse files
authored
Test UDP sockets (#638)
Test the following scenarios: * Non-blocking sockets with single connection * Non-blocking sockets with single client, connectionless * Non-blocking sockets with multiple clients * Blocking sockets with multiple clients, separate client and server process
1 parent 1590774 commit 4758107

File tree

7 files changed

+581
-1
lines changed

7 files changed

+581
-1
lines changed

test/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ $(SYSROOT):
4545
LIBC_TEST_URL ?= https://github.com/bytecodealliance/libc-test
4646
LIBC_TEST = $(DOWNDIR)/libc-test
4747
ARCH := $(shell uname -m)
48-
WASMTIME_URL ?= https://github.com/bytecodealliance/wasmtime/releases/download/v29.0.1/wasmtime-v29.0.1-$(ARCH)-linux.tar.xz
48+
WASMTIME_URL ?= https://github.com/bytecodealliance/wasmtime/releases/download/v38.0.2/wasmtime-v38.0.2-$(ARCH)-linux.tar.xz
4949
WASMTIME = $(abspath $(DOWNDIR)/$(shell basename $(WASMTIME_URL) .tar.xz)/wasmtime)
5050
WASM_TOOLS_URL ?= https://github.com/bytecodealliance/wasm-tools/releases/download/v1.224.0/wasm-tools-1.224.0-$(ARCH)-linux.tar.gz
5151
WASM_TOOLS = $(DOWNDIR)/$(shell basename $(WASM_TOOLS_URL) .tar.gz)/wasm-tools

test/scripts/run-test.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,17 @@ fi
8484
if [ $testname == "sockets-multiple-server.component.wasm" ]; then
8585
exit 0
8686
fi
87+
if [ $testname == "sockets-server-udp-blocking.component.wasm" ]; then
88+
exit 0
89+
fi
8790
if [ $testname == "sockets-client.component.wasm" ]; then
8891
run_sockets_test $parent/"sockets-server.component.wasm"
8992
exit $?
9093
fi
94+
if [ $testname == "sockets-client-udp-blocking.component.wasm" ]; then
95+
run_sockets_test $parent/"sockets-server-udp-blocking.component.wasm"
96+
exit $?
97+
fi
9198
if [ $testname == "sockets-multiple-client.component.wasm" ]; then
9299
run_sockets_test_multiple $parent/"sockets-multiple-server.component.wasm"
93100
exit $?
@@ -131,6 +138,10 @@ if [ $testname == "sockets-client-handle-hangups.component.wasm" ]; then
131138
# Deliberately fall through so that we can run this test without a server
132139
# (to test the behavior when connect() fails)
133140
fi
141+
# This test fails nondeterministically
142+
if [ $testname == "sockets-nonblocking-udp-multiple.c" ]; then
143+
exit 0
144+
fi
134145
cd $DIR
135146
mkdir -p fs
136147
echo "$ENV $ENGINE $RUN $WASM $ARGS" > cmd.sh
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//! filter.py(TARGET_TRIPLE): wasm32-wasip2
2+
//! add-flags.py(RUN): --wasi=inherit-network=y
3+
#include <errno.h>
4+
#include <fcntl.h>
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <unistd.h>
9+
#include <netdb.h>
10+
#include <sys/socket.h>
11+
#include <netinet/in.h>
12+
#include <arpa/inet.h>
13+
#include "test.h"
14+
15+
#define TEST(c) do { \
16+
errno = 0; \
17+
if (!(c)) \
18+
t_error("%s failed (errno = %d)\n", #c, errno); \
19+
} while(0)
20+
21+
int BUFSIZE = 256;
22+
23+
// See sockets-server-udp-blocking.c -- must be running already as a separate executable
24+
void test_udp_client() {
25+
// Prepare server socket
26+
int server_port = 4001;
27+
28+
// Prepare client socket
29+
// Use blocking sockets
30+
int socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
31+
TEST(socket_fd != -1);
32+
33+
// Prepare sockaddr_in for client
34+
struct sockaddr_in sockaddr_in;
35+
sockaddr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
36+
sockaddr_in.sin_family = AF_INET;
37+
sockaddr_in.sin_port = htons(server_port);
38+
39+
// Connect from client
40+
char message[] = "There's gonna be a party when the wolf comes home";
41+
int len = strlen(message);
42+
char client_buffer[BUFSIZE];
43+
44+
TEST(connect(socket_fd, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) != -1);
45+
46+
// Client writes a message to server
47+
TEST(send(socket_fd, message, len, 0) == len);
48+
49+
// Client reads from server
50+
int32_t bytes_received = recv(socket_fd, client_buffer, len, 0);
51+
TEST(bytes_received == len);
52+
53+
// Message received should be the same as message sent
54+
TEST(strcmp(message, client_buffer) == 0);
55+
56+
// Shut down client
57+
close(socket_fd);
58+
}
59+
60+
int main(void)
61+
{
62+
test_udp_client();
63+
64+
return t_status;
65+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
//! filter.py(TARGET_TRIPLE): wasm32-wasip2
2+
//! add-flags.py(RUN): --wasi=inherit-network=y
3+
#include <errno.h>
4+
#include <fcntl.h>
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <stdbool.h>
8+
#include <string.h>
9+
#include <unistd.h>
10+
#include <netdb.h>
11+
#include <sys/socket.h>
12+
#include <netinet/in.h>
13+
#include <arpa/inet.h>
14+
#include <poll.h>
15+
#include "test.h"
16+
17+
#define TEST(c) do { \
18+
errno = 0; \
19+
if (!(c)) \
20+
t_error("%s failed (errno = %d)\n", #c, errno); \
21+
} while(0)
22+
23+
// #define _DEBUG 1
24+
25+
#ifdef _DEBUG
26+
#define DEBUG_PRINT(...) fprintf( stderr, __VA_ARGS__ ); fflush(stdout)
27+
#else
28+
#define DEBUG_PRINT(...) do{ } while ( 0 )
29+
#endif
30+
31+
#define BUFSIZE 256
32+
int MAX_CONNECTIONS = 10;
33+
34+
struct response {
35+
bool responded;
36+
char message[BUFSIZE];
37+
int32_t client_index;
38+
};
39+
40+
struct request {
41+
bool sent;
42+
char message[BUFSIZE];
43+
};
44+
45+
// We are done if client_incoming == client_outgoing
46+
bool done(struct request out[MAX_CONNECTIONS], char in[MAX_CONNECTIONS][BUFSIZE]) {
47+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
48+
if (out[i].message[0] == 0) {
49+
return false;
50+
}
51+
if (strcmp(out[i].message, in[i]) != 0) {
52+
DEBUG_PRINT("sent[%zu] = %s != received[%zu] = %s\n", i, out[i].message, i, in[i]);
53+
return false;
54+
}
55+
DEBUG_PRINT("i = %zu %s matches %s\n", i, out[i].message, in[i]);
56+
}
57+
return true;
58+
}
59+
60+
void test_udp_client() {
61+
// Prepare server socket
62+
int server_port = 4001;
63+
// Use non-blocking sockets
64+
int server_socket_fd = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
65+
66+
// Bind server to socket
67+
struct sockaddr_in server_address;
68+
server_address.sin_family = AF_INET;
69+
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
70+
server_address.sin_port = htons(server_port);
71+
TEST(bind(server_socket_fd, (struct sockaddr*)&server_address, sizeof(server_address)) != -1);
72+
73+
// Prepare client sockets
74+
// Use non-blocking sockets
75+
int client_sockets[MAX_CONNECTIONS];
76+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
77+
client_sockets[i] = socket(AF_INET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
78+
TEST(client_sockets[i] != -1);
79+
}
80+
81+
// Prepare sockaddr_in for client
82+
struct sockaddr_in sockaddr_in;
83+
sockaddr_in.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
84+
sockaddr_in.sin_family = AF_INET;
85+
sockaddr_in.sin_port = htons(server_port);
86+
87+
// Prepare requests from clients
88+
struct request client_outgoing[MAX_CONNECTIONS];
89+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
90+
int32_t number = 0;
91+
getentropy(&number, sizeof(int32_t));
92+
sprintf(client_outgoing[i].message, "%d", number);
93+
client_outgoing[i].sent = false;
94+
}
95+
char client_incoming[MAX_CONNECTIONS][BUFSIZE];
96+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
97+
client_incoming[i][0] = 0;
98+
}
99+
struct response server_incoming[MAX_CONNECTIONS];
100+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
101+
server_incoming[i].responded = false;
102+
server_incoming[i].message[0] = 0;
103+
}
104+
struct pollfd client_pollfds[MAX_CONNECTIONS];
105+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
106+
client_pollfds[i].fd = client_sockets[i];
107+
client_pollfds[i].events = POLLRDNORM;
108+
client_pollfds[i].revents = 0;
109+
}
110+
111+
struct sockaddr_in addresses_of_clients[MAX_CONNECTIONS];
112+
socklen_t address_len = sizeof(sockaddr_in);
113+
114+
struct pollfd server_pollfd = { .fd = server_socket_fd, .events = POLLRDNORM, .revents = 0 };
115+
struct pollfd server_client_pollfds[MAX_CONNECTIONS];
116+
size_t next_client = 0;
117+
size_t server_client_index = 0;
118+
int32_t tries = 0;
119+
while (!done(client_outgoing, client_incoming) && (tries < (MAX_CONNECTIONS * 2))) {
120+
DEBUG_PRINT("server polling for new messages\n");
121+
122+
// Server polls for new messages
123+
server_pollfd.events = POLLRDNORM;
124+
poll(&server_pollfd, 1, 100);
125+
if ((server_pollfd.revents & POLLRDNORM) != 0) {
126+
int bytes_received = recvfrom(server_socket_fd, server_incoming[server_client_index].message,
127+
BUFSIZE, 0, (struct sockaddr*)&(addresses_of_clients[server_client_index]), &address_len);
128+
if (bytes_received > 0) {
129+
server_incoming[server_client_index].client_index = server_client_index;
130+
server_client_index++;
131+
} else
132+
DEBUG_PRINT("Shouldn't happen: poll(server_pollfd) said there is data but recvfrom() failed to return it\n");
133+
}
134+
135+
DEBUG_PRINT("next client sending\n");
136+
// Next client sends
137+
if (next_client < MAX_CONNECTIONS) {
138+
struct pollfd client_pollfd = { .fd = client_sockets[next_client], .events = POLLWRNORM, .revents = 0 };
139+
poll(&client_pollfd, 1, 100);
140+
if ((client_pollfd.events & POLLWRNORM) != 0) {
141+
int32_t bytes_sent = sendto(client_sockets[next_client], client_outgoing[next_client].message,
142+
strlen(client_outgoing[next_client].message) + 1, 0, (struct sockaddr*)&sockaddr_in, address_len);
143+
if (bytes_sent == -1) {
144+
DEBUG_PRINT("client %zu failed to send, error: %s\n", next_client, strerror(errno));
145+
TEST(errno==EWOULDBLOCK);
146+
// Will retry on next iteration of loop
147+
} else {
148+
DEBUG_PRINT("client %zu sent %d bytes\n", next_client, bytes_sent);
149+
client_outgoing[next_client].sent = true;
150+
if (next_client < MAX_CONNECTIONS)
151+
next_client++;
152+
}
153+
}
154+
}
155+
156+
DEBUG_PRINT("server echoing\n");
157+
// Echo messages back to clients
158+
server_pollfd.events = POLLWRNORM;
159+
poll(&server_pollfd, 1, 100);
160+
for (size_t i = 0; i < server_client_index; i++) {
161+
if (server_incoming[i].message[0] != 0 && !server_incoming[i].responded) {
162+
ssize_t bytes_sent = sendto(server_socket_fd, server_incoming[i].message, BUFSIZE, 0, (struct sockaddr*)&addresses_of_clients[i], address_len);
163+
if (bytes_sent == -1) {
164+
int the_error = errno;
165+
TEST(the_error==EWOULDBLOCK);
166+
// Will retry on next iteration of loop
167+
} else {
168+
DEBUG_PRINT("server echoed message %s back to client %zu\n", server_incoming[i].message, i);
169+
server_incoming[i].responded = true;
170+
}
171+
break;
172+
}
173+
}
174+
175+
DEBUG_PRINT("clients receiving\n");
176+
// Clients receive messages
177+
poll(client_pollfds, MAX_CONNECTIONS, 100);
178+
DEBUG_PRINT("======= client_pollfds: =========\n");
179+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
180+
DEBUG_PRINT("{ .fd = %d .events = %d .revents = %d }\n", client_pollfds[i].fd, client_pollfds[i].events, client_pollfds[i].revents);
181+
}
182+
DEBUG_PRINT("=================================\n");
183+
for (size_t i = 0; i < MAX_CONNECTIONS; i++) {
184+
// Note: poll() will return revents == events for an unbound UDP socket;
185+
// so that's why we check if we already sent on this socket, and do
186+
// nothing if we haven't yet
187+
if ((client_pollfds[i].revents & POLLRDNORM) != 0) {
188+
DEBUG_PRINT("i = %zu, already received (%s), already sent? %s\n", i, client_incoming[i], client_outgoing[i].sent ? "yes" : "no");
189+
if (client_incoming[i][0] == 0 && client_outgoing[i].sent) {
190+
int32_t bytes_received = recvfrom(client_pollfds[i].fd, &client_incoming[i], BUFSIZE, 0, (struct sockaddr*)&server_address, &address_len);
191+
if (bytes_received != -1) {
192+
DEBUG_PRINT("client %zu received message %s (%d bytes) from server\n", i, client_incoming[i], bytes_received);
193+
} else {
194+
DEBUG_PRINT("Shouldn't happen: poll() said there was data, but client %zu did not receive message from server: %s\n", i, strerror(errno));
195+
}
196+
}
197+
}
198+
}
199+
tries++;
200+
}
201+
202+
DEBUG_PRINT("========= exited loop ==========\n");
203+
204+
TEST(done(client_outgoing, client_incoming));
205+
206+
// Shut down clients
207+
for (size_t i = 0; i < MAX_CONNECTIONS; i++)
208+
close(client_sockets[i]);
209+
210+
// Shut down server
211+
close(server_socket_fd);
212+
}
213+
214+
int main(void)
215+
{
216+
test_udp_client();
217+
218+
return t_status;
219+
}

0 commit comments

Comments
 (0)