Emscripten: socket proxy first draft

This commit is contained in:
Sergii Pylypenko
2021-07-18 02:20:11 +03:00
parent 4d49fa47fa
commit bd866de601
7 changed files with 1001 additions and 4 deletions

View File

@@ -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")

View File

@@ -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

View File

@@ -34,6 +34,9 @@ bool NetworkCoreInitialize()
}
}
#endif /* _WIN32 */
#if defined(__EMSCRIPTEN__)
em_proxy_initialize_network();
#endif
return true;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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 <stdio.h>
#include <stdlib.h>
#include <emscripten/emscripten.h>
#include <emscripten/websocket.h>
#include <emscripten/threading.h>
#include <pthread.h>
#include <sys/socket.h>
#include <errno.h>
#include <assert.h>
#include <netdb.h>
#if defined(__APPLE__) || defined(__linux__)
#include <arpa/inet.h>
#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: <netdb.h>
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__ */

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
/**
* @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 */

View File

@@ -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
}
/**

View File

@@ -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 */