From bd866de60156ea2dd06aea64b5c9a5da44f8c7a1 Mon Sep 17 00:00:00 2001 From: Sergii Pylypenko Date: Sun, 18 Jul 2021 02:20:11 +0300 Subject: [PATCH] Emscripten: socket proxy first draft --- CMakeLists.txt | 3 + src/network/core/CMakeLists.txt | 2 + src/network/core/core.cpp | 3 + src/network/core/em_proxy.cpp | 933 ++++++++++++++++++++++++++++ src/network/core/em_proxy.h | 56 ++ src/network/core/os_abstraction.cpp | 4 - src/network/core/os_abstraction.h | 4 + 7 files changed, 1001 insertions(+), 4 deletions(-) create mode 100644 src/network/core/em_proxy.cpp create mode 100644 src/network/core/em_proxy.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b3c00c8ae7..2b2f106805 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,6 +350,9 @@ if(EMSCRIPTEN) # We use IDBFS for persistent storage. target_link_libraries(WASM::WASM INTERFACE "-lidbfs.js") + # Socket proxy + target_link_libraries(WASM::WASM INTERFACE "-lwebsocket.js") + # Use custom pre-js and shell.html. target_link_libraries(WASM::WASM INTERFACE "--pre-js ${CMAKE_SOURCE_DIR}/os/emscripten/pre.js") target_link_libraries(WASM::WASM INTERFACE "--shell-file ${CMAKE_SOURCE_DIR}/os/emscripten/shell.html") diff --git a/src/network/core/CMakeLists.txt b/src/network/core/CMakeLists.txt index bf713be99c..ea91e81959 100644 --- a/src/network/core/CMakeLists.txt +++ b/src/network/core/CMakeLists.txt @@ -4,6 +4,8 @@ add_files( config.h core.cpp core.h + em_proxy.cpp + em_proxy.h game_info.cpp game_info.h host.cpp diff --git a/src/network/core/core.cpp b/src/network/core/core.cpp index 563deae963..f83260e51a 100644 --- a/src/network/core/core.cpp +++ b/src/network/core/core.cpp @@ -34,6 +34,9 @@ bool NetworkCoreInitialize() } } #endif /* _WIN32 */ +#if defined(__EMSCRIPTEN__) + em_proxy_initialize_network(); +#endif return true; } diff --git a/src/network/core/em_proxy.cpp b/src/network/core/em_proxy.cpp new file mode 100644 index 0000000000..c676c6c8ed --- /dev/null +++ b/src/network/core/em_proxy.cpp @@ -0,0 +1,933 @@ +#if defined(__EMSCRIPTEN__) + +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** + * @file em_proxy.cpp Emscripten socket proxy, implementation copied from here: + * https://github.com/emscripten-core/emscripten/blob/main/system/lib/websocket/websocket_to_posix_socket.cpp + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) || defined(__linux__) +#include +#endif + +#define EM_PROXY_NO_MACROS 1 + +#include "../../stdafx.h" +#include "../../debug.h" +#include "os_abstraction.h" +#include "em_proxy.h" + + +// Uncomment to enable debug printing +// #define POSIX_SOCKET_DEBUG + +// Uncomment to enable more verbose debug printing (in addition to uncommenting POSIX_SOCKET_DEBUG) +// #define POSIX_SOCKET_DEEP_DEBUG + +#define MIN(a,b) (((a)<(b))?(a):(b)) + +static void *memdup(const void *ptr, size_t sz) +{ + if (!ptr) return 0; + void *dup = malloc(sz); + if (dup) memcpy(dup, ptr, sz); + return dup; +} + +// Each proxied socket call has at least the following data. +struct SocketCallHeader +{ + int callId; + int function; +}; + +// Each socket call returns at least the following data. +struct SocketCallResultHeader +{ + int callId; + int ret; + int errno_; + // Buffer can contain more data here, conceptually: + // uint8_t extraData[]; +}; + +struct PosixSocketCallResult +{ + PosixSocketCallResult *next; + int callId; + int operationCompleted; + + // Before the call has finished, this field represents the minimum expected number of bytes that server will need to report back. + // After the call has finished, this field reports back the number of bytes pointed to by data, >= the expected value. + int bytes; + + // Result data: + SocketCallResultHeader *data; +}; + +// Shield multithreaded accesses to POSIX sockets functions in the program, namely the two variables 'bridgeSocket' and 'callResultHead' below. +static pthread_mutex_t bridgeLock = PTHREAD_MUTEX_INITIALIZER; + +// Socket handle for the connection from browser WebSocket to the sockets bridge proxy server. +static EMSCRIPTEN_WEBSOCKET_T bridgeSocket = (EMSCRIPTEN_WEBSOCKET_T)0; + +// https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/readyState +enum { BRIDGE_CONNECTING = 0, BRIDGE_OPEN = 1, BRIDGE_CLOSING = 2, BRIDGE_CLOSED = 3 }; +static uint16_t bridgeState = BRIDGE_CONNECTING; + +// Stores a linked list of all currently pending sockets operations (ones that are waiting for a reply back from the sockets proxy server) +static PosixSocketCallResult *callResultHead = 0; + +static PosixSocketCallResult *allocate_call_result(int expectedBytes) +{ + pthread_mutex_lock(&bridgeLock); // Guard multithreaded access to 'callResultHead' and 'nextId' below + PosixSocketCallResult *b = (PosixSocketCallResult*)(malloc(sizeof(PosixSocketCallResult))); + if (!b) + { +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "allocate_call_result: Failed to allocate call result struct of size %d bytes!\n", (int)sizeof(PosixSocketCallResult)); +#endif + pthread_mutex_unlock(&bridgeLock); + return 0; + } + static int nextId = 1; + b->callId = nextId++; +#ifdef POSIX_SOCKET_DEEP_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "allocate_call_result: allocated call ID %d\n", b->callId); +#endif + b->bytes = expectedBytes; + b->data = 0; + b->operationCompleted = 0; + b->next = 0; + + if (!callResultHead) + callResultHead = b; + else + { + PosixSocketCallResult *t = callResultHead; + while(t->next) t = t->next; + t->next = b; + } + pthread_mutex_unlock(&bridgeLock); + return b; +} + +static void free_call_result(PosixSocketCallResult *buffer) +{ +#ifdef POSIX_SOCKET_DEEP_DEBUG + if (buffer) + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "free_call_result: freed call ID %d\n", buffer->callId); +#endif + + if (buffer->data) free(buffer->data); + free(buffer); +} + +PosixSocketCallResult *pop_call_result(int callId) +{ + pthread_mutex_lock(&bridgeLock); // Guard multithreaded access to 'callResultHead' + PosixSocketCallResult *prev = 0; + PosixSocketCallResult *b = callResultHead; + while(b) + { + if (b->callId == callId) + { + if (prev) prev->next = b->next; + else callResultHead = b->next; + b->next = 0; +#ifdef POSIX_SOCKET_DEEP_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "pop_call_result: Removed call ID %d from pending sockets call queue\n", callId); +#endif + pthread_mutex_unlock(&bridgeLock); + return b; + } + prev = b; + b = b->next; + } + pthread_mutex_unlock(&bridgeLock); +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "pop_call_result: No such call ID %d in pending sockets call queue!\n", callId); +#endif + return 0; +} + +void wait_for_call_result(PosixSocketCallResult *b) +{ +#ifdef POSIX_SOCKET_DEEP_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "wait_for_call_result: Waiting for call ID %d\n", b->callId); +#endif + while(!emscripten_atomic_load_u32(&b->operationCompleted)) + emscripten_futex_wait(&b->operationCompleted, 0, 1e9); +#ifdef POSIX_SOCKET_DEEP_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "wait_for_call_result: Waiting for call ID %d done\n", b->callId); +#endif +} + +static EM_BOOL bridge_socket_on_message(int eventType, const EmscriptenWebSocketMessageEvent *websocketEvent, void *userData) +{ + if (websocketEvent->numBytes < sizeof(SocketCallResultHeader)) + { + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Received corrupt WebSocket result message with size %d, not enough space for header, at least %d bytes!\n", (int)websocketEvent->numBytes, (int)sizeof(SocketCallResultHeader)); + return EM_TRUE; + } + + SocketCallResultHeader *header = (SocketCallResultHeader *)websocketEvent->data; + +#ifdef POSIX_SOCKET_DEEP_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "POSIX sockets bridge received message on thread %p, size: %d bytes, for call ID %d\n", (void*)pthread_self(), websocketEvent->numBytes, header->callId); +#endif + + PosixSocketCallResult *b = pop_call_result(header->callId); + if (!b) + { + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Received WebSocket result message to unknown call ID %d!\n", (int)header->callId); + // TODO: Craft a socket result that signifies a failure, and wake the listening thread + return EM_TRUE; + } + + if ((int)websocketEvent->numBytes < b->bytes) + { + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Received corrupt WebSocket result message with size %d, expected at least %d bytes!\n", (int)websocketEvent->numBytes, b->bytes); + // TODO: Craft a socket result that signifies a failure, and wake the listening thread + return EM_TRUE; + } + + b->bytes = websocketEvent->numBytes; + b->data = (SocketCallResultHeader*)memdup(websocketEvent->data, websocketEvent->numBytes); + + if (!b->data) + { + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Out of memory, tried to allocate %d bytes!\n", websocketEvent->numBytes); + return EM_TRUE; + } + + if (b->operationCompleted != 0) + { + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "Memory corruption(?): the received result for completed operation at address %p was expected to be in state 0, but it was at state %d!\n", &b->operationCompleted, (int)b->operationCompleted); + } + + emscripten_atomic_store_u32(&b->operationCompleted, 1); + emscripten_futex_wake(&b->operationCompleted, 0x7FFFFFFF); + + return EM_TRUE; +} + +EMSCRIPTEN_WEBSOCKET_T emscripten_init_websocket_to_posix_socket_bridge(const char *bridgeUrl) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_JS_STACK, "emscripten_init_websocket_to_posix_socket_bridge(bridgeUrl=\"%s\")\n", bridgeUrl); +#endif + pthread_mutex_lock(&bridgeLock); // Guard multithreaded access to 'bridgeSocket' + if (bridgeSocket) + { +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_WARN | EM_LOG_JS_STACK, "emscripten_init_websocket_to_posix_socket_bridge(bridgeUrl=\"%s\"): A previous bridge socket connection handle existed! Forcibly tearing old connection down.\n", bridgeUrl); +#endif + emscripten_websocket_close(bridgeSocket, 0, 0); + emscripten_websocket_delete(bridgeSocket); + bridgeSocket = 0; + } + EmscriptenWebSocketCreateAttributes attr; + emscripten_websocket_init_create_attributes(&attr); + attr.url = bridgeUrl; + bridgeSocket = emscripten_websocket_new(&attr); + emscripten_websocket_set_onmessage_callback_on_thread(bridgeSocket, 0, bridge_socket_on_message, EM_CALLBACK_THREAD_CONTEXT_MAIN_BROWSER_THREAD); + + pthread_mutex_unlock(&bridgeLock); + return bridgeSocket; +} + +int em_proxy_initialize_network(void) +{ + const char * proxy = "ws://localhost:3999"; + int bridge = emscripten_init_websocket_to_posix_socket_bridge(proxy); + DEBUG(net, 0, "[em_proxy] Opened proxy socket to %s: socket %d", proxy, bridge); + return (bridge > 0); +} + +#define POSIX_SOCKET_MSG_SOCKET 1 +#define POSIX_SOCKET_MSG_SOCKETPAIR 2 +#define POSIX_SOCKET_MSG_SHUTDOWN 3 +#define POSIX_SOCKET_MSG_BIND 4 +#define POSIX_SOCKET_MSG_CONNECT 5 +#define POSIX_SOCKET_MSG_LISTEN 6 +#define POSIX_SOCKET_MSG_ACCEPT 7 +#define POSIX_SOCKET_MSG_GETSOCKNAME 8 +#define POSIX_SOCKET_MSG_GETPEERNAME 9 +#define POSIX_SOCKET_MSG_SEND 10 +#define POSIX_SOCKET_MSG_RECV 11 +#define POSIX_SOCKET_MSG_SENDTO 12 +#define POSIX_SOCKET_MSG_RECVFROM 13 +#define POSIX_SOCKET_MSG_SENDMSG 14 +#define POSIX_SOCKET_MSG_RECVMSG 15 +#define POSIX_SOCKET_MSG_GETSOCKOPT 16 +#define POSIX_SOCKET_MSG_SETSOCKOPT 17 +#define POSIX_SOCKET_MSG_GETADDRINFO 18 +#define POSIX_SOCKET_MSG_GETNAMEINFO 19 + +#define MAX_SOCKADDR_SIZE 256 +#define MAX_OPTIONVALUE_SIZE 16 + +int em_proxy_socket(int domain, int type, int protocol) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "socket(domain=%d,type=%d,protocol=%d) on thread %p\n", domain, type, protocol, (void*)pthread_self()); +#endif + + if (bridgeState == BRIDGE_CONNECTING) { + if (bridgeSocket > 0) { + emscripten_websocket_get_ready_state(bridgeSocket, &bridgeState); + } + DEBUG(net, 0, "[em_proxy] Proxy socket state %d: %s", bridgeState, bridgeState == BRIDGE_OPEN ? "connected" : "proxy disabled"); + if (bridgeState == BRIDGE_CONNECTING) { + bridgeState = BRIDGE_CLOSED; + } + } + if (bridgeState != BRIDGE_OPEN) { + return socket(domain, type, protocol); + } + + struct { + SocketCallHeader header; + int domain; + int type; + int protocol; + } d; + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_SOCKET; + d.domain = domain; + d.type = type; + d.protocol = protocol; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret < 0) errno = b->data->errno_; + free_call_result(b); + return ret; +} + +int em_proxy_closesocket(int socket) +{ + if (bridgeState != BRIDGE_OPEN) { + return closesocket(socket); + } + + // TODO: closing UDP socket not implemented + return em_proxy_shutdown(socket, SHUT_RDWR); +} + +int em_proxy_shutdown(int socket, int how) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "shutdown(socket=%d,how=%d)\n", socket, how); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return shutdown(socket, how); + } + + struct { + SocketCallHeader header; + int socket; + int how; + } d; + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_SHUTDOWN; + d.socket = socket; + d.how = how; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret != 0) errno = b->data->errno_; + free_call_result(b); + return ret; +} + +int em_proxy_bind(int socket, const struct sockaddr *address, socklen_t address_len) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "bind(socket=%d,address=%p,address_len=%d)\n", socket, address, address_len); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return bind(socket, address, address_len); + } + + struct Data { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + uint8_t address[]; + }; + int numBytes = sizeof(Data) + address_len; + Data *d = (Data*)malloc(numBytes); + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d->header.callId = b->callId; + d->header.function = POSIX_SOCKET_MSG_BIND; + d->socket = socket; + d->address_len = address_len; + if (address) memcpy(d->address, address, address_len); + else memset(d->address, 0, address_len); + emscripten_websocket_send_binary(bridgeSocket, d, numBytes); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret != 0) errno = b->data->errno_; + free_call_result(b); + + free(d); + return ret; +} + +int em_proxy_connect(int socket, const struct sockaddr *address, socklen_t address_len) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "connect(socket=%d,address=%p,address_len=%d)\n", socket, address, address_len); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return connect(socket, address, address_len); + } + + struct Data { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + uint8_t address[]; + }; + int numBytes = sizeof(Data) + address_len; + Data *d = (Data*)malloc(numBytes); + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d->header.callId = b->callId; + d->header.function = POSIX_SOCKET_MSG_CONNECT; + d->socket = socket; + d->address_len = address_len; + if (address) memcpy(d->address, address, address_len); + else memset(d->address, 0, address_len); + emscripten_websocket_send_binary(bridgeSocket, d, numBytes); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret != 0) errno = b->data->errno_; + free_call_result(b); + + free(d); + return ret; +} + +int em_proxy_listen(int socket, int backlog) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "listen(socket=%d,backlog=%d)\n", socket, backlog); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return listen(socket, backlog); + } + + struct { + SocketCallHeader header; + int socket; + int backlog; + } d; + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_LISTEN; + d.socket = socket; + d.backlog = backlog; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret != 0) errno = b->data->errno_; + free_call_result(b); + return ret; +} + +int em_proxy_accept(int socket, struct sockaddr *address, socklen_t *address_len) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "accept(socket=%d,address=%p,address_len=%p)\n", socket, address, address_len); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return accept(socket, address, address_len); + } + + struct { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + } d; + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_ACCEPT; + d.socket = socket; + d.address_len = address_len ? *address_len : 0; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + struct Result { + SocketCallResultHeader header; + int address_len; + uint8_t address[]; + }; + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret == 0) + { + Result *r = (Result*)b->data; + int realAddressLen = MIN(b->bytes - (int)sizeof(Result), r->address_len); + int copiedAddressLen = MIN((int)*address_len, realAddressLen); + if (address) memcpy(address, r->address, copiedAddressLen); + if (address_len) *address_len = realAddressLen; + } + else + { + errno = b->data->errno_; + } + free_call_result(b); + return ret; +} + +ssize_t em_proxy_send(int socket, const void *message, size_t length, int flags) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "send(socket=%d,message=%p,length=%zd,flags=%d)\n", socket, message, length, flags); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return send(socket, message, length, flags); + } + + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*size_t*/ length; + int flags; + uint8_t message[]; + }; + size_t sz = sizeof(MSG)+length; + MSG *d = (MSG*)malloc(sz); + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d->header.callId = b->callId; + d->header.function = POSIX_SOCKET_MSG_SEND; + d->socket = socket; + d->length = length; + d->flags = flags; + if (message) memcpy(d->message, message, length); + else memset(d->message, 0, length); + emscripten_websocket_send_binary(bridgeSocket, d, sz); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret < 0) errno = b->data->errno_; + free_call_result(b); + + free(d); + return ret; +} + +ssize_t em_proxy_recv(int socket, void *buffer, size_t length, int flags) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "recv(socket=%d,buffer=%p,length=%zd,flags=%d)\n", socket, buffer, length, flags); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return recv(socket, buffer, length, flags); + } + + struct { + SocketCallHeader header; + int socket; + uint32_t/*size_t*/ length; + int flags; + } d; + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_RECV; + d.socket = socket; + d.length = length; + d.flags = flags; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret >= 0) + { + struct Result { + SocketCallResultHeader header; + uint8_t data[]; + }; + Result *r = (Result*)b->data; + if (buffer) memcpy(buffer, r->data, MIN(ret, (int)length)); + } + else + { + errno = b->data->errno_; + } + free_call_result(b); + + return ret; +} + +ssize_t em_proxy_sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "sendto(socket=%d,message=%p,length=%zd,flags=%d,dest_addr=%p,dest_len=%d)\n", socket, message, length, flags, dest_addr, dest_len); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return sendto(socket, message, length, flags, dest_addr, dest_len); + } + + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*size_t*/ length; + int flags; + uint32_t/*socklen_t*/ dest_len; + uint8_t dest_addr[MAX_SOCKADDR_SIZE]; + uint8_t message[]; + }; + size_t sz = sizeof(MSG)+length; + MSG *d = (MSG*)malloc(sz); + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d->header.callId = b->callId; + d->header.function = POSIX_SOCKET_MSG_SENDTO; + d->socket = socket; + d->length = length; + d->flags = flags; + d->dest_len = dest_len; + memset(d->dest_addr, 0, sizeof(d->dest_addr)); + if (dest_addr) memcpy(d->dest_addr, dest_addr, dest_len); + if (message) memcpy(d->message, message, length); + else memset(d->message, 0, length); + emscripten_websocket_send_binary(bridgeSocket, d, sz); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret < 0) errno = b->data->errno_; + free_call_result(b); + + free(d); + return ret; +} + +ssize_t em_proxy_recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "recvfrom(socket=%d,buffer=%p,length=%zd,flags=%d,address=%p,address_len=%p)\n", socket, buffer, length, flags, address, address_len); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return recvfrom(socket, buffer, length, flags, address, address_len); + } + + struct { + SocketCallHeader header; + int socket; + uint32_t/*size_t*/ length; + int flags; + uint32_t/*socklen_t*/ address_len; + } d; + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_RECVFROM; + d.socket = socket; + d.length = length; + d.flags = flags; + d.address_len = *address_len; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret >= 0) + { + struct Result { + SocketCallResultHeader header; + int data_len; + int address_len; // N.B. this is the reported address length of the sender, that may be larger than what is actually serialized to this message. + uint8_t data_and_address[]; + }; + Result *r = (Result*)b->data; + if (buffer) memcpy(buffer, r->data_and_address, MIN(r->data_len, (int)length)); + int copiedAddressLen = MIN((address_len ? (int)*address_len : 0), r->address_len); + if (address) memcpy(address, r->data_and_address + r->data_len, copiedAddressLen); + if (address_len) *address_len = r->address_len; + } + else + { + errno = b->data->errno_; + } + free_call_result(b); + + return ret; +} + +int em_proxy_getsockopt(int socket, int level, int option_name, void *option_value, socklen_t *option_len) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "getsockopt(socket=%d,level=%d,option_name=%d,option_value=%p,option_len=%p)\n", socket, level, option_name, option_value, option_len); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return getsockopt(socket, level, option_name, option_value, option_len); + } + + struct { + SocketCallHeader header; + int socket; + int level; + int option_name; + uint32_t/*socklen_t*/ option_len; + } d; + + struct Result { + SocketCallResultHeader header; + uint8_t option_value[]; + }; + + PosixSocketCallResult *b = allocate_call_result(sizeof(Result)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_GETSOCKOPT; + d.socket = socket; + d.level = level; + d.option_name = option_name; + d.option_len = *option_len; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret == 0) + { + Result *r = (Result*)b->data; + int optLen = b->bytes - sizeof(Result); + if (option_value) memcpy(option_value, r->option_value, MIN((int)*option_len, optLen)); + if (option_len) *option_len = optLen; + } + else + { + errno = b->data->errno_; + } + free_call_result(b); + return ret; +} + +int em_proxy_setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "setsockopt(socket=%d,level=%d,option_name=%d,option_value=%p,option_len=%d)\n", socket, level, option_name, option_value, option_len); +#endif + + if (bridgeState != BRIDGE_OPEN) { + return setsockopt(socket, level, option_name, option_value, option_len); + } + + struct MSG { + SocketCallHeader header; + int socket; + int level; + int option_name; + int option_len; + uint8_t option_value[]; + }; + int messageSize = sizeof(MSG) + option_len; + MSG *d = (MSG*)malloc(messageSize); + + PosixSocketCallResult *b = allocate_call_result(sizeof(SocketCallResultHeader)); + d->header.callId = b->callId; + d->header.function = POSIX_SOCKET_MSG_SETSOCKOPT; + d->socket = socket; + d->level = level; + d->option_name = option_name; + if (option_value) memcpy(d->option_value, option_value, option_len); + else memset(d->option_value, 0, option_len); + d->option_len = option_len; + emscripten_websocket_send_binary(bridgeSocket, d, messageSize); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret != 0) errno = b->data->errno_; + free_call_result(b); + + free(d); + return ret; +} + +// Host name resolution: + +int em_proxy_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) +{ +#define MAX_NODE_LEN 2048 +#define MAX_SERVICE_LEN 128 + + if (bridgeState != BRIDGE_OPEN) { + return getaddrinfo(node, service, hints, res); + } + + struct { + SocketCallHeader header; + char node[MAX_NODE_LEN]; // Arbitrary max length limit + char service[MAX_SERVICE_LEN]; // Arbitrary max length limit + int hasHints; + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + } d; + + struct ResAddrinfo + { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + int/*socklen_t*/ ai_addrlen; + uint8_t /*sockaddr **/ ai_addr[]; + }; + + struct Result { + SocketCallResultHeader header; + char ai_canonname[MAX_NODE_LEN]; + int addrCount; + uint8_t /*ResAddrinfo[]*/ addr[]; + }; + + memset(&d, 0, sizeof(d)); + PosixSocketCallResult *b = allocate_call_result(sizeof(Result)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_GETADDRINFO; + if (node) + { + assert(strlen(node) <= MAX_NODE_LEN-1); + strncpy(d.node, node, MAX_NODE_LEN-1); + } + if (service) + { + assert(strlen(service) <= MAX_SERVICE_LEN-1); + strncpy(d.service, service, MAX_SERVICE_LEN-1); + } + d.hasHints = !!hints; + if (hints) + { + d.ai_flags = hints->ai_flags; + d.ai_family = hints->ai_family; + d.ai_socktype = hints->ai_socktype; + d.ai_protocol = hints->ai_protocol; + } + +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "getaddrinfo(node=%s,service=%s,hasHints=%d,ai_flags=%d,ai_family=%d,ai_socktype=%d,ai_protocol=%d,hintsPtr=%p,resPtr=%p)\n", node, service, d.hasHints, d.ai_flags, d.ai_family, d.ai_socktype, d.ai_protocol, hints, res); +#endif + + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d)); + + wait_for_call_result(b); + int ret = b->data->ret; +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "getaddrinfo finished, ret=%d\n", ret); +#endif + if (ret == 0) + { + if (res) + { + Result *r = (Result*)b->data; + uint8_t *raiAddr = (uint8_t*)&r->addr[0]; + addrinfo *results = (addrinfo*)malloc(sizeof(addrinfo)*r->addrCount); +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "%d results\n", r->addrCount); +#endif + for(size_t i = 0; (int)i < r->addrCount; ++i) + { + ResAddrinfo *rai = (ResAddrinfo*)raiAddr; + results[i].ai_flags = rai->ai_flags; + results[i].ai_family = rai->ai_family; + results[i].ai_socktype = rai->ai_socktype; + results[i].ai_protocol = rai->ai_protocol; + results[i].ai_addrlen = rai->ai_addrlen; + results[i].ai_addr = (sockaddr *)malloc(results[i].ai_addrlen); + memcpy(results[i].ai_addr, rai->ai_addr, results[i].ai_addrlen); + results[i].ai_canonname = (i == 0) ? strdup(r->ai_canonname) : 0; + results[i].ai_next = (int)i+1 < r->addrCount ? &results[i+1] : 0; + fprintf(stderr, "%d: ai_flags=%d, ai_family=%d, ai_socktype=%d, ai_protocol=%d, ai_addrlen=%d, ai_addr=", (int)i, results[i].ai_flags, results[i].ai_family, results[i].ai_socktype, results[i].ai_protocol, results[i].ai_addrlen); + for(size_t j = 0; j < results[i].ai_addrlen; ++j) + fprintf(stderr, " %02X", ((uint8_t*)results[i].ai_addr)[j]); + fprintf(stderr, ",ai_canonname=%s, ai_next=%p\n", results[i].ai_canonname, results[i].ai_next); + raiAddr += sizeof(ResAddrinfo) + rai->ai_addrlen; + } + *res = results; + } + } + else + { + errno = b->data->errno_; + if (res) *res = 0; + } + free_call_result(b); + + return ret; +} + +void em_proxy_freeaddrinfo(struct addrinfo *res) +{ + if (bridgeState != BRIDGE_OPEN) { + return freeaddrinfo(res); + } + + for(addrinfo *r = res; r; r = r->ai_next) + { + free(r->ai_canonname); + free(r->ai_addr); + } + free(res); +} + +int em_proxy_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) +{ + return getnameinfo(addr, addrlen, host, hostlen, serv, servlen, flags); +} + +int em_proxy_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout) +{ + if (bridgeState != BRIDGE_OPEN) { + return select(nfds, readfds, writefds, exceptfds, timeout); + } + + // TODO: implement + return -1; +} + +#endif /* __EMSCRIPTEN__ */ diff --git a/src/network/core/em_proxy.h b/src/network/core/em_proxy.h new file mode 100644 index 0000000000..dfd26d40c4 --- /dev/null +++ b/src/network/core/em_proxy.h @@ -0,0 +1,56 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD 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 OpenTTD. If not, see . + */ + +/** + * @file em_proxy.h Emscripten socket proxy + */ + +#ifndef NETWORK_CORE_EM_PROXY_H +#define NETWORK_CORE_EM_PROXY_H + +int em_proxy_initialize_network(void); + +int em_proxy_socket(int domain, int type, int protocol); +int em_proxy_closesocket(int socket); +int em_proxy_shutdown(int socket, int how); +int em_proxy_bind(int socket, const struct sockaddr *address, socklen_t address_len); +int em_proxy_connect(int socket, const struct sockaddr *address, socklen_t address_len); +int em_proxy_listen(int socket, int backlog); +int em_proxy_accept(int socket, struct sockaddr *address, socklen_t *address_len); +ssize_t em_proxy_send(int socket, const void *message, size_t length, int flags); +ssize_t em_proxy_recv(int socket, void *buffer, size_t length, int flags); +ssize_t em_proxy_sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t dest_len); +ssize_t em_proxy_recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len); +int em_proxy_getsockopt(int socket, int level, int option_name, void *option_value, socklen_t *option_len); +int em_proxy_setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len); +int em_proxy_getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +void em_proxy_freeaddrinfo(struct addrinfo *res); +int em_proxy_getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); +int em_proxy_select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); + +#ifndef EM_PROXY_NO_MACROS +# define socket em_proxy_socket +# undef closesocket +# define closesocket em_proxy_closesocket +# define shutdown em_proxy_shutdown +# define bind em_proxy_bind +# define connect em_proxy_connect +# define listen em_proxy_listen +# define accept em_proxy_accept +# define send em_proxy_send +# define recv em_proxy_recv +# define sendto em_proxy_sendto +# define recvfrom em_proxy_recvfrom +# define getsockopt em_proxy_getsockopt +# define setsockopt em_proxy_setsockopt +# define getaddrinfo em_proxy_getaddrinfo +# define freeaddrinfo em_proxy_freeaddrinfo +# define getnameinfo em_proxy_getnameinfo +# define select em_proxy_select +#endif /* EM_PROXY_NO_MACROS */ + +#endif /* NETWORK_CORE_EM_PROXY_H */ diff --git a/src/network/core/os_abstraction.cpp b/src/network/core/os_abstraction.cpp index b2d3475d01..814ac04939 100644 --- a/src/network/core/os_abstraction.cpp +++ b/src/network/core/os_abstraction.cpp @@ -150,13 +150,9 @@ bool SetNonBlocking(SOCKET d) */ bool SetNoDelay(SOCKET d) { -#ifdef __EMSCRIPTEN__ - return true; -#else int flags = 1; /* The (const char*) cast is needed for windows */ return setsockopt(d, IPPROTO_TCP, TCP_NODELAY, (const char *)&flags, sizeof(flags)) == 0; -#endif } /** diff --git a/src/network/core/os_abstraction.h b/src/network/core/os_abstraction.h index bde1f6bedd..4ef7994bd7 100644 --- a/src/network/core/os_abstraction.h +++ b/src/network/core/os_abstraction.h @@ -174,4 +174,8 @@ NetworkError GetSocketError(SOCKET d); static_assert(sizeof(in_addr) == 4); ///< IPv4 addresses should be 4 bytes. static_assert(sizeof(in6_addr) == 16); ///< IPv6 addresses should be 16 bytes. +#if defined(__EMSCRIPTEN__) +# include "em_proxy.h" +#endif /* __EMSCRIPTEN__ */ + #endif /* NETWORK_CORE_OS_ABSTRACTION_H */