Skip to content

Commit 7df48f6

Browse files
authored
Fix TCP socket send() immediately back to back after connect() (#22630)
Previously, if an emulated TCP socket is connect()ed and one attempts to send() back to back right after connecting, the send would fail because the socket is not yet actually connected. It is a bit hard to imagine where such behavior would be useful. So this PR changes this behavior to be identical to how connectionless UDP sockets are emulated: instead, connect() calls are always pretended to succeed (since we cannot synchronously establish if the connect would fail, so presume it'll work), and all send() calls that are issued while the socket is connecting will be queued to be sent after the socket actually connects.
1 parent 649e86a commit 7df48f6

File tree

3 files changed

+85
-17
lines changed

3 files changed

+85
-17
lines changed

src/library_sockfs.js

+20-17
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ addToLibrary({
241241
addr,
242242
port,
243243
socket: ws,
244-
dgram_send_queue: []
244+
msg_send_queue: []
245245
};
246246

247247
SOCKFS.websocket_sock_ops.addPeer(sock, peer);
@@ -254,7 +254,7 @@ addToLibrary({
254254
#if SOCKET_DEBUG
255255
dbg('websocket: queuing port message (port ' + sock.sport + ')');
256256
#endif
257-
peer.dgram_send_queue.push(new Uint8Array([
257+
peer.msg_send_queue.push(new Uint8Array([
258258
255, 255, 255, 255,
259259
'p'.charCodeAt(0), 'o'.charCodeAt(0), 'r'.charCodeAt(0), 't'.charCodeAt(0),
260260
((sock.sport & 0xff00) >> 8) , (sock.sport & 0xff)
@@ -283,13 +283,13 @@ addToLibrary({
283283
Module['websocket'].emit('open', sock.stream.fd);
284284

285285
try {
286-
var queued = peer.dgram_send_queue.shift();
286+
var queued = peer.msg_send_queue.shift();
287287
while (queued) {
288288
#if SOCKET_DEBUG
289289
dbg('websocket: sending queued data (' + queued.byteLength + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(queued))]);
290290
#endif
291291
peer.socket.send(queued);
292-
queued = peer.dgram_send_queue.shift();
292+
queued = peer.msg_send_queue.shift();
293293
}
294294
} catch (e) {
295295
// not much we can do here in the way of proper error handling as we've already
@@ -493,8 +493,9 @@ addToLibrary({
493493
sock.daddr = peer.addr;
494494
sock.dport = peer.port;
495495

496-
// always "fail" in non-blocking mode
497-
throw new FS.ErrnoError({{{ cDefs.EINPROGRESS }}});
496+
// because we cannot synchronously block to wait for the WebSocket
497+
// connection to complete, we return here pretending that the connection
498+
// was a success.
498499
},
499500
listen(sock, backlog) {
500501
if (!ENVIRONMENT_IS_NODE) {
@@ -605,8 +606,10 @@ addToLibrary({
605606
if (sock.type === {{{ cDefs.SOCK_STREAM }}}) {
606607
if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
607608
throw new FS.ErrnoError({{{ cDefs.ENOTCONN }}});
609+
#if SOCKET_DEBUG
608610
} else if (dest.socket.readyState === dest.socket.CONNECTING) {
609-
throw new FS.ErrnoError({{{ cDefs.EAGAIN }}});
611+
dbg('socket sendmsg called while socket is still connecting.');
612+
#endif
610613
}
611614
}
612615

@@ -631,21 +634,21 @@ addToLibrary({
631634
}
632635
#endif
633636

634-
// if we're emulating a connection-less dgram socket and don't have
635-
// a cached connection, queue the buffer to send upon connect and
636-
// lie, saying the data was sent now.
637-
if (sock.type === {{{ cDefs.SOCK_DGRAM }}}) {
638-
if (!dest || dest.socket.readyState !== dest.socket.OPEN) {
639-
// if we're not connected, open a new connection
637+
// if we don't have a cached connectionless UDP datagram connection, or
638+
// the TCP socket is still connecting, queue the message to be sent upon
639+
// connect, and lie, saying the data was sent now.
640+
if (!dest || dest.socket.readyState !== dest.socket.OPEN) {
641+
// if we're not connected, open a new connection
642+
if (sock.type === {{{ cDefs.SOCK_DGRAM }}}) {
640643
if (!dest || dest.socket.readyState === dest.socket.CLOSING || dest.socket.readyState === dest.socket.CLOSED) {
641644
dest = SOCKFS.websocket_sock_ops.createPeer(sock, addr, port);
642645
}
646+
}
643647
#if SOCKET_DEBUG
644-
dbg('websocket: queuing (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]);
648+
dbg('websocket: queuing (' + length + ' bytes): ' + [Array.prototype.slice.call(new Uint8Array(data))]);
645649
#endif
646-
dest.dgram_send_queue.push(data);
647-
return length;
648-
}
650+
dest.msg_send_queue.push(data);
651+
return length;
649652
}
650653

651654
try {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// This test verifies that calling send() back to back right after calling
2+
// connect() succeeds.
3+
4+
#include <sys/socket.h>
5+
#include <netinet/in.h>
6+
#include <arpa/inet.h>
7+
#include <emscripten/html5.h>
8+
#include <string.h>
9+
#include <stdio.h>
10+
11+
int sock;
12+
13+
EM_BOOL wait_for_recv(double d, void *u) {
14+
// Poll read from the socket to see what we have received
15+
char buf[1024] = {};
16+
recv(sock, buf, sizeof(buf)-1, 0);
17+
if (strlen(buf) > 0) {
18+
printf("%s\n", buf);
19+
if (!strcmp(buf, "Hello")) {
20+
printf("Got hello, test finished.\n");
21+
#ifdef REPORT_RESULT
22+
REPORT_RESULT(0);
23+
#endif
24+
}
25+
}
26+
return EM_TRUE;
27+
}
28+
29+
int main() {
30+
// Connect socket to a WebSocket echo server
31+
struct sockaddr_in addr = {
32+
.sin_family = AF_INET,
33+
.sin_port = htons(8089)
34+
};
35+
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
36+
sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
37+
if (sock < 0) {
38+
printf("socket() failed to error %d\n", sock);
39+
return sock;
40+
}
41+
42+
// Connect to echo server.
43+
int error = connect(sock, (struct sockaddr*)&addr, sizeof(addr));
44+
if (error) {
45+
printf("connect() failed to error %d\n", error);
46+
return error;
47+
}
48+
49+
// Immediately send a message back-to-back from connecting to the socket
50+
const char *msg = "Hello";
51+
ssize_t bytes = send(sock, msg, strlen(msg), 0);
52+
if (bytes != strlen(msg)) {
53+
printf("send() failed to send %d bytes. Return value: %d\n", (int)strlen(msg), (int)bytes);
54+
return bytes;
55+
}
56+
57+
// Asynchronously wait until we get the message echoed back
58+
emscripten_set_timeout_loop(wait_for_recv, 0, 0);
59+
return 0;
60+
}

test/test_sockets.py

+5
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,11 @@ def test_posix_proxy_sockets(self):
350350
# Build and run the TCP echo client program with Emscripten
351351
self.btest_exit('websocket/tcp_echo_client.c', args=['-lwebsocket', '-sPROXY_POSIX_SOCKETS', '-pthread', '-sPROXY_TO_PTHREAD'])
352352

353+
# Test that calling send() right after a socket connect() works.
354+
def test_sockets_send_while_connecting(self):
355+
with NodeJsWebSocketEchoServerProcess():
356+
self.btest('sockets/test_sockets_send_while_connecting.c', args=['-DSOCKET_DEBUG'], expected='0')
357+
353358

354359
class sockets64(sockets):
355360
def setUp(self):

0 commit comments

Comments
 (0)