diff --git a/embuilder.py b/embuilder.py index a25afb47e9ad0..0f9b5b02d8273 100755 --- a/embuilder.py +++ b/embuilder.py @@ -35,6 +35,7 @@ build libc libc-mt libc-extras + libc-sockets struct_info emmalloc emmalloc_debug @@ -47,6 +48,7 @@ dlmalloc_threadsafe_noerrno dlmalloc_threadsafe_debug_noerrno pthreads + posix_proxy libc++ libc++_noexcept libc++abi @@ -110,11 +112,11 @@ SYSTEM_TASKS = [ 'al', 'compiler-rt', 'gl', 'gl-mt', 'libc', 'libc-mt', 'libc-extras', - 'emmalloc', 'emmalloc_debug', 'dlmalloc', 'dlmalloc_threadsafe', 'pthreads', - 'dlmalloc_debug', 'dlmalloc_threadsafe_debug', 'libc++', 'libc++_noexcept', + 'libc-sockets', 'emmalloc', 'emmalloc_debug', 'dlmalloc', + 'dlmalloc_threadsafe', 'pthreads', 'posix_proxy', 'dlmalloc_debug', + 'dlmalloc_threadsafe_debug', 'libc++', 'libc++_noexcept', 'dlmalloc_debug_noerrno', 'dlmalloc_threadsafe_debug_noerrno', - 'dlmalloc_noerrno', 'dlmalloc_threadsafe_noerrno', - 'libc++abi', 'html5' + 'dlmalloc_noerrno', 'dlmalloc_threadsafe_noerrno', 'libc++abi', 'html5' ] USER_TASKS = [ 'binaryen', 'bullet', 'freetype', 'icu', 'libpng', 'ogg', 'sdl2', @@ -303,6 +305,20 @@ def main(): build_port('cocos2d', None, ['-s', 'USE_COCOS2D=3', '-s', 'USE_ZLIB=1', '-s', 'USE_LIBPNG=1', '-s', 'ERROR_ON_UNDEFINED_SYMBOLS=0']) elif what == 'regal': build_port('regal', 'libregal.bc', ['-s', 'USE_REGAL=1']) + elif what == 'libc-sockets': + build(''' + #include + int main() { + return socket(0,0,0); + } + ''', ['libc-sockets.bc']) + elif what == 'posix_proxy': + build(''' + #include + int main() { + return socket(0,0,0); + } + ''', ['libposix-sockets-proxy.bc'], ['-s', 'PROXY_POSIX_SOCKETS=1', '-s', 'USE_PTHREADS=1', '-s', 'PROXY_TO_PTHREAD=1']) else: logger.error('unfamiliar build target: ' + what) sys.exit(1) diff --git a/emscripten-version.txt b/emscripten-version.txt index f720db2068beb..0610304eebb32 100644 --- a/emscripten-version.txt +++ b/emscripten-version.txt @@ -1 +1 @@ -"1.38.25" +"1.38.26" diff --git a/site/source/docs/porting/index.rst b/site/source/docs/porting/index.rst index f9538de78d977..1a2d7e2adde8f 100644 --- a/site/source/docs/porting/index.rst +++ b/site/source/docs/porting/index.rst @@ -17,6 +17,7 @@ The topics in this section cover the main integration points that you need to co Audio Debugging pthreads + networking simd asyncify emterpreter diff --git a/site/source/docs/porting/networking.rst b/site/source/docs/porting/networking.rst new file mode 100644 index 0000000000000..cd4ab6351f1e6 --- /dev/null +++ b/site/source/docs/porting/networking.rst @@ -0,0 +1,38 @@ +.. Networking: + +============================== +Networking +============================== + +Emscripten compiled applications have a number of ways to connect with online servers. Check the subtopics here to learn about the different strategies that are available. + +If you are familiar with networking concepts provided by different web APIs, such as XmlHttpRequest, Fetch, WebSockets and WebRTC, you can quickly get started by leveraging what you already know: by calling out from C/C++ code to JavaScript (see the "Connecting C++ and JavaScript" section), you can establish networked connections by writing regular JavaScript. For C/C++ developers, Emscripten provides a few approaches, described here. + +Emscripten WebSockets API +========================= + +WebSockets API provides connection-oriented message-framed bidirectional asynchronous networking communication to the browser. It is the closest to TCP on the web that web sites can access, direct access to TCP sockets is not possible from web browsers. + +Emscripten provides a passthrough API for accessing the WebSockets API from C/C++ code. This is useful for developers who would prefer not to write any JavaScript code, or deal with the C/C++ and JavaScript language interop. See the system include file for details. One benefit that the Emscripten WebSockets API provides over manual WebSockets access in JavaScript is the ability to share access to a WebSocket handle across multiple threads, something that can be time consuming to develop from scratch. + +Emulated POSIX TCP Sockets over WebSockets +========================================== + +If you have existing TCP networking code written in C/C++ that utilizes the Posix Sockets API, by default Emscripten attempts to emulate such connections to take place over the WebSocket protocol instead. For this to work, you will need to use something like WebSockify on the server side to enable the TCP server stack to receive incoming WebSocket connections. This emulation is not very complete at the moment, it is likely that you will run into problems out of the box and need to adapt the code to work within the limitations that this emulation provides. + +Full POSIX Sockets over WebSocket Proxy Server +============================================== + +Emscripten provides a native POSIX Sockets proxy server program, located in directory tools/websocket_to_posix_proxy/, that allows full POSIX Sockets API access from a web browser. This support works by proxying all POSIX Sockets API calls from the browser to the Emscripten POSIX Sockets proxy server (via transparent use of WebSockets API), and the proxy server then performs the native TCP/UDP calls on behalf of the page. This allows a web browser page to run full TCP & UDP connections, act as a server to accept incoming connections, and perform host name lookups and reverse lookups. Because all API calls are individually proxied, this support can be slow. This support is mostly useful for developing testing infrastructure and debugging. + +For an example of how the POSIX Sockets proxy server works in an Emscripten client program, see the file tests/websocket/tcp_echo_client.cpp. + +XmlHttpRequests and Fetch API +============================= + +For HTTP transfers, one can use the browser built-in XmlHttpRequest (XHR) API and the newer Fetch API. These can be accessed directly from JavaScript. Emscripten also provides passthrough APIs to perform HTTP requests. For more information, see the emscripten_async_wget*() C API and the Emscripten Fetch API. + +WebRTC and UDP +============== + +Direct UDP communication is not available in browsers, but as a close alternative, the WebRTC specification provides a mechanism to perform UDP-like communication with WebRTC Data Channels. Currently Emscripten does not provide a C/C++ API for interacting with WebRTC. diff --git a/src/library.js b/src/library.js index 5d3f0d2e6cd2a..a1527d8dcdb0f 100644 --- a/src/library.js +++ b/src/library.js @@ -3455,6 +3455,7 @@ LibraryManager.library = { // arpa/inet.h // ========================================================================== +#if PROXY_POSIX_SOCKETS == 0 // old ipv4 only functions inet_addr__deps: ['_inet_pton4_raw'], inet_addr: function(ptr) { @@ -4197,6 +4198,8 @@ LibraryManager.library = { 0x0b00000a, 0x0c00000a, 0x0d00000a, 0x0e00000a] /* 0x0100000a is reserved */ }, +#endif // PROXY_POSIX_SOCKETS == 0 + // pwd.h getpwnam: function() { throw 'getpwnam: TODO' }, diff --git a/src/library_syscall.js b/src/library_syscall.js index c5a50299ecf14..04400a8968eba 100644 --- a/src/library_syscall.js +++ b/src/library_syscall.js @@ -476,6 +476,7 @@ var SyscallsLibrary = { __syscall97: function(which, varargs) { // setpriority return -ERRNO_CODES.EPERM; }, +#if PROXY_POSIX_SOCKETS == 0 __syscall102__deps: ['$SOCKFS', '$DNS', '_read_sockaddr', '_write_sockaddr'], __syscall102: function(which, varargs) { // socketcall var call = SYSCALLS.get(), socketvararg = SYSCALLS.get(); @@ -654,6 +655,7 @@ var SyscallsLibrary = { default: abort('unsupported socketcall syscall ' + call); } }, +#endif // ~PROXY_POSIX_SOCKETS==0 __syscall104: function(which, varargs) { // setitimer return -ERRNO_CODES.ENOSYS; // unsupported feature }, diff --git a/src/library_websocket.js b/src/library_websocket.js index 91e4e0d8e81d6..c9c4fbbbe7e4a 100644 --- a/src/library_websocket.js +++ b/src/library_websocket.js @@ -109,6 +109,7 @@ var LibraryWebSocket = { #endif return {{{ cDefine('EMSCRIPTEN_RESULT_INVALID_TARGET') }}}; } + HEAPU32[urlLength>>2] = lengthBytesUTF8(socket.url); return {{{ cDefine('EMSCRIPTEN_RESULT_SUCCESS') }}}; }, diff --git a/src/settings.js b/src/settings.js index 0a75a29a8d79f..da02d26775dcb 100644 --- a/src/settings.js +++ b/src/settings.js @@ -364,6 +364,9 @@ var SOCKET_WEBRTC = 0; // where addr and port are derived from the socket connect/bind/accept calls. var WEBSOCKET_URL = 'ws://'; +// If 1, the POSIX sockets API uses a proxy bridge to proxy sockets calls +var PROXY_POSIX_SOCKETS = 0; + // A string containing a comma separated list of WebSocket subprotocols // as would be present in the Sec-WebSocket-Protocol header. var WEBSOCKET_SUBPROTOCOL = 'binary'; diff --git a/system/include/emscripten/posix_socket.h b/system/include/emscripten/posix_socket.h new file mode 100644 index 0000000000000..c07c7999213ab --- /dev/null +++ b/system/include/emscripten/posix_socket.h @@ -0,0 +1,13 @@ +#pragma once + +#include "websocket.h" + +#ifdef __cplusplus +extern "C" { +#endif + +extern EMSCRIPTEN_RESULT emscripten_init_websocket_to_posix_socket_bridge(const char *bridgeUrl); + +#ifdef __cplusplus +} +#endif diff --git a/system/lib/libc-sockets.symbols b/system/lib/libc-sockets.symbols new file mode 100644 index 0000000000000..0f9dd710c483e --- /dev/null +++ b/system/lib/libc-sockets.symbols @@ -0,0 +1,21 @@ + U __errno_location + U __syscall102 + U __syscall221 + U __syscall_ret +-------- T accept +-------- T bind +-------- T connect +-------- T getpeername +-------- T getsockname +-------- T getsockopt +-------- T listen +-------- T recv +-------- T recvfrom +-------- T recvmsg +-------- T send +-------- T sendmsg +-------- T sendto +-------- T setsockopt +-------- T shutdown +-------- T socket +-------- T socketpair diff --git a/system/lib/websocket/websocket_to_posix_socket.cpp b/system/lib/websocket/websocket_to_posix_socket.cpp new file mode 100644 index 0000000000000..10a44b4f128b7 --- /dev/null +++ b/system/lib/websocket/websocket_to_posix_socket.cpp @@ -0,0 +1,965 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +// 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)) + +extern "C" +{ + +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; + +// 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 (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; +} + +#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 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 + + 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 socketpair(int domain, int type, int protocol, int socket_vector[2]) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "socketpair(domain=%d,type=%d,protocol=%d, socket_vector=[%d,%d])\n", domain, type, protocol, socket_vector[0], socket_vector[1]); +#endif + + struct { + SocketCallHeader header; + int domain; + int type; + int protocol; + } d; + + struct Result { + SocketCallResultHeader header; + int sv[2]; + }; + + PosixSocketCallResult *b = allocate_call_result(sizeof(Result)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_SOCKETPAIR; + 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) + { + Result *r = (Result*)b->data; + socket_vector[0] = r->sv[0]; + socket_vector[1] = r->sv[1]; + } + else + { + errno = b->data->errno_; + } + free_call_result(b); + return ret; +} + +int 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 + + 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 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 + + 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 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 + + 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 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 + + 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 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 + + 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 - sizeof(Result), r->address_len); + int copiedAddressLen = MIN(*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; +} + +int getsockname(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, "getsockname(socket=%d,address=%p,address_len=%p)\n", socket, address, address_len); +#endif + + struct { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + } d; + + struct Result { + SocketCallResultHeader header; + int address_len; + uint8_t address[]; + }; + + PosixSocketCallResult *b = allocate_call_result(sizeof(Result)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_GETSOCKNAME; + d.socket = socket; + d.address_len = *address_len; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d) + *address_len - MAX_SOCKADDR_SIZE); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret == 0) + { + Result *r = (Result*)b->data; + int realAddressLen = MIN(b->bytes - sizeof(Result), r->address_len); + int copiedAddressLen = MIN(*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; +} + +int getpeername(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, "getpeername(socket=%d,address=%p,address_len=%p)\n", socket, address, address_len); +#endif + + struct { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + } d; + + struct Result { + SocketCallResultHeader header; + int address_len; + uint8_t address[]; + }; + + PosixSocketCallResult *b = allocate_call_result(sizeof(Result)); + d.header.callId = b->callId; + d.header.function = POSIX_SOCKET_MSG_GETPEERNAME; + d.socket = socket; + d.address_len = *address_len; + emscripten_websocket_send_binary(bridgeSocket, &d, sizeof(d) + *address_len - MAX_SOCKADDR_SIZE); + + wait_for_call_result(b); + int ret = b->data->ret; + if (ret == 0) + { + Result *r = (Result*)b->data; + int realAddressLen = MIN(b->bytes - sizeof(Result), r->address_len); + int copiedAddressLen = MIN(*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 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 + + 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 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 + + 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, length)); + } + else + { + errno = b->data->errno_; + } + free_call_result(b); + + return ret; +} + +ssize_t 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 + + 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 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 + + 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, length)); + int copiedAddressLen = MIN((address_len ? *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; +} + +ssize_t sendmsg(int socket, const struct msghdr *message, int flags) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "sendmsg(socket=%d,message=%p,flags=%d)\n", socket, message, flags); +#endif + + exit(1); // TODO + return 0; +} + +ssize_t recvmsg(int socket, struct msghdr *message, int flags) +{ +#ifdef POSIX_SOCKET_DEBUG + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "recvmsg(socket=%d,message=%p,flags=%d)\n", socket, message, flags); +#endif + + exit(1); // TODO + return 0; +} + +int 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 + + 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(*option_len, optLen)); + if (option_len) *option_len = optLen; + } + else + { + errno = b->data->errno_; + } + free_call_result(b); + return ret; +} + +int 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 + + 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 getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) +{ +#define MAX_NODE_LEN 2048 +#define MAX_SERVICE_LEN 128 + + 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; + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "getaddrinfo finished, ret=%d\n", ret); + 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); + emscripten_log(EM_LOG_NO_PATHS | EM_LOG_CONSOLE | EM_LOG_ERROR | EM_LOG_JS_STACK, "%d results\n", r->addrCount); + for(size_t i = 0; 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 = 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 freeaddrinfo(struct addrinfo *res) +{ + for(addrinfo *r = res; r; r = r->ai_next) + { + free(r->ai_canonname); + free(r->ai_addr); + } + free(res); +} + +int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags) +{ + // TODO + // POSIX_SOCKET_MSG_GETNAMEINFO + return -1; +} + +// TODO: +// const char *gai_strerror(int); + + + +} // ~extern "C" diff --git a/tests/test_sockets.py b/tests/test_sockets.py index b5546829b2ef8..9b06a001efd2f 100644 --- a/tests/test_sockets.py +++ b/tests/test_sockets.py @@ -123,6 +123,29 @@ def __exit__(self, *args, **kwargs): # make sure to use different ports in each one because it takes a while for the processes to be cleaned up +# Executes a native executable server process +class BackgroundServerProcess(object): + def __init__(self, args): + self.processes = [] + self.args = args + + def __enter__(self): + print('Running background server: ' + str(self.args)) + process = Popen(self.args) + self.processes.append(process) + + def __exit__(self, *args, **kwargs): + clean_processes(self.processes) + + +def NodeJsWebSocketEchoServerProcess(): + return BackgroundServerProcess([path_from_root('tests', 'websocket', 'nodejs_websocket_echo_server.js')]) + + +def PythonTcpEchoServerProcess(port): + return BackgroundServerProcess([PYTHON, path_from_root('tests', 'websocket', 'tcp_echo_server.py'), port]) + + class sockets(BrowserCore): emcc_args = [] @@ -433,3 +456,24 @@ def test_nodejs_sockets_echo(self): out = run_js('client.js', engine=NODE_JS, full_output=True) self.assertContained('do_msg_read: read 14 bytes', out) self.assertContained('connect: ws://localhost:59168/testA/testB, text,base64,binary', out) + + # Test Emscripten WebSockets API to send and receive text and binary messages against an echo server. + # N.B. running this test requires 'npm install ws' in Emscripten root directory + def test_websocket_send(self): + with NodeJsWebSocketEchoServerProcess(): + self.btest(os.path.join('websocket', 'test_websocket_send.c'), expected='101', args=['-lwebsocket', '-s', 'NO_EXIT_RUNTIME=1', '-s', 'WEBSOCKET_DEBUG=1']) + + # Test that native POSIX sockets API can be used by proxying calls to an intermediate WebSockets -> POSIX sockets bridge server + def test_posix_proxy_sockets(self): + # Build the websocket bridge server + run_process(['cmake', path_from_root('tools', 'websocket_to_posix_proxy')]) + run_process(['cmake', '--build', '.']) + if os.name == 'nt': # This is not quite exact, instead of "isWindows()" this should be "If CMake defaults to building with Visual Studio", but there is no good check for that, so assume Windows==VS. + proxy_server = os.path.join(self.get_dir(), 'Debug', 'websocket_to_posix_proxy.exe') + else: + proxy_server = os.path.join(self.get_dir(), 'websocket_to_posix_proxy') + + with BackgroundServerProcess([proxy_server, '8080']): + with PythonTcpEchoServerProcess('7777'): + # Build and run the TCP echo client program with Emscripten + self.btest(path_from_root('tests', 'websocket', 'tcp_echo_client.cpp'), expected='101', args=['-lwebsocket', '-s', 'PROXY_POSIX_SOCKETS=1', '-s', 'USE_PTHREADS=1', '-s', 'PROXY_TO_PTHREAD=1']) diff --git a/tests/websocket/nodejs_websocket_echo_server.js b/tests/websocket/nodejs_websocket_echo_server.js new file mode 100644 index 0000000000000..c2923f8d59284 --- /dev/null +++ b/tests/websocket/nodejs_websocket_echo_server.js @@ -0,0 +1,36 @@ +function padHex(number, digits) { + var s = number.toString(16); + return Array(Math.max(digits - s.length + 1, 0)).join(0) + s; +} + +function hexDump(bytes) { + var s = ' '; + for(var i = 0; i < bytes.length; ++i) { + s += ' ' + padHex(bytes[i], 2); + if (i % 16 == 15) { + console.log(s); + s = ' '; + } + else if (i % 8 == 7) s += ' '; + } + console.log(s); +} + +var port = 8088; +var ws = require('ws'); +var wss = new ws.Server({ port: port }); +console.log('WebSocket server listening on ws://localhost:' + port + '/'); +wss.on('connection', function(ws) { + console.log('Client connected!'); + ws.on('message', function(message) { + if (typeof message === 'string') { + console.log('received TEXT: ' + message.length + ' characters:'); + console.log(' "' + message + '"'); + } else { + console.log('received BINARY: ' + message.length + ' bytes:'); + hexDump(message); + } + console.log(''); + ws.send(message); // Echo back the received message + }); +}); diff --git a/tests/websocket/tcp_echo_client.cpp b/tests/websocket/tcp_echo_client.cpp new file mode 100644 index 0000000000000..47a001ef7a46c --- /dev/null +++ b/tests/websocket/tcp_echo_client.cpp @@ -0,0 +1,123 @@ +// TCP client that sends a few messages to a server and prints out the replies +#include +#include +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#include +#include + +EMSCRIPTEN_WEBSOCKET_T bridgeSocket = 0; + +extern "C" { +EMSCRIPTEN_WEBSOCKET_T emscripten_init_websocket_to_posix_socket_bridge(const char *bridgeUrl); +} +#endif + +int lookup_host(const char *host) +{ + struct addrinfo hints, *res; + int errcode; + char addrstr[100]; + void *ptr; + + memset(&hints, 0, sizeof (hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + errcode = getaddrinfo(host, NULL, &hints, &res); + if (errcode != 0) + { + printf("getaddrinfo failed!\n"); + return -1; + } + + printf("Host: %s\n", host); + while (res) + { + inet_ntop(res->ai_family, res->ai_addr->sa_data, addrstr, 100); + + switch (res->ai_family) + { + case AF_INET: + ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr; + break; + case AF_INET6: + ptr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr; + break; + } + inet_ntop(res->ai_family, ptr, addrstr, 100); + printf("IPv%d address: %s (%s)\n", res->ai_family == PF_INET6 ? 6 : 4, addrstr, res->ai_canonname); + res = res->ai_next; + } + + return 0; +} + +int main(int argc , char *argv[]) +{ +#ifdef __EMSCRIPTEN__ + bridgeSocket = emscripten_init_websocket_to_posix_socket_bridge("ws://localhost:8080"); + // Synchronously wait until connection has been established. + uint16_t readyState = 0; + do { + emscripten_websocket_get_ready_state(bridgeSocket, &readyState); + emscripten_thread_sleep(100); + } while(readyState == 0); +#endif + + lookup_host("google.com"); + + // Create socket + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) + { + printf("Could not create socket"); + exit(1); + } + printf("Socket created: %d\n", sock); + + struct sockaddr_in server; + server.sin_addr.s_addr = inet_addr("127.0.0.1"); + server.sin_family = AF_INET; + server.sin_port = htons(7777); + + if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) + { + perror("connect failed. Error"); + return 1; + } + + puts("Connected\n"); + for(int i = 0; i < 10; ++i) + { + const char message[] = "hell"; + if (send(sock, message, strlen(message), 0) < 0) + { + puts("Send failed"); + return 1; + } + + char server_reply[256]; + if (recv(sock, server_reply, 256, 0) < 0) + { + puts("recv failed"); + break; + } + + puts("Server reply: "); + puts(server_reply); + } + + close(sock); +#ifdef REPORT_RESULT + REPORT_RESULT(101); +#endif + return 0; +} diff --git a/tests/websocket/tcp_echo_server.py b/tests/websocket/tcp_echo_server.py new file mode 100644 index 0000000000000..fce31bb8ab026 --- /dev/null +++ b/tests/websocket/tcp_echo_server.py @@ -0,0 +1,18 @@ +import socket, sys + +def listen(): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.bind(('0.0.0.0', int(sys.argv[1]))) + s.listen(10) + while True: + conn, address = s.accept() + while True: + data = conn.recv(2048) + if not data: + break + conn.sendall(data) + conn.close() + +if __name__ == "__main__": + listen() diff --git a/tests/websocket/tcp_in_two_threads.cpp b/tests/websocket/tcp_in_two_threads.cpp new file mode 100644 index 0000000000000..b74378259d9a8 --- /dev/null +++ b/tests/websocket/tcp_in_two_threads.cpp @@ -0,0 +1,162 @@ +// TCP client that operates TCP connection in two threads: one thread that does blocking recv(), +// and another that does send()s +#include +#include +#include +#include +#include +#include +#include + +#ifdef __EMSCRIPTEN__ +#include +#include +#include + +EMSCRIPTEN_WEBSOCKET_T bridgeSocket = 0; + +extern "C" { +EMSCRIPTEN_WEBSOCKET_T emscripten_init_websocket_to_posix_socket_bridge(const char *bridgeUrl); +} +#endif + +int lookup_host(const char *host) +{ + struct addrinfo hints, *res; + int errcode; + char addrstr[100]; + void *ptr; + + memset(&hints, 0, sizeof (hints)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags |= AI_CANONNAME; + + errcode = getaddrinfo(host, NULL, &hints, &res); + if (errcode != 0) + { + printf("getaddrinfo failed!\n"); + return -1; + } + + printf("Host: %s\n", host); + while (res) + { + inet_ntop(res->ai_family, res->ai_addr->sa_data, addrstr, 100); + + switch (res->ai_family) + { + case AF_INET: + ptr = &((struct sockaddr_in *)res->ai_addr)->sin_addr; + break; + case AF_INET6: + ptr = &((struct sockaddr_in6 *)res->ai_addr)->sin6_addr; + break; + } + inet_ntop(res->ai_family, ptr, addrstr, 100); + printf("IPv%d address: %s (%s)\n", res->ai_family == PF_INET6 ? 6 : 4, addrstr, res->ai_canonname); + res = res->ai_next; + } + + return 0; +} + +int pendingMessages = 0; + +void *send_thread(void *arg) +{ + int sock = (int)arg; + + for(int i = 0; i < 10; ++i) + { + char message[] = "hella"; + message[4] += i; + + while(emscripten_atomic_load_u32(&pendingMessages) != 0) + emscripten_thread_sleep(10); + + printf("Send()ing\n"); + if (send(sock, message, strlen(message), 0) < 0) + { + puts("Send failed"); + pthread_exit((void*)1); + } + printf("Send() done\n"); + emscripten_atomic_add_u32(&pendingMessages, 1); + } + pthread_exit(0); +} + +void *recv_thread(void *arg) +{ + int sock = (int)arg; + + for(int i = 0; i < 10; ++i) + { + char server_reply[256] = {}; + printf("Recv()ing\n"); + if (recv(sock, server_reply, 256, 0) < 0) + { + puts("recv failed"); + pthread_exit((void*)1); + } + printf("Recv() done\n"); + + puts("Server reply: "); + puts(server_reply); + emscripten_atomic_sub_u32(&pendingMessages, 1); + } + pthread_exit(0); +} + +int main(int argc , char *argv[]) +{ +#ifdef __EMSCRIPTEN__ + bridgeSocket = emscripten_init_websocket_to_posix_socket_bridge("ws://localhost:8080"); + // Synchronously wait until connection has been established. + uint16_t readyState = 0; + do { + emscripten_websocket_get_ready_state(bridgeSocket, &readyState); + emscripten_thread_sleep(100); + } while(readyState == 0); +#endif + + lookup_host("google.com"); + + // Create socket + int sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) + { + printf("Could not create socket"); + exit(1); + } + printf("Socket created: %d\n", sock); + + struct sockaddr_in server; + server.sin_addr.s_addr = inet_addr("127.0.0.1"); + server.sin_family = AF_INET; + server.sin_port = htons(8888); + + if (connect(sock, (struct sockaddr *)&server, sizeof(server)) < 0) + { + perror("connect failed. Error"); + return 1; + } + + pthread_t sendThread; + pthread_t recvThread; + pthread_create(&recvThread, 0, &recv_thread, (void*)sock); + emscripten_thread_sleep(1000); // To get a better guarantee that the first recv() precedes the first send(), sleep for a moment between recv+send thread starts + pthread_create(&sendThread, 0, &send_thread, (void*)sock); + void *sendRet; + void *recvRet; + printf("Waiting for send thread to be finished\n"); + pthread_join(sendThread, &sendRet); + printf("Send thread finished, waiting for recv thread to be finished\n"); + pthread_join(recvThread, &recvRet); + printf("Send thread and recv thread finished\n"); + close(sock); + if ((int)sendRet != 0) fprintf(stderr, "pthread send failed!\n"); + if ((int)recvRet != 0) fprintf(stderr, "pthread recv failed!\n"); + return 0; +} diff --git a/tests/websocket/test_websocket_send.c b/tests/websocket/test_websocket_send.c new file mode 100644 index 0000000000000..b72a092d501f0 --- /dev/null +++ b/tests/websocket/test_websocket_send.c @@ -0,0 +1,89 @@ +#include +#include +#include + +EM_BOOL WebSocketOpen(int eventType, const EmscriptenWebSocketOpenEvent *e, void *userData) +{ + printf("open(eventType=%d, userData=%d)\n", eventType, (int)userData); + + emscripten_websocket_send_utf8_text(e->socket, "hello on the other side"); + + char data[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }; + emscripten_websocket_send_binary(e->socket, data, sizeof(data)); + + return 0; +} + +EM_BOOL WebSocketClose(int eventType, const EmscriptenWebSocketCloseEvent *e, void *userData) +{ + printf("close(eventType=%d, wasClean=%d, code=%d, reason=%s, userData=%d)\n", eventType, e->wasClean, e->code, e->reason, (int)userData); + return 0; +} + +EM_BOOL WebSocketError(int eventType, const EmscriptenWebSocketErrorEvent *e, void *userData) +{ + printf("error(eventType=%d, userData=%d)\n", eventType, (int)userData); + return 0; +} + +static int passed = 0; + +EM_BOOL WebSocketMessage(int eventType, const EmscriptenWebSocketMessageEvent *e, void *userData) +{ + printf("message(eventType=%d, userData=%d, data=%p, numBytes=%d, isText=%d)\n", eventType, (int)userData, e->data, e->numBytes, e->isText); + if (e->isText) + { + printf("text data: \"%s\"\n", e->data); +#ifdef REPORT_RESULT + if (!!strcmp((const char*)e->data, "hello on the other side")) REPORT_RESULT(-1); + passed += 1; +#endif + } + else + { + printf("binary data:"); + for(int i = 0; i < e->numBytes; ++i) + { + printf(" %02X", e->data[i]); +#ifdef REPORT_RESULT + if (e->data[i] != i) REPORT_RESULT(-2); +#endif + } + printf("\n"); + passed += 100; + + emscripten_websocket_close(e->socket, 0, 0); + emscripten_websocket_delete(e->socket); +#ifdef REPORT_RESULT + printf("%d\n", passed); + REPORT_RESULT(passed); +#endif + } + return 0; +} + +int main() +{ + if (!emscripten_websocket_is_supported()) + { + printf("WebSockets are not supported, cannot continue!\n"); + exit(1); + } + + EmscriptenWebSocketCreateAttributes attr; + emscripten_websocket_init_create_attributes(&attr); + + attr.url = "ws://localhost:8088"; + + EMSCRIPTEN_WEBSOCKET_T socket = emscripten_websocket_new(&attr); + if (socket <= 0) + { + printf("WebSocket creation failed, error code %d!\n", (EMSCRIPTEN_RESULT)socket); + exit(1); + } + + emscripten_websocket_set_onopen_callback(socket, (void*)42, WebSocketOpen); + emscripten_websocket_set_onclose_callback(socket, (void*)43, WebSocketClose); + emscripten_websocket_set_onerror_callback(socket, (void*)44, WebSocketError); + emscripten_websocket_set_onmessage_callback(socket, (void*)45, WebSocketMessage); +} diff --git a/tools/shared.py b/tools/shared.py index 1f080135ef685..e47d279617369 100644 --- a/tools/shared.py +++ b/tools/shared.py @@ -2712,7 +2712,8 @@ def path_to_system_js_libraries(library_name): 'X11': 'library_xlib.js', 'SDL': 'library_sdl.js', 'stdc++': '', - 'uuid': 'library_uuid.js' + 'uuid': 'library_uuid.js', + 'websocket': 'library_websocket.js' } library_files = [] if library_name in js_system_libraries: diff --git a/tools/system_libs.py b/tools/system_libs.py index 118a7127f1ad2..b10a392aeded2 100755 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -19,6 +19,12 @@ stdout = None stderr = None +LIBC_SOCKETS = ['socket.c', 'socketpair.c', 'shutdown.c', 'bind.c', 'connect.c', + 'listen.c', 'accept.c', 'getsockname.c', 'getpeername.c', 'send.c', + 'recv.c', 'sendto.c', 'recvfrom.c', 'sendmsg.c', 'recvmsg.c', + 'getsockopt.c', 'setsockopt.c', 'getaddrinfo.c', 'getnameinfo.c', + 'freeaddrinfo.c'] + def call_process(cmd): shared.run_process(cmd, stdout=stdout, stderr=stderr) @@ -87,6 +93,7 @@ def read_symbols(path): # XXX We also need to add libc symbols that use malloc, for example strdup. It's very rare to use just them and not # a normal malloc symbol (like free, after calling strdup), so we haven't hit this yet, but it is possible. libc_symbols = read_symbols(shared.path_from_root('system', 'lib', 'libc.symbols')) + libc_sockets_symbols = read_symbols(shared.path_from_root('system', 'lib', 'libc-sockets.symbols')) libcxx_symbols = read_symbols(shared.path_from_root('system', 'lib', 'libcxx', 'symbols')) libcxxabi_symbols = read_symbols(shared.path_from_root('system', 'lib', 'libcxxabi', 'symbols')) gl_symbols = read_symbols(shared.path_from_root('system', 'lib', 'gl.symbols')) @@ -179,14 +186,15 @@ def create_libc(libname): # individual files blacklist += [ - 'memcpy.c', 'memset.c', 'memmove.c', 'getaddrinfo.c', 'getnameinfo.c', + 'memcpy.c', 'memset.c', 'memmove.c', 'inet_addr.c', 'res_query.c', 'res_querydomain.c', 'gai_strerror.c', 'proto.c', 'gethostbyaddr.c', 'gethostbyaddr_r.c', 'gethostbyname.c', 'gethostbyname2_r.c', 'gethostbyname_r.c', 'gethostbyname2.c', 'usleep.c', 'alarm.c', 'syscall.c', '_exit.c', 'popen.c', 'getgrouplist.c', 'initgroups.c', 'wordexp.c', 'timer_create.c', - 'faccessat.c', - ] + 'faccessat.c'] + + blacklist += LIBC_SOCKETS # individual math files blacklist += [ @@ -235,6 +243,19 @@ def create_libc(libname): args += threading_flags(libname) return build_libc(libname, libc_files, args) + def create_libc_sockets(libname): + logging.debug('building libc-sockets for cache') + network_dir = shared.path_from_root('system', 'lib', 'libc', 'musl', 'src', 'network') + libc_files = [os.path.join(network_dir, x) for x in LIBC_SOCKETS] + + # Without -fno-builtin, LLVM can optimize away or convert calls to library + # functions to something else based on assumptions that they behave exactly + # like the standard library. This can cause unexpected bugs when we use our + # custom standard library. The same for other libc/libm builds. + args = ['-Os', '-fno-builtin'] + args += threading_flags(libname) + return build_libc(libname, libc_files, args) + def create_pthreads(libname): # Add pthread files. pthreads_files = files_in_path( @@ -385,6 +406,9 @@ def create_html5(libname): files += [os.path.join(src_dir, f) for f in filenames] return build_libc(libname, files, ['-Oz']) + def create_posix_proxy(libname): + return build_libc(libname, [shared.path_from_root('system', 'lib', 'websocket', 'websocket_to_posix_socket.cpp')], ['-Oz']) + def create_compiler_rt(libname): files = files_in_path( path_components=['system', 'lib', 'compiler-rt', 'lib', 'builtins'], @@ -633,6 +657,11 @@ class Dummy(object): system_libs += [Library('libgl', ext, create_gl, gl_symbols, [libc_name], False)] # noqa system_libs.append(Library(libc_name, ext, create_libc, libc_symbols, libc_deps, False)) + if shared.Settings.PROXY_POSIX_SOCKETS: + system_libs.append(Library('libposix-sockets-proxy', ext, create_posix_proxy, [], [], False)) + forced += ['libposix-sockets-proxy'] + else: + system_libs.append(Library(libc_name + '-sockets', ext, create_libc_sockets, libc_sockets_symbols, libc_deps, False)) # if building to wasm, we need more math code, since we have less builtins if shared.Settings.WASM: diff --git a/tools/websocket_to_posix_proxy/CMakeLists.txt b/tools/websocket_to_posix_proxy/CMakeLists.txt new file mode 100644 index 0000000000000..ce06da6c2145f --- /dev/null +++ b/tools/websocket_to_posix_proxy/CMakeLists.txt @@ -0,0 +1,13 @@ +cmake_minimum_required(VERSION 2.8) + +project(websocket_to_posix_proxy) + +file(GLOB sourceFiles src/*.cpp src/*.h) + +add_executable(websocket_to_posix_proxy ${sourceFiles}) + +if (WIN32) + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(/wd4200) # "nonstandard extension used: zero-sized array in struct/union" + target_link_libraries(websocket_to_posix_proxy Ws2_32.lib) +endif() diff --git a/tools/websocket_to_posix_proxy/src/main.cpp b/tools/websocket_to_posix_proxy/src/main.cpp new file mode 100644 index 0000000000000..96b84ffde50cc --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/main.cpp @@ -0,0 +1,382 @@ +#include +#include +#include +#include +#include "posix_sockets.h" +#include "threads.h" +#include +#include + +#include "sha1.h" +#include "websocket_to_posix_proxy.h" +#include "socket_registry.h" + +// #define PROXY_DEBUG + +// #define PROXY_DEEP_DEBUG + +static const unsigned char b64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +static void base64_encode(void *dst, const void *src, size_t len) // thread-safe, re-entrant +{ + assert(dst != src); + unsigned int *d = (unsigned int *)dst; + const unsigned char *s = (const unsigned char*)src; + const unsigned char *end = s + len; + while(s < end) + { + uint32_t e = *s++ << 16; + if (s < end) e |= *s++ << 8; + if (s < end) e |= *s++; + *d++ = b64[e >> 18] | (b64[(e >> 12) & 0x3F] << 8) | (b64[(e >> 6) & 0x3F] << 16) | (b64[e & 0x3F] << 24); + } + for (size_t i = 0; i < (3 - (len % 3)) % 3; i++) ((char *)d)[-1-i] = '='; +} + +#define BUFFER_SIZE 1024 +#define on_error(...) { fprintf(stderr, __VA_ARGS__); fflush(stderr); exit(1); } + +// Given a multiline string of HTTP headers, returns a pointer to the beginning of the value of given header inside the string that was passed in. +static int GetHttpHeader(const char *headers, const char *header, char *out) // thread-safe, re-entrant +{ + const char *pos = strstr(headers, header); + if (!pos) return 0; + pos += strlen(header); + const char *end = pos; + while(*end != '\r') ++end; + memcpy(out, pos, end-pos); + out[end-pos] = '\0'; + return end-pos; +} + +// Sends WebSocket handshake back to the given WebSocket connection. +void SendHandshake(int fd, const char *request) +{ + char key[128]; + GetHttpHeader(request, "Sec-WebSocket-Key: ", key); + const char webSocketGlobalGuid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + strcat(key, webSocketGlobalGuid); + + char sha1[21]; + printf("hashing key: \"%s\"\n", key); + SHA1(sha1, key, strlen(key)); + + char handshakeMsg[] = + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: 0000000000000000000000000000\r\n" + "\r\n"; + + base64_encode(strstr(handshakeMsg, "Sec-WebSocket-Accept: ") + strlen("Sec-WebSocket-Accept: "), sha1, 20); + + int err = send(fd, handshakeMsg, strlen(handshakeMsg), 0); + if (err < 0) on_error("Client write failed\n"); + printf("Sent handshake:\n%s\n", handshakeMsg); +} + +// Validates if the given, possibly partially received WebSocket message has enough bytes to contain a full WebSocket header. +static bool WebSocketHasFullHeader(uint8_t *data, uint64_t obtainedNumBytes) +{ + if (obtainedNumBytes < 2) return false; + uint64_t expectedNumBytes = 2; + WebSocketMessageHeader *header = (WebSocketMessageHeader *)data; + if (header->mask) expectedNumBytes += 4; + switch(header->payloadLength) + { + case 127: return expectedNumBytes += 8; break; + case 126: return expectedNumBytes += 2; break; + default: break; + } + return obtainedNumBytes >= expectedNumBytes; +} + +// Computes the total number of bytes that the given WebSocket message will take up. +uint64_t WebSocketFullMessageSize(uint8_t *data, uint64_t obtainedNumBytes) +{ + assert(WebSocketHasFullHeader(data, obtainedNumBytes)); + + uint64_t expectedNumBytes = 2; + WebSocketMessageHeader *header = (WebSocketMessageHeader *)data; + if (header->mask) expectedNumBytes += 4; + switch(header->payloadLength) + { + case 127: return expectedNumBytes += 8 + ntoh64(*(uint64_t*)(data+2)); break; + case 126: return expectedNumBytes += 2 + ntohs(*(uint16_t*)(data+2)); break; + default: expectedNumBytes += header->payloadLength; break; + } + return expectedNumBytes; +} + +// Tests the structure integrity of the websocket message length. +bool WebSocketValidateMessageSize(uint8_t *data, uint64_t obtainedNumBytes) +{ + uint64_t expectedNumBytes = WebSocketFullMessageSize(data, obtainedNumBytes); + + if (expectedNumBytes != obtainedNumBytes) + { + printf("Corrupt WebSocket message size! (got %llu bytes, expected %llu bytes)\n", obtainedNumBytes, expectedNumBytes); + printf("Received data:"); + for(size_t i = 0; i < obtainedNumBytes; ++i) + printf(" %02X", data[i]); + printf("\n"); + } + return expectedNumBytes == obtainedNumBytes; +} + +uint64_t WebSocketMessagePayloadLength(uint8_t *data, uint64_t numBytes) +{ + WebSocketMessageHeader *header = (WebSocketMessageHeader *)data; + switch(header->payloadLength) + { + case 127: return ntoh64(*(uint64_t*)(data+2)); + case 126: return ntohs(*(uint16_t*)(data+2)); + default: return header->payloadLength; + } +} + +uint32_t WebSocketMessageMaskingKey(uint8_t *data, uint64_t numBytes) +{ + WebSocketMessageHeader *header = (WebSocketMessageHeader *)data; + if (!header->mask) return 0; + switch(header->payloadLength) + { + case 127: return *(uint32_t*)(data+10); + case 126: return *(uint32_t*)(data+4); + default: return *(uint32_t*)(data+2); + } +} + +uint8_t *WebSocketMessageData(uint8_t *data, uint64_t numBytes) +{ + WebSocketMessageHeader *header = (WebSocketMessageHeader *)data; + data += 2; // Two bytes of fixed size header + if (header->mask) data += 4; // If there is a masking key present in the header, that takes up 4 bytes + switch(header->payloadLength) + { + case 127: return data + 8; // 64-bit length + case 126: return data + 2; // 16-bit length + default: return data; // 7-bit length that was embedded in fixed size header. + } +} + +void CloseWebSocket(int client_fd) +{ + printf("Closing WebSocket connection %d\n", client_fd); + CloseAllSocketsByConnection(client_fd); + shutdown(client_fd, SHUTDOWN_BIDIRECTIONAL); + CLOSE_SOCKET(client_fd); +} + +const char *WebSocketOpcodeToString(int opcode) +{ + static const char *opcodes[] = { "continuation frame (0x0)", "text frame (0x1)", "binary frame (0x2)", "reserved(0x3)", "reserved(0x4)", "reserved(0x5)", + "reserved(0x6)", "reserved(0x7)", "connection close (0x8)", "ping (0x9)", "pong (0xA)", "reserved(0xB)", "reserved(0xC)", "reserved(0xD)", "reserved(0xE)", "reserved(0xF)" }; + return opcodes[opcode]; +} + +void DumpWebSocketMessage(uint8_t *data, uint64_t numBytes) +{ + bool goodMessageSize = WebSocketValidateMessageSize(data, numBytes); + if (!goodMessageSize) + return; + + WebSocketMessageHeader *header = (WebSocketMessageHeader *)data; + uint64_t payloadLength = WebSocketMessagePayloadLength(data, numBytes); + uint8_t *payload = WebSocketMessageData(data, numBytes); + + printf("Received: FIN: %d, opcode: %s, mask: 0x%08X, payload length: %llu bytes, unmasked payload:", header->fin, WebSocketOpcodeToString(header->opcode), + WebSocketMessageMaskingKey(data, numBytes), payloadLength); + for(uint64_t i = 0; i < payloadLength; ++i) + { + if (i%16 == 0) printf("\n"); + if (i%8==0) printf(" "); + printf(" %02X", payload[i]); + if (i >= 63 && payloadLength > 64) + { + printf("\n ... (%llu more bytes)", payloadLength-i); + break; + } + } + printf("\n"); +} + +// connection thread manages a single active proxy connection. +THREAD_RETURN_T connection_thread(void *arg) +{ + int client_fd = (int)(uintptr_t)arg; + printf("Established new proxy connection handler thread for incoming connection, at fd=%d\n", client_fd); // TODO: print out getpeername()+getsockname() for more info + + // Waiting for connection upgrade handshake + char buf[BUFFER_SIZE]; + int read = recv(client_fd, buf, BUFFER_SIZE, 0); + + if (!read) + { + CloseWebSocket(client_fd); + EXIT_THREAD(0); + } + + if (read < 0) + { + fprintf(stderr, "Client read failed\n"); + CloseWebSocket(client_fd); + EXIT_THREAD(0); + } + +#ifdef PROXY_DEEP_DEBUG + printf("Received:"); + for(int i = 0; i < read; ++i) + { + printf(" %02X", buf[i]); + } + printf("\n"); +// printf("In text:\n%s\n", buf); +#endif + SendHandshake(client_fd, buf); + +#ifdef PROXY_DEEP_DEBUG + printf("Handshake received, entering message loop:\n"); +#endif + + std::vector fragmentData; + + bool connectionAlive = true; + while (connectionAlive) + { + int read = recv(client_fd, buf, BUFFER_SIZE, 0); + + if (!read) break; // done reading + if (read < 0) + { + fprintf(stderr, "Client read failed\n"); + EXIT_THREAD(0); + } + +#ifdef PROXY_DEEP_DEBUG + printf("Received:"); + for(int i = 0; i < read; ++i) + { + printf(" %02X", ((unsigned char*)buf)[i]); + } + printf("\n"); +// printf("In text:\n%s\n", buf); +#endif + +#ifdef PROXY_DEEP_DEBUG + printf("Have %d+%d==%d bytes now in queue\n", (int)fragmentData.size(), (int)read, (int)(fragmentData.size()+read)); +#endif + fragmentData.insert(fragmentData.end(), buf, buf+read); + + // Process received fragments until there is not enough data for a full message + while(!fragmentData.empty()) + { + bool hasFullHeader = WebSocketHasFullHeader(&fragmentData[0], fragmentData.size()); + if (!hasFullHeader) + { +#ifdef PROXY_DEEP_DEBUG + printf("(not enough for a full WebSocket header)\n"); +#endif + break; + } + uint64_t neededBytes = WebSocketFullMessageSize(&fragmentData[0], fragmentData.size()); + if (fragmentData.size() < neededBytes) + { +#ifdef PROXY_DEEP_DEBUG + printf("(not enough for a full WebSocket message, needed %d bytes)\n", (int)neededBytes); +#endif + break; + } + + WebSocketMessageHeader *header = (WebSocketMessageHeader *)&fragmentData[0]; + uint64_t payloadLength = WebSocketMessagePayloadLength(&fragmentData[0], neededBytes); + uint8_t *payload = WebSocketMessageData(&fragmentData[0], neededBytes); + + // Unmask payload + if (header->mask) + WebSocketMessageUnmaskPayload(payload, payloadLength, WebSocketMessageMaskingKey(&fragmentData[0], neededBytes)); + +#ifdef PROXY_DEEP_DEBUG + DumpWebSocketMessage(&fragmentData[0], neededBytes); +#endif + + switch(header->opcode) + { + case 0x02: /*binary message*/ ProcessWebSocketMessage(client_fd, payload, payloadLength); break; + case 0x08: connectionAlive = false; break; + default: + fprintf(stderr, "Unknown WebSocket opcode received %x!\n", header->opcode); + connectionAlive = false; // Kill connection + break; + } + + fragmentData.erase(fragmentData.begin(), fragmentData.begin() + (ptrdiff_t)neededBytes); +#ifdef PROXY_DEEP_DEBUG + printf("Cleared used bytes, got %d left in fragment queue.\n", (int)fragmentData.size()); +#endif + } + } + printf("Proxy connection closed\n"); + CloseWebSocket(client_fd); + EXIT_THREAD(0); +} + +int main(int argc, char *argv[]) +{ + if (argc < 2) on_error("websocket_to_posix_proxy creates a bridge that allows WebSocket connections on a web page to proxy out to perform TCP/UDP connections.\nUsage: %s [port]\n", argv[0]); + +#ifdef _WIN32 + WSADATA wsaData; + int failed = WSAStartup(MAKEWORD(2,2), &wsaData); + if (failed) + { + printf("WSAStartup failed: %d\n", failed); + return 1; + } +#else + signal(SIGPIPE, SIG_IGN); +#endif + + const int port = atoi(argv[1]); + int server_fd = socket(AF_INET, SOCK_STREAM, 0); + if (server_fd < 0) on_error("Could not create socket\n"); + + struct sockaddr_in server; + server.sin_family = AF_INET; + server.sin_port = htons(port); + server.sin_addr.s_addr = htonl(INADDR_ANY); + + int opt_val = 1; + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, (SETSOCKOPT_PTR_TYPE)&opt_val, sizeof opt_val); + + int err = bind(server_fd, (struct sockaddr *) &server, sizeof(server)); + if (err < 0) on_error("Could not bind socket\n"); + + err = listen(server_fd, 128); + if (err < 0) on_error("Could not listen on socket\n"); + + printf("websocket_to_posix_proxy server is now listening for WebSocket connections to ws://localhost:%d/\n", port); + + while (1) + { + int client_fd = accept(server_fd, 0, 0); + if (client_fd < 0) + { + fprintf(stderr, "Could not establish new incoming proxy connection\n"); + continue; // Do not quit here, but keep serving any existing proxy connections. + } + + THREAD_T connection; + CREATE_THREAD_RETURN_T ret = CREATE_THREAD(connection, connection_thread, (void*)(uintptr_t)client_fd); + if (!CREATE_THREAD_SUCCEEDED(ret)) + { + fprintf(stderr, "Failed to create a connection handler thread for incoming proxy connection!\n"); + continue; // Do not quit here, but keep program alive to manage other existing proxy connections. + } + } + +#ifdef _WIN32 + WSACleanup(); +#endif + + return 0; +} diff --git a/tools/websocket_to_posix_proxy/src/posix_sockets.h b/tools/websocket_to_posix_proxy/src/posix_sockets.h new file mode 100644 index 0000000000000..78b433d9f5197 --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/posix_sockets.h @@ -0,0 +1,52 @@ +#pragma once + +#include + +#if defined(__APPLE__) || defined(____linux__) + +#include +#include +#include +#include +#include + +#define SHUTDOWN_READ SHUT_RD +#define SHUTDOWN_WRITE SHUT_WR +#define SHUTDOWN_BIDIRECTIONAL SHUT_RDWR +#define SETSOCKOPT_PTR_TYPE const int* +#define SEND_RET_TYPE ssize_t +#define SEND_FORMATTING_SPECIFIER "%ld" +#define CLOSE_SOCKET(x) close(x) + +#define GET_SOCKET_ERROR() (errno) + +#define PRINT_SOCKET_ERROR(errorCode) do { \ + printf("Call failed! errno: %s(%d)\n", strerror(errorCode), errorCode); \ + } while(0) + +#elif defined(_MSC_VER) + +#include +#include + +#define SHUTDOWN_READ SD_RECEIVE +#define SHUTDOWN_WRITE SD_SEND +#define SHUTDOWN_BIDIRECTIONAL SD_BOTH +#define SETSOCKOPT_PTR_TYPE const char* +#define SEND_RET_TYPE int +#define SEND_FORMATTING_SPECIFIER "%d" +#define CLOSE_SOCKET(x) closesocket(x) + +#define GET_SOCKET_ERROR() (WSAGetLastError()) + +static inline void PRINT_SOCKET_ERROR(int errorCode) +{ + void *lpMsgBuf = 0; + HRESULT hresult = HRESULT_FROM_WIN32(errorCode); + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, + 0, hresult, 0 /*Default language*/, (LPTSTR)&lpMsgBuf, 0, 0); + printf("Call failed! WSAGetLastError: %s(%d)\n", (char*)lpMsgBuf, errorCode); + LocalFree(lpMsgBuf); +} + +#endif diff --git a/tools/websocket_to_posix_proxy/src/sha1.cpp b/tools/websocket_to_posix_proxy/src/sha1.cpp new file mode 100644 index 0000000000000..c2c529f529d75 --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/sha1.cpp @@ -0,0 +1,295 @@ +/* +SHA-1 in C +By Steve Reid +100% Public Domain + +Test Vectors (from FIPS PUB 180-1) +"abc" + A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D +"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq" + 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1 +A million repetitions of "a" + 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F +*/ + +/* #define LITTLE_ENDIAN * This should be #define'd already, if true. */ +/* #define SHA1HANDSOFF * Copies data before messing with it. */ + +#define SHA1HANDSOFF + +#include +#include + +/* for uint32_t */ +#include + +#include "sha1.h" + + +#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + +/* blk0() and blk() perform the initial expand. */ +/* I got the idea of expanding during the round function from SSLeay */ +#if BYTE_ORDER == LITTLE_ENDIAN +#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \ + |(rol(block->l[i],8)&0x00FF00FF)) +#elif BYTE_ORDER == BIG_ENDIAN +#define blk0(i) block->l[i] +#else +#error "Endianness not defined!" +#endif +#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \ + ^block->l[(i+2)&15]^block->l[i&15],1)) + +/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */ +#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30); +#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30); +#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30); +#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30); + + +/* Hash a single 512-bit block. This is the core of the algorithm. */ + +void SHA1Transform( + uint32_t state[5], + const unsigned char buffer[64] +) +{ + uint32_t a, b, c, d, e; + + typedef union + { + unsigned char c[64]; + uint32_t l[16]; + } CHAR64LONG16; + +#ifdef SHA1HANDSOFF + CHAR64LONG16 block[1]; /* use array to appear as a pointer */ + + memcpy(block, buffer, 64); +#else + /* The following had better never be used because it causes the + * pointer-to-const buffer to be cast into a pointer to non-const. + * And the result is written through. I threw a "const" in, hoping + * this will cause a diagnostic. + */ + CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer; +#endif + /* Copy context->state[] to working vars */ + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + /* 4 rounds of 20 operations each. Loop unrolled. */ + R0(a, b, c, d, e, 0); + R0(e, a, b, c, d, 1); + R0(d, e, a, b, c, 2); + R0(c, d, e, a, b, 3); + R0(b, c, d, e, a, 4); + R0(a, b, c, d, e, 5); + R0(e, a, b, c, d, 6); + R0(d, e, a, b, c, 7); + R0(c, d, e, a, b, 8); + R0(b, c, d, e, a, 9); + R0(a, b, c, d, e, 10); + R0(e, a, b, c, d, 11); + R0(d, e, a, b, c, 12); + R0(c, d, e, a, b, 13); + R0(b, c, d, e, a, 14); + R0(a, b, c, d, e, 15); + R1(e, a, b, c, d, 16); + R1(d, e, a, b, c, 17); + R1(c, d, e, a, b, 18); + R1(b, c, d, e, a, 19); + R2(a, b, c, d, e, 20); + R2(e, a, b, c, d, 21); + R2(d, e, a, b, c, 22); + R2(c, d, e, a, b, 23); + R2(b, c, d, e, a, 24); + R2(a, b, c, d, e, 25); + R2(e, a, b, c, d, 26); + R2(d, e, a, b, c, 27); + R2(c, d, e, a, b, 28); + R2(b, c, d, e, a, 29); + R2(a, b, c, d, e, 30); + R2(e, a, b, c, d, 31); + R2(d, e, a, b, c, 32); + R2(c, d, e, a, b, 33); + R2(b, c, d, e, a, 34); + R2(a, b, c, d, e, 35); + R2(e, a, b, c, d, 36); + R2(d, e, a, b, c, 37); + R2(c, d, e, a, b, 38); + R2(b, c, d, e, a, 39); + R3(a, b, c, d, e, 40); + R3(e, a, b, c, d, 41); + R3(d, e, a, b, c, 42); + R3(c, d, e, a, b, 43); + R3(b, c, d, e, a, 44); + R3(a, b, c, d, e, 45); + R3(e, a, b, c, d, 46); + R3(d, e, a, b, c, 47); + R3(c, d, e, a, b, 48); + R3(b, c, d, e, a, 49); + R3(a, b, c, d, e, 50); + R3(e, a, b, c, d, 51); + R3(d, e, a, b, c, 52); + R3(c, d, e, a, b, 53); + R3(b, c, d, e, a, 54); + R3(a, b, c, d, e, 55); + R3(e, a, b, c, d, 56); + R3(d, e, a, b, c, 57); + R3(c, d, e, a, b, 58); + R3(b, c, d, e, a, 59); + R4(a, b, c, d, e, 60); + R4(e, a, b, c, d, 61); + R4(d, e, a, b, c, 62); + R4(c, d, e, a, b, 63); + R4(b, c, d, e, a, 64); + R4(a, b, c, d, e, 65); + R4(e, a, b, c, d, 66); + R4(d, e, a, b, c, 67); + R4(c, d, e, a, b, 68); + R4(b, c, d, e, a, 69); + R4(a, b, c, d, e, 70); + R4(e, a, b, c, d, 71); + R4(d, e, a, b, c, 72); + R4(c, d, e, a, b, 73); + R4(b, c, d, e, a, 74); + R4(a, b, c, d, e, 75); + R4(e, a, b, c, d, 76); + R4(d, e, a, b, c, 77); + R4(c, d, e, a, b, 78); + R4(b, c, d, e, a, 79); + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + /* Wipe variables */ + a = b = c = d = e = 0; +#ifdef SHA1HANDSOFF + memset(block, '\0', sizeof(block)); +#endif +} + + +/* SHA1Init - Initialize new context */ + +void SHA1Init( + SHA1_CTX * context +) +{ + /* SHA1 initialization constants */ + context->state[0] = 0x67452301; + context->state[1] = 0xEFCDAB89; + context->state[2] = 0x98BADCFE; + context->state[3] = 0x10325476; + context->state[4] = 0xC3D2E1F0; + context->count[0] = context->count[1] = 0; +} + + +/* Run your data through this. */ + +void SHA1Update( + SHA1_CTX * context, + const unsigned char *data, + uint32_t len +) +{ + uint32_t i; + + uint32_t j; + + j = context->count[0]; + if ((context->count[0] += len << 3) < j) + context->count[1]++; + context->count[1] += (len >> 29); + j = (j >> 3) & 63; + if ((j + len) > 63) + { + memcpy(&context->buffer[j], data, (i = 64 - j)); + SHA1Transform(context->state, context->buffer); + for (; i + 63 < len; i += 64) + { + SHA1Transform(context->state, &data[i]); + } + j = 0; + } + else + i = 0; + memcpy(&context->buffer[j], &data[i], len - i); +} + + +/* Add padding and return the message digest. */ + +void SHA1Final( + unsigned char digest[20], + SHA1_CTX * context +) +{ + unsigned i; + + unsigned char finalcount[8]; + + unsigned char c; + +#if 0 /* untested "improvement" by DHR */ + /* Convert context->count to a sequence of bytes + * in finalcount. Second element first, but + * big-endian order within element. + * But we do it all backwards. + */ + unsigned char *fcp = &finalcount[8]; + + for (i = 0; i < 2; i++) + { + uint32_t t = context->count[i]; + + int j; + + for (j = 0; j < 4; t >>= 8, j++) + *--fcp = (unsigned char) t} +#else + for (i = 0; i < 8; i++) + { + finalcount[i] = (unsigned char) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */ + } +#endif + c = 0200; + SHA1Update(context, &c, 1); + while ((context->count[0] & 504) != 448) + { + c = 0000; + SHA1Update(context, &c, 1); + } + SHA1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++) + { + digest[i] = (unsigned char) + ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255); + } + /* Wipe variables */ + memset(context, '\0', sizeof(*context)); + memset(&finalcount, '\0', sizeof(finalcount)); +} + +void SHA1( + char *hash_out, + const char *str, + int len) +{ + SHA1_CTX ctx; + int ii; + + SHA1Init(&ctx); + for (ii=0; ii + 100% Public Domain + */ + +#include "stdint.h" + +typedef struct +{ + uint32_t state[5]; + uint32_t count[2]; + unsigned char buffer[64]; +} SHA1_CTX; + +void SHA1Transform( + uint32_t state[5], + const unsigned char buffer[64] + ); + +void SHA1Init( + SHA1_CTX * context + ); + +void SHA1Update( + SHA1_CTX * context, + const unsigned char *data, + uint32_t len + ); + +void SHA1Final( + unsigned char digest[20], + SHA1_CTX * context + ); + +void SHA1( + char *hash_out, + const char *str, + int len); + +#endif /* SHA1_H */ diff --git a/tools/websocket_to_posix_proxy/src/socket_registry.cpp b/tools/websocket_to_posix_proxy/src/socket_registry.cpp new file mode 100644 index 0000000000000..f9d564d2be7ec --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/socket_registry.cpp @@ -0,0 +1,50 @@ +#include "socket_registry.h" + +#include +#include +#include + +namespace +{ + std::map > socketsPerProxyConnection; +} + +void TrackSocketUsedByConnection(int proxyConnection, int usedSocket) +{ + if (usedSocket == 0) return; + if (IsSocketPartOfConnection(proxyConnection, usedSocket)) + return; + socketsPerProxyConnection[proxyConnection].push_back(usedSocket); +} + +void CloseSocketByConnection(int proxyConnection, int usedSocket) +{ + if (!IsSocketPartOfConnection(proxyConnection, usedSocket)) + return; + printf("Closing socket fd %d used by proxy connection %d\n", usedSocket, proxyConnection); + CLOSE_SOCKET(usedSocket); + std::vector &sockets = socketsPerProxyConnection[proxyConnection]; + sockets.erase(std::remove(sockets.begin(), sockets.end(), usedSocket), sockets.end()); +} + +void CloseAllSocketsByConnection(int proxyConnection) +{ + std::vector &sockets = socketsPerProxyConnection[proxyConnection]; + for(size_t i = 0; i < sockets.size(); ++i) + { + printf("Closing socket fd %d used by proxy connection %d.\n", sockets[i], proxyConnection); + shutdown(sockets[i], SHUTDOWN_BIDIRECTIONAL); + CLOSE_SOCKET(sockets[i]); + } + socketsPerProxyConnection.erase(proxyConnection); +} + +bool IsSocketPartOfConnection(int proxyConnection, int usedSocket) +{ + if (usedSocket == 0) return true; // Allow all proxy connections to access "socket 0" when/if they need to refer to socket that does not exist. + if (socketsPerProxyConnection.find(proxyConnection) == socketsPerProxyConnection.end()) + return false; + + std::vector &sockets = socketsPerProxyConnection[proxyConnection]; + return std::find(sockets.begin(), sockets.end(), usedSocket) != sockets.end(); +} diff --git a/tools/websocket_to_posix_proxy/src/socket_registry.h b/tools/websocket_to_posix_proxy/src/socket_registry.h new file mode 100644 index 0000000000000..ae29104d085a9 --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/socket_registry.h @@ -0,0 +1,21 @@ +#pragma once + +#include "posix_sockets.h" + +// Socket Registry remembers all the sockets created by incoming proxy connections, so that those sockets can be properly +// shut down when an incoming proxy connection disconnects. + +// Tracks that the given socket is part of the specified proxy connection. When proxyConnection disconnects, all sockets +// used by it are shut down. +void TrackSocketUsedByConnection(int proxyConnection, int usedSocket); + +// Untracks the given socket - the proxy connection has shut it down. +void CloseSocketByConnection(int proxyConnection, int usedSocket); + +// Given proxy connection has disconnected - shut down all the sockets it had created. +void CloseAllSocketsByConnection(int proxyConnection); + +// Returns if the given socket is known to be owned by the specified proxy connection. +// This is used to gate socket connections so that two proxy connections can not access +// each others' sockets. +bool IsSocketPartOfConnection(int proxyConnection, int usedSocket); diff --git a/tools/websocket_to_posix_proxy/src/threads.h b/tools/websocket_to_posix_proxy/src/threads.h new file mode 100644 index 0000000000000..52bba0e8f2bc2 --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/threads.h @@ -0,0 +1,39 @@ +#pragma once + +#if defined(__unix__) || defined(__APPLE__) || defined(__linux__) +#include +#define THREAD_T pthread_t +#define CREATE_THREAD(threadPtr, threadFunc, threadArg) pthread_create(&threadPtr, 0, threadFunc, threadArg) +#define CREATE_THREAD_RETURN_T int +#define CREATE_THREAD_SUCCEEDED(x) (x == 0) +#define EXIT_THREAD(code) pthread_exit((void*)(uintptr_t)code) +#define THREAD_RETURN_T void* +#define MUTEX_T pthread_mutex_t +inline MUTEX_T CREATE_MUTEX() +{ + pthread_mutex_t m; + pthread_mutex_init(&m, 0); + return m; +} +#define LOCK_MUTEX(m) pthread_mutex_lock(m) +#define UNLOCK_MUTEX(m) pthread_mutex_unlock(m) +#endif + +#if defined(_WIN32) +#include +#define THREAD_T HANDLE +#define CREATE_THREAD(threadPtr, threadFunc, threadArg) threadPtr = CreateThread(0, 0, threadFunc, threadArg, 0, 0) +#define CREATE_THREAD_RETURN_T HANDLE +#define CREATE_THREAD_SUCCEEDED(x) (x != 0) +#define EXIT_THREAD(code) ExitThread((DWORD)code) +#define THREAD_RETURN_T DWORD WINAPI +#define MUTEX_T CRITICAL_SECTION +inline MUTEX_T CREATE_MUTEX() +{ + CRITICAL_SECTION cs; + InitializeCriticalSectionAndSpinCount(&cs, 0x00000400); + return cs; +} +#define LOCK_MUTEX(m) EnterCriticalSection(m) +#define UNLOCK_MUTEX(m) LeaveCriticalSection(m) +#endif diff --git a/tools/websocket_to_posix_proxy/src/websocket_to_posix_proxy.cpp b/tools/websocket_to_posix_proxy/src/websocket_to_posix_proxy.cpp new file mode 100644 index 0000000000000..c436734d62a9b --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/websocket_to_posix_proxy.cpp @@ -0,0 +1,1672 @@ +#include +#include +#include +#include "posix_sockets.h" +#include "threads.h" +#include +#include +#include + +#include "websocket_to_posix_proxy.h" +#include "socket_registry.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)) +#define MAX(a,b) (((a)>(b))?(a):(b)) + +uint64_t ntoh64(uint64_t x) // thread-safe, re-entrant +{ + return ntohl(x>>32) | ((uint64_t)ntohl(x&0xFFFFFFFFu) << 32); +} + +#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 + +struct SocketCallHeader +{ + int callId; + int function; +}; + +static char buf_temp_str[2048] = {}; + +static char *BufferToString(const void *buf, size_t len) // not thread-safe, but only used for debug prints, so expected not to cause trouble +{ + uint8_t *b = (uint8_t *)buf; + if (!b) + { + sprintf(buf_temp_str, "(null ptr) (%d bytes)", (int)len); + return buf_temp_str; + } + + for(size_t i = 0; i < len && i*3 + 64 < sizeof(buf_temp_str); ++i) + { + sprintf(buf_temp_str + i*3, "%02X ", b[i]); + } + sprintf(buf_temp_str + len*3, " (%d bytes)", (int)len); + return buf_temp_str; +} + +void WebSocketMessageUnmaskPayload(uint8_t *payload, uint64_t payloadLength, uint32_t maskingKey) // thread-safe, re-entrant +{ + uint8_t maskingKey8[4]; + memcpy(maskingKey8, &maskingKey, 4); + uint32_t *data_u32 = (uint32_t *)payload; + uint32_t *end_u32 = (uint32_t *)((uintptr_t)(payload + (payloadLength & ~3u))); + + while(data_u32 < end_u32) + *data_u32++ ^= maskingKey; + + uint8_t *end = payload + payloadLength; + uint8_t *data = (uint8_t *)data_u32; + while(data < end) + { + *data ^= maskingKey8[(data-payload) % 4]; + ++data; + } +} + +// Technically only would need one lock per connection, but this is now one lock per all connections, which would be +// slightly inefficient if we were handling multiple proxied connections at the same time. (currently that is a rare +// use case, expected to only be proxying one connection at a time - if this proxy bridge is expected to be used +// for hundreds of connections simultaneously, this mutex should be refactored to be per-connection) +static MUTEX_T webSocketLock = CREATE_MUTEX(); + +void SendWebSocketMessage(int client_fd, void *buf, uint64_t numBytes) +{ + LOCK_MUTEX(&webSocketLock); + uint8_t headerData[sizeof(WebSocketMessageHeader) + 8/*possible extended length*/] = {}; + WebSocketMessageHeader *header = (WebSocketMessageHeader *)headerData; + header->opcode = 0x02; + header->fin = 1; + int headerBytes = 2; + + if (numBytes < 126) + header->payloadLength = numBytes; + else if (numBytes <= 65535) + { + header->payloadLength = 126; + *(uint16_t*)(headerData+headerBytes) = htons((unsigned short)numBytes); + headerBytes += 2; + } + else + { + header->payloadLength = 127; + *(uint64_t*)(headerData+headerBytes) = hton64(numBytes); + headerBytes += 8; + } + +#ifdef POSIX_SOCKET_DEEP_DEBUG + printf("Sending %llu bytes message (%llu bytes of payload) to WebSocket\n", headerBytes + numBytes, numBytes); + + printf("Header:"); + for(int i = 0; i < headerBytes; ++i) + printf(" %02X", headerData[i]); + + printf("\nPayload:"); + for(int i = 0; i < numBytes; ++i) + printf(" %02X", ((unsigned char*)buf)[i]); + printf("\n"); +#endif + + send(client_fd, (const char*)headerData, headerBytes, 0); // header + send(client_fd, (const char*)buf, (int)numBytes, 0); // payload + UNLOCK_MUTEX(&webSocketLock); +} + +#define MUSL_PF_UNSPEC 0 +#define MUSL_PF_LOCAL 1 +#define MUSL_PF_UNIX PF_LOCAL +#define MUSL_PF_FILE PF_LOCAL +#define MUSL_PF_INET 2 +#define MUSL_PF_AX25 3 +#define MUSL_PF_IPX 4 +#define MUSL_PF_APPLETALK 5 +#define MUSL_PF_NETROM 6 +#define MUSL_PF_BRIDGE 7 +#define MUSL_PF_ATMPVC 8 +#define MUSL_PF_X25 9 +#define MUSL_PF_INET6 10 +#define MUSL_PF_ROSE 11 +#define MUSL_PF_DECnet 12 +#define MUSL_PF_NETBEUI 13 +#define MUSL_PF_SECURITY 14 +#define MUSL_PF_KEY 15 +#define MUSL_PF_NETLINK 16 +#define MUSL_PF_ROUTE PF_NETLINK +#define MUSL_PF_PACKET 17 +#define MUSL_PF_ASH 18 +#define MUSL_PF_ECONET 19 +#define MUSL_PF_ATMSVC 20 +#define MUSL_PF_RDS 21 +#define MUSL_PF_SNA 22 +#define MUSL_PF_IRDA 23 +#define MUSL_PF_PPPOX 24 +#define MUSL_PF_WANPIPE 25 +#define MUSL_PF_LLC 26 +#define MUSL_PF_IB 27 +#define MUSL_PF_MPLS 28 +#define MUSL_PF_CAN 29 +#define MUSL_PF_TIPC 30 +#define MUSL_PF_BLUETOOTH 31 +#define MUSL_PF_IUCV 32 +#define MUSL_PF_RXRPC 33 +#define MUSL_PF_ISDN 34 +#define MUSL_PF_PHONET 35 +#define MUSL_PF_IEEE802154 36 +#define MUSL_PF_CAIF 37 +#define MUSL_PF_ALG 38 +#define MUSL_PF_NFC 39 +#define MUSL_PF_VSOCK 40 +#define MUSL_PF_KCM 41 +#define MUSL_PF_MAX 42 + +#define MUSL_AF_UNSPEC MUSL_PF_UNSPEC +#define MUSL_AF_LOCAL MUSL_PF_LOCAL +#define MUSL_AF_UNIX MUSL_AF_LOCAL +#define MUSL_AF_FILE MUSL_AF_LOCAL +#define MUSL_AF_INET MUSL_PF_INET +#define MUSL_AF_AX25 MUSL_PF_AX25 +#define MUSL_AF_IPX MUSL_PF_IPX +#define MUSL_AF_APPLETALK MUSL_PF_APPLETALK +#define MUSL_AF_NETROM MUSL_PF_NETROM +#define MUSL_AF_BRIDGE MUSL_PF_BRIDGE +#define MUSL_AF_ATMPVC MUSL_PF_ATMPVC +#define MUSL_AF_X25 MUSL_PF_X25 +#define MUSL_AF_INET6 MUSL_PF_INET6 +#define MUSL_AF_ROSE MUSL_PF_ROSE +#define MUSL_AF_DECnet MUSL_PF_DECnet +#define MUSL_AF_NETBEUI MUSL_PF_NETBEUI +#define MUSL_AF_SECURITY MUSL_PF_SECURITY +#define MUSL_AF_KEY MUSL_PF_KEY +#define MUSL_AF_NETLINK MUSL_PF_NETLINK +#define MUSL_AF_ROUTE MUSL_PF_ROUTE +#define MUSL_AF_PACKET MUSL_PF_PACKET +#define MUSL_AF_ASH MUSL_PF_ASH +#define MUSL_AF_ECONET MUSL_PF_ECONET +#define MUSL_AF_ATMSVC MUSL_PF_ATMSVC +#define MUSL_AF_RDS MUSL_PF_RDS +#define MUSL_AF_SNA MUSL_PF_SNA +#define MUSL_AF_IRDA MUSL_PF_IRDA +#define MUSL_AF_PPPOX MUSL_PF_PPPOX +#define MUSL_AF_WANPIPE MUSL_PF_WANPIPE +#define MUSL_AF_LLC MUSL_PF_LLC +#define MUSL_AF_IB MUSL_PF_IB +#define MUSL_AF_MPLS MUSL_PF_MPLS +#define MUSL_AF_CAN MUSL_PF_CAN +#define MUSL_AF_TIPC MUSL_PF_TIPC +#define MUSL_AF_BLUETOOTH MUSL_PF_BLUETOOTH +#define MUSL_AF_IUCV MUSL_PF_IUCV +#define MUSL_AF_RXRPC MUSL_PF_RXRPC +#define MUSL_AF_ISDN MUSL_PF_ISDN +#define MUSL_AF_PHONET MUSL_PF_PHONET +#define MUSL_AF_IEEE802154 MUSL_PF_IEEE802154 +#define MUSL_AF_CAIF MUSL_PF_CAIF +#define MUSL_AF_ALG MUSL_PF_ALG +#define MUSL_AF_NFC MUSL_PF_NFC +#define MUSL_AF_VSOCK MUSL_PF_VSOCK +#define MUSL_AF_KCM MUSL_PF_KCM +#define MUSL_AF_MAX MUSL_PF_MAX + +static int Translate_Socket_Domain(int domain) +{ + switch(domain) + { +// case MUSL_PF_UNSPEC: return PF_UNSPEC; +// case MUSL_PF_LOCAL: return PF_LOCAL; +// case MUSL_PF_UNIX: return PF_UNIX; +// case MUSL_PF_FILE: return PF_FILE; +// case MUSL_PF_INET: return PF_INET; +// case MUSL_PF_AX25: return PF_AX25; +// case MUSL_PF_IPX: return PF_IPX; +// case MUSL_PF_APPLETALK: return PF_APPLETALK; +// case MUSL_PF_NETROM: return PF_NETROM; +// case MUSL_PF_BRIDGE: return PF_BRIDGE; +// case MUSL_PF_ATMPVC: return PF_ATMPVC; +// case MUSL_PF_X25: return PF_X25; +// case MUSL_PF_INET6: return PF_INET6; +// case MUSL_PF_ROSE: return PF_ROSE; +// case MUSL_PF_DECnet: return PF_DECnet; +// case MUSL_PF_NETBEUI: return PF_NETBEUI; +// case MUSL_PF_SECURITY: return PF_SECURITY; +// case MUSL_PF_KEY: return PF_KEY; +// case MUSL_PF_NETLINK: return PF_NETLINK; +// case MUSL_PF_ROUTE: return PF_ROUTE; +// case MUSL_PF_PACKET: return PF_PACKET; +// case MUSL_PF_ASH: return PF_ASH; +// case MUSL_PF_ECONET: return PF_ECONET; +// case MUSL_PF_ATMSVC: return PF_ATMSVC; +// case MUSL_PF_RDS: return PF_RDS; +// case MUSL_PF_SNA: return PF_SNA; +// case MUSL_PF_IRDA: return PF_IRDA; +// case MUSL_PF_PPPOX: return PF_PPPOX; +// case MUSL_PF_WANPIPE: return PF_WANPIPE; +// case MUSL_PF_LLC: return PF_LLC; +// case MUSL_PF_IB: return PF_IB; +// case MUSL_PF_MPLS: return PF_MPLS; +// case MUSL_PF_CAN: return PF_CAN; +// case MUSL_PF_TIPC: return PF_TIPC; +// case MUSL_PF_BLUETOOTH: return PF_BLUETOOTH; +// case MUSL_PF_IUCV: return PF_IUCV; +// case MUSL_PF_RXRPC: return PF_RXRPC; +// case MUSL_PF_ISDN: return PF_ISDN; +// case MUSL_PF_PHONET: return PF_PHONET; +// case MUSL_PF_IEEE802154: return PF_IEEE802154; +// case MUSL_PF_CAIF: return PF_CAIF; +// case MUSL_PF_ALG: return PF_ALG; +// case MUSL_PF_NFC: return PF_NFC; +// case MUSL_PF_VSOCK: return PF_VSOCK; +// case MUSL_PF_KCM: return PF_KCM; +// case MUSL_PF_MAX: return PF_MAX; + + case MUSL_AF_UNSPEC: return AF_UNSPEC; +#ifdef AF_LOCAL + case MUSL_AF_LOCAL: return AF_LOCAL; +#endif +// case MUSL_AF_UNIX: return AF_UNIX; +// case MUSL_AF_FILE: return AF_FILE; + case MUSL_AF_INET: return AF_INET; +// case MUSL_AF_AX25: return AF_AX25; + case MUSL_AF_IPX: return AF_IPX; + case MUSL_AF_APPLETALK: return AF_APPLETALK; +// case MUSL_AF_NETROM: return AF_NETROM; +// case MUSL_AF_BRIDGE: return AF_BRIDGE; +// case MUSL_AF_ATMPVC: return AF_ATMPVC; +// case MUSL_AF_X25: return AF_X25; + case MUSL_AF_INET6: return AF_INET6; +// case MUSL_AF_ROSE: return AF_ROSE; + case MUSL_AF_DECnet: return AF_DECnet; +// case MUSL_AF_NETBEUI: return AF_NETBEUI; +// case MUSL_AF_SECURITY: return AF_SECURITY; +// case MUSL_AF_KEY: return AF_KEY; +// case MUSL_AF_NETLINK: return AF_NETLINK; +// case MUSL_AF_ROUTE: return AF_ROUTE; +// case MUSL_AF_PACKET: return AF_PACKET; +// case MUSL_AF_ASH: return AF_ASH; +// case MUSL_AF_ECONET: return AF_ECONET; +// case MUSL_AF_ATMSVC: return AF_ATMSVC; +// case MUSL_AF_RDS: return AF_RDS; + case MUSL_AF_SNA: return AF_SNA; +// case MUSL_AF_IRDA: return AF_IRDA; +// case MUSL_AF_PPPOX: return AF_PPPOX; +// case MUSL_AF_WANPIPE: return AF_WANPIPE; +// case MUSL_AF_LLC: return AF_LLC; +// case MUSL_AF_IB: return AF_IB; +// case MUSL_AF_MPLS: return AF_MPLS; +// case MUSL_AF_CAN: return AF_CAN; +// case MUSL_AF_TIPC: return AF_TIPC; +// case MUSL_AF_BLUETOOTH: return AF_BLUETOOTH; +// case MUSL_AF_IUCV: return AF_IUCV; +// case MUSL_AF_RXRPC: return AF_RXRPC; +#ifdef AF_ISDN + case MUSL_AF_ISDN: return AF_ISDN; +#endif +// case MUSL_AF_PHONET: return AF_PHONET; +// case MUSL_AF_IEEE802154: return AF_IEEE802154; +// case MUSL_AF_CAIF: return AF_CAIF; +// case MUSL_AF_ALG: return AF_ALG; +// case MUSL_AF_NFC: return AF_NFC; +// case MUSL_AF_VSOCK: return AF_VSOCK; +// case MUSL_AF_KCM: return AF_KCM; + case MUSL_AF_MAX: return AF_MAX; + default: + fprintf(stderr, "Uncrecognized Socket Domain %d!\n", domain); + return domain; + } +} + +#define MUSL_SOCK_STREAM 1 +#define MUSL_SOCK_DGRAM 2 +#define MUSL_SOCK_RAW 3 +#define MUSL_SOCK_RDM 4 +#define MUSL_SOCK_SEQPACKET 5 +#define MUSL_SOCK_DCCP 6 +#define MUSL_SOCK_PACKET 10 +#define MUSL_SOCK_CLOEXEC 02000000 +#define MUSL_SOCK_NONBLOCK 04000 + +static int Translate_Socket_Type(int type) +{ + if ((type & MUSL_SOCK_CLOEXEC) != 0) + { + fprintf(stderr, "Unsupported MUSL SOCK_CLOEXEC passed!\n"); + type &= ~MUSL_SOCK_CLOEXEC; + } + if ((type & MUSL_SOCK_NONBLOCK) != 0) + { + fprintf(stderr, "Unsupported MUSL SOCK_NONBLOCK passed!\n"); + type &= ~MUSL_SOCK_NONBLOCK; + } + + switch(type) + { + case MUSL_SOCK_STREAM: return SOCK_STREAM; + case MUSL_SOCK_DGRAM: return SOCK_DGRAM; + case MUSL_SOCK_RAW: return SOCK_RAW; + case MUSL_SOCK_RDM: return SOCK_RDM; + case MUSL_SOCK_SEQPACKET: return SOCK_SEQPACKET; +// case MUSL_SOCK_DCCP: return SOCK_DCCP; +// case MUSL_SOCK_PACKET: return SOCK_PACKET; + default: + fprintf(stderr, "Uncrecognized socket type %d!\n", type); + return type; + } +} + +#define MUSL_IPPROTO_IP 0 +#define MUSL_IPPROTO_HOPOPTS 0 +#define MUSL_IPPROTO_ICMP 1 +#define MUSL_IPPROTO_IGMP 2 +#define MUSL_IPPROTO_IPIP 4 +#define MUSL_IPPROTO_TCP 6 +#define MUSL_IPPROTO_EGP 8 +#define MUSL_IPPROTO_PUP 12 +#define MUSL_IPPROTO_UDP 17 +#define MUSL_IPPROTO_IDP 22 +#define MUSL_IPPROTO_TP 29 +#define MUSL_IPPROTO_DCCP 33 +#define MUSL_IPPROTO_IPV6 41 +#define MUSL_IPPROTO_ROUTING 43 +#define MUSL_IPPROTO_FRAGMENT 44 +#define MUSL_IPPROTO_RSVP 46 +#define MUSL_IPPROTO_GRE 47 +#define MUSL_IPPROTO_ESP 50 +#define MUSL_IPPROTO_AH 51 +#define MUSL_IPPROTO_ICMPV6 58 +#define MUSL_IPPROTO_NONE 59 +#define MUSL_IPPROTO_DSTOPTS 60 +#define MUSL_IPPROTO_MTP 92 +#define MUSL_IPPROTO_BEETPH 94 +#define MUSL_IPPROTO_ENCAP 98 +#define MUSL_IPPROTO_PIM 103 +#define MUSL_IPPROTO_COMP 108 +#define MUSL_IPPROTO_SCTP 132 +#define MUSL_IPPROTO_MH 135 +#define MUSL_IPPROTO_UDPLITE 136 +#define MUSL_IPPROTO_MPLS 137 +#define MUSL_IPPROTO_RAW 255 + +static int Translate_Socket_Protocol(int protocol) +{ + switch(protocol) + { + case MUSL_IPPROTO_IP: return IPPROTO_IP; +// case MUSL_IPPROTO_HOPOPTS: return IPPROTO_HOPOPTS; + case MUSL_IPPROTO_ICMP: return IPPROTO_ICMP; + case MUSL_IPPROTO_IGMP: return IPPROTO_IGMP; +#ifdef IPPROTO_IPIP + case MUSL_IPPROTO_IPIP: return IPPROTO_IPIP; +#endif + case MUSL_IPPROTO_TCP: return IPPROTO_TCP; + case MUSL_IPPROTO_EGP: return IPPROTO_EGP; + case MUSL_IPPROTO_PUP: return IPPROTO_PUP; + case MUSL_IPPROTO_UDP: return IPPROTO_UDP; + case MUSL_IPPROTO_IDP: return IPPROTO_IDP; +#ifdef IPPROTO_TP + case MUSL_IPPROTO_TP: return IPPROTO_TP; +#endif +// case MUSL_IPPROTO_DCCP: return IPPROTO_DCCP; + case MUSL_IPPROTO_IPV6: return IPPROTO_IPV6; + case MUSL_IPPROTO_ROUTING: return IPPROTO_ROUTING; + case MUSL_IPPROTO_FRAGMENT: return IPPROTO_FRAGMENT; +#ifdef IPPROTO_RSVP + case MUSL_IPPROTO_RSVP: return IPPROTO_RSVP; +#endif +#ifdef IPPROTO_GRE + case MUSL_IPPROTO_GRE: return IPPROTO_GRE; +#endif + case MUSL_IPPROTO_ESP: return IPPROTO_ESP; + case MUSL_IPPROTO_AH: return IPPROTO_AH; + case MUSL_IPPROTO_ICMPV6: return IPPROTO_ICMPV6; + case MUSL_IPPROTO_NONE: return IPPROTO_NONE; + case MUSL_IPPROTO_DSTOPTS: return IPPROTO_DSTOPTS; +#ifdef IPPROTO_MTP + case MUSL_IPPROTO_MTP: return IPPROTO_MTP; +#endif +// case MUSL_IPPROTO_BEETPH: return IPPROTO_BEETPH; +#ifdef IPPROTO_ENCAP + case MUSL_IPPROTO_ENCAP: return IPPROTO_ENCAP; +#endif + case MUSL_IPPROTO_PIM: return IPPROTO_PIM; +// case MUSL_IPPROTO_COMP: return IPPROTO_COMP; + case MUSL_IPPROTO_SCTP: return IPPROTO_SCTP; +// case MUSL_IPPROTO_MH: return IPPROTO_MH; +// case MUSL_IPPROTO_UDPLITE: return IPPROTO_UDPLITE; +// case MUSL_IPPROTO_MPLS: return IPPROTO_MPLS; + case MUSL_IPPROTO_RAW: return IPPROTO_RAW; + default: + fprintf(stderr, "Unrecognized socket protocol %d!\n", protocol); + return protocol; + } +} + +#define MUSL_SOL_SOCKET 1 +#define MUSL_SOL_IP 0 +#define MUSL_SOL_IPV6 41 +#define MUSL_SOL_ICMPV6 58 +#define MUSL_SOL_RAW 255 +#define MUSL_SOL_DECNET 261 +#define MUSL_SOL_X25 262 +#define MUSL_SOL_PACKET 263 +#define MUSL_SOL_ATM 264 +#define MUSL_SOL_AAL 265 +#define MUSL_SOL_IRDA 266 +#define MUSL_SOL_NETBEUI 267 +#define MUSL_SOL_LLC 268 +#define MUSL_SOL_DCCP 269 +#define MUSL_SOL_NETLINK 270 +#define MUSL_SOL_TIPC 271 +#define MUSL_SOL_RXRPC 272 +#define MUSL_SOL_PPPOL2TP 273 +#define MUSL_SOL_BLUETOOTH 274 +#define MUSL_SOL_PNPIPE 275 +#define MUSL_SOL_RDS 276 +#define MUSL_SOL_IUCV 277 +#define MUSL_SOL_CAIF 278 +#define MUSL_SOL_ALG 279 +#define MUSL_SOL_NFC 280 +#define MUSL_SOL_KCM 281 + +static int Translate_Socket_Level(int level) +{ + switch(level) + { + case MUSL_SOL_SOCKET: return SOL_SOCKET; + case MUSL_IPPROTO_TCP: return IPPROTO_TCP; +// case MUSL_SOL_IP: return SOL_IP; +// case MUSL_SOL_IPV6: return SOL_IPV6; +// case MUSL_SOL_ICMPV6: return SOL_ICMPV6; +// case MUSL_SOL_RAW: return SOL_RAW; +// case MUSL_SOL_DECNET: return SOL_DECNET; +// case MUSL_SOL_X25: return SOL_X25; +// case MUSL_SOL_PACKET: return SOL_PACKET; +// case MUSL_SOL_ATM: return SOL_ATM; +// case MUSL_SOL_AAL: return SOL_AAL; +// case MUSL_SOL_IRDA: return SOL_IRDA; +// case MUSL_SOL_NETBEUI: return SOL_NETBEUI; +// case MUSL_SOL_LLC: return SOL_LLC; +// case MUSL_SOL_DCCP: return SOL_DCCP; +// case MUSL_SOL_NETLINK: return SOL_NETLINK; +// case MUSL_SOL_TIPC: return SOL_TIPC; +// case MUSL_SOL_RXRPC: return SOL_RXRPC; +// case MUSL_SOL_PPPOL2TP: return SOL_PPPOL2TP; +// case MUSL_SOL_BLUETOOTH: return SOL_BLUETOOTH; +// case MUSL_SOL_PNPIPE: return SOL_PNPIPE; +// case MUSL_SOL_RDS: return SOL_RDS; +// case MUSL_SOL_IUCV: return SOL_IUCV; +// case MUSL_SOL_CAIF: return SOL_CAIF; +// case MUSL_SOL_ALG: return SOL_ALG; +// case MUSL_SOL_NFC: return SOL_NFC; +// case MUSL_SOL_KCM: return SOL_KCM; + default: + fprintf(stderr, "Uncrecognized socket level %d!\n", level); + return level; + } +} + +#define MUSL_SO_DEBUG 1 +#define MUSL_SO_REUSEADDR 2 +#define MUSL_SO_TYPE 3 +#define MUSL_SO_ERROR 4 +#define MUSL_SO_DONTROUTE 5 +#define MUSL_SO_BROADCAST 6 +#define MUSL_SO_SNDBUF 7 +#define MUSL_SO_RCVBUF 8 +#define MUSL_SO_KEEPALIVE 9 +#define MUSL_SO_OOBINLINE 10 +#define MUSL_SO_NO_CHECK 11 +#define MUSL_SO_PRIORITY 12 +#define MUSL_SO_LINGER 13 +#define MUSL_SO_BSDCOMPAT 14 +#define MUSL_SO_REUSEPORT 15 +#define MUSL_SO_PASSCRED 16 +#define MUSL_SO_PEERCRED 17 +#define MUSL_SO_RCVLOWAT 18 +#define MUSL_SO_SNDLOWAT 19 +#define MUSL_SO_RCVTIMEO 20 +#define MUSL_SO_SNDTIMEO 21 +#define MUSL_SO_ACCEPTCONN 30 +#define MUSL_SO_SNDBUFFORCE 32 +#define MUSL_SO_RCVBUFFORCE 33 +#define MUSL_SO_PROTOCOL 38 +#define MUSL_SO_DOMAIN 39 +#define MUSL_SO_SECURITY_AUTHENTICATION 22 +#define MUSL_SO_SECURITY_ENCRYPTION_TRANSPORT 23 +#define MUSL_SO_SECURITY_ENCRYPTION_NETWORK 24 +#define MUSL_SO_BINDTODEVICE 25 +#define MUSL_SO_ATTACH_FILTER 26 +#define MUSL_SO_DETACH_FILTER 27 +#define MUSL_SO_PEERNAME 28 +#define MUSL_SO_TIMESTAMP 29 +#define MUSL_SO_PEERSEC 31 +#define MUSL_SO_PASSSEC 34 +#define MUSL_SO_TIMESTAMPNS 35 +#define MUSL_SO_MARK 36 +#define MUSL_SO_TIMESTAMPING 37 +#define MUSL_SO_RXQ_OVFL 40 +#define MUSL_SO_WIFI_STATUS 41 +#define MUSL_SO_PEEK_OFF 42 +#define MUSL_SO_NOFCS 43 +#define MUSL_SO_LOCK_FILTER 44 +#define MUSL_SO_SELECT_ERR_QUEUE 45 +#define MUSL_SO_BUSY_POLL 46 +#define MUSL_SO_MAX_PACING_RATE 47 +#define MUSL_SO_BPF_EXTENSIONS 48 +#define MUSL_SO_INCOMING_CPU 49 +#define MUSL_SO_ATTACH_BPF 50 +#define MUSL_SO_ATTACH_REUSEPORT_CBPF 51 +#define MUSL_SO_ATTACH_REUSEPORT_EBPF 52 +#define MUSL_SO_CNX_ADVICE 53 + +static int Translate_SOL_SOCKET_option(int sockopt) +{ + switch(sockopt) + { + case MUSL_SO_DEBUG: return SO_DEBUG; + case MUSL_SO_REUSEADDR: return SO_REUSEADDR; + case MUSL_SO_TYPE: return SO_TYPE; + case MUSL_SO_ERROR: return SO_ERROR; + case MUSL_SO_DONTROUTE: return SO_DONTROUTE; + case MUSL_SO_BROADCAST: return SO_BROADCAST; + case MUSL_SO_SNDBUF: return SO_SNDBUF; + case MUSL_SO_RCVBUF: return SO_RCVBUF; + case MUSL_SO_KEEPALIVE: return SO_KEEPALIVE; + case MUSL_SO_OOBINLINE: return SO_OOBINLINE; +// case MUSL_SO_NO_CHECK: return SO_NO_CHECK; +// case MUSL_SO_PRIORITY: return SO_PRIORITY; + case MUSL_SO_LINGER: return SO_LINGER; +// case MUSL_SO_BSDCOMPAT: return SO_BSDCOMPAT; +#ifdef SO_REUSEPORT + case MUSL_SO_REUSEPORT: return SO_REUSEPORT; +#endif +// case MUSL_SO_PASSCRED: return SO_PASSCRED; +// case MUSL_SO_PEERCRED: return SO_PEERCRED; + case MUSL_SO_RCVLOWAT: return SO_RCVLOWAT; + case MUSL_SO_SNDLOWAT: return SO_SNDLOWAT; + case MUSL_SO_RCVTIMEO: return SO_RCVTIMEO; + case MUSL_SO_SNDTIMEO: return SO_SNDTIMEO; + case MUSL_SO_ACCEPTCONN: return SO_ACCEPTCONN; +// case MUSL_SO_SNDBUFFORCE: return SO_SNDBUFFORCE; +// case MUSL_SO_RCVBUFFORCE: return SO_RCVBUFFORCE; +// case MUSL_SO_PROTOCOL: return SO_PROTOCOL; +// case MUSL_SO_DOMAIN: return SO_DOMAIN; +// case MUSL_SO_SECURITY_AUTHENTICATION: return SO_SECURITY_AUTHENTICATION; +// case MUSL_SO_SECURITY_ENCRYPTION_TRANSPORT: return SO_SECURITY_ENCRYPTION_TRANSPORT; +// case MUSL_SO_SECURITY_ENCRYPTION_NETWORK: return SO_SECURITY_ENCRYPTION_NETWORK; +// case MUSL_SO_BINDTODEVICE: return SO_BINDTODEVICE; +// case MUSL_SO_ATTACH_FILTER: return SO_ATTACH_FILTER; +// case MUSL_SO_DETACH_FILTER: return SO_DETACH_FILTER; +// case MUSL_SO_PEERNAME: return SO_PEERNAME; +#ifdef SO_TIMESTAMP + case MUSL_SO_TIMESTAMP: return SO_TIMESTAMP; +#endif +// case MUSL_SO_PEERSEC: return SO_PEERSEC; +// case MUSL_SO_PASSSEC: return SO_PASSSEC; +// case MUSL_SO_TIMESTAMPNS: return SO_TIMESTAMPNS; +// case MUSL_SO_MARK: return SO_MARK; +// case MUSL_SO_TIMESTAMPING: return SO_TIMESTAMPING; +// case MUSL_SO_RXQ_OVFL: return SO_RXQ_OVFL; +// case MUSL_SO_WIFI_STATUS: return SO_WIFI_STATUS; +// case MUSL_SO_PEEK_OFF: return SO_PEEK_OFF; +// case MUSL_SO_NOFCS: return SO_NOFCS; +// case MUSL_SO_LOCK_FILTER: return SO_LOCK_FILTER; +// case MUSL_SO_SELECT_ERR_QUEUE: return SO_SELECT_ERR_QUEUE; +// case MUSL_SO_BUSY_POLL: return SO_BUSY_POLL; +// case MUSL_SO_MAX_PACING_RATE: return SO_MAX_PACING_RATE; +// case MUSL_SO_BPF_EXTENSIONS: return SO_BPF_EXTENSIONS; +// case MUSL_SO_INCOMING_CPU: return SO_INCOMING_CPU; +// case MUSL_SO_ATTACH_BPF: return SO_ATTACH_BPF; +// case MUSL_SO_ATTACH_REUSEPORT_CBPF: return SO_ATTACH_REUSEPORT_CBPF; +// case MUSL_SO_ATTACH_REUSEPORT_EBPF: return SO_ATTACH_REUSEPORT_EBPF; +// case MUSL_SO_CNX_ADVICE: return SO_CNX_ADVICE; + default: + fprintf(stderr, "Unrecognized SOL_SOCKET option %d!\n", sockopt); + return sockopt; + } +} + +#define MUSL_TCP_NODELAY 1 +#define MUSL_TCP_MAXSEG 2 +#define MUSL_TCP_CORK 3 +#define MUSL_TCP_KEEPIDLE 4 +#define MUSL_TCP_KEEPINTVL 5 +#define MUSL_TCP_KEEPCNT 6 +#define MUSL_TCP_SYNCNT 7 +#define MUSL_TCP_LINGER2 8 +#define MUSL_TCP_DEFER_ACCEPT 9 +#define MUSL_TCP_WINDOW_CLAMP 10 +#define MUSL_TCP_INFO 11 +#define MUSL_TCP_QUICKACK 12 +#define MUSL_TCP_CONGESTION 13 +#define MUSL_TCP_MD5SIG 14 +#define MUSL_TCP_THIN_LINEAR_TIMEOUTS 16 +#define MUSL_TCP_THIN_DUPACK 17 +#define MUSL_TCP_USER_TIMEOUT 18 +#define MUSL_TCP_REPAIR 19 +#define MUSL_TCP_REPAIR_QUEUE 20 +#define MUSL_TCP_QUEUE_SEQ 21 +#define MUSL_TCP_REPAIR_OPTIONS 22 +#define MUSL_TCP_FASTOPEN 23 +#define MUSL_TCP_TIMESTAMP 24 +#define MUSL_TCP_NOTSENT_LOWAT 25 +#define MUSL_TCP_CC_INFO 26 +#define MUSL_TCP_SAVE_SYN 27 +#define MUSL_TCP_SAVED_SYN 28 + +static int Translate_IPPROTO_TCP_option(int sockopt) +{ + switch(sockopt) + { + case MUSL_TCP_NODELAY: return TCP_NODELAY; + case MUSL_TCP_MAXSEG: return TCP_MAXSEG; +// case MUSL_TCP_CORK: return TCP_CORK; +// case MUSL_TCP_KEEPIDLE: return TCP_KEEPIDLE; + case MUSL_TCP_KEEPINTVL: return TCP_KEEPINTVL; + case MUSL_TCP_KEEPCNT: return TCP_KEEPCNT; +// case MUSL_TCP_SYNCNT: return TCP_SYNCNT; +// case MUSL_TCP_LINGER2: return TCP_LINGER2; +// case MUSL_TCP_DEFER_ACCEPT: return TCP_DEFER_ACCEPT; +// case MUSL_TCP_WINDOW_CLAMP: return TCP_WINDOW_CLAMP; +// case MUSL_TCP_INFO: return TCP_INFO; +// case MUSL_TCP_QUICKACK: return TCP_QUICKACK; +// case MUSL_TCP_CONGESTION: return TCP_CONGESTION; +// case MUSL_TCP_MD5SIG: return TCP_MD5SIG; +// case MUSL_TCP_THIN_LINEAR_TIMEOUTS: return TCP_THIN_LINEAR_TIMEOUTS; +// case MUSL_TCP_THIN_DUPACK: return TCP_THIN_DUPACK; +// case MUSL_TCP_USER_TIMEOUT: return TCP_USER_TIMEOUT; +// case MUSL_TCP_REPAIR: return TCP_REPAIR; +// case MUSL_TCP_REPAIR_QUEUE: return TCP_REPAIR_QUEUE; +// case MUSL_TCP_QUEUE_SEQ: return TCP_QUEUE_SEQ; +// case MUSL_TCP_REPAIR_OPTIONS: return TCP_REPAIR_OPTIONS; + case MUSL_TCP_FASTOPEN: return TCP_FASTOPEN; +// case MUSL_TCP_TIMESTAMP: return TCP_TIMESTAMP; +#ifdef TCP_NOTSENT_LOWAT + case MUSL_TCP_NOTSENT_LOWAT: return TCP_NOTSENT_LOWAT; +#endif +// case MUSL_TCP_CC_INFO: return TCP_CC_INFO; +// case MUSL_TCP_SAVE_SYN: return TCP_SAVE_SYN; +// case MUSL_TCP_SAVED_SYN: return TCP_SAVED_SYN; + default: + fprintf(stderr, "Unrecognized IPPROTO_TCP option %d!\n", sockopt); + return sockopt; + } +} + +void Socket(int client_fd, uint8_t *data, uint64_t numBytes) // int socket(int domain, int type, int protocol); +{ + struct MSG { + SocketCallHeader header; + int domain; + int type; + int protocol; + }; + MSG *d = (MSG*)data; + + d->domain = Translate_Socket_Domain(d->domain); + d->type = Translate_Socket_Type(d->type); + d->protocol = Translate_Socket_Protocol(d->protocol); + int ret = socket(d->domain, d->type, d->protocol); + int errorCode = (ret < 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("socket(domain=%d,type=%d,protocol=%d)->%d\n", d->domain, d->type, d->protocol, ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + + if (errorCode == 0) + { + // The proxy client connection created a new socket - track its lifetime and mark the new socket to be part of + // this particular proxy connection so that it will be properly freed when the proxy connection disconnects, + // and that no other proxy connections will be able to access this socket. + TrackSocketUsedByConnection(client_fd, ret); + } + + struct { + int callId; + int ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = ret; + r.errno_ = errorCode; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Socketpair(int client_fd, uint8_t *data, uint64_t numBytes) // int socketpair(int domain, int type, int protocol, int socket_vector[2]); +{ + struct MSG { + SocketCallHeader header; + int domain; + int type; + int protocol; + }; + MSG *d = (MSG*)data; + + int socket_vector[2]; + +#ifdef _MSC_VER + printf("TODO implement socketpair() on Windows\n"); + int ret = -1; + int errorCode = -1; +#else + d->domain = Translate_Socket_Domain(d->domain); + d->type = Translate_Socket_Type(d->type); + d->protocol = Translate_Socket_Protocol(d->protocol); + int ret = socketpair(d->domain, d->type, d->protocol, socket_vector); + int errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; +#endif + +#ifdef POSIX_SOCKET_DEBUG + printf("socketpair(domain=%d,type=%d,protocol=%d, socket_vector=[%d,%d])->%d\n", d->domain, d->type, d->protocol, socket_vector[0], socket_vector[1], ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + + if (errorCode == 0) + { + // The proxy client connection created two new sockets - track their lifetime and mark the new sockets to be part of + // this particular proxy connection so that they will be properly freed when the proxy connection disconnects, + // and that no other proxy connections will be able to access these sockets. + TrackSocketUsedByConnection(client_fd, socket_vector[0]); + TrackSocketUsedByConnection(client_fd, socket_vector[1]); + } + + struct { + int callId; + int ret; + int errno_; + int sv[2]; + } r; + r.callId = d->header.callId; + r.ret = ret; + r.errno_ = errorCode; + r.sv[0] = socket_vector[0]; + r.sv[1] = socket_vector[1]; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +#define MUSL_SHUT_RD 0 +#define MUSL_SHUT_WR 1 +#define MUSL_SHUT_RDWR 2 + +static int Translate_Shutdown_How(int how) +{ + switch(how) + { + case MUSL_SHUT_RD: return SHUTDOWN_READ; + case MUSL_SHUT_WR: return SHUTDOWN_WRITE; + case MUSL_SHUT_RDWR: return SHUTDOWN_BIDIRECTIONAL; + default: + fprintf(stderr, "Unrecognized shutdown() how option %d!\n", how); + return how; + } +} + +void Shutdown(int client_fd, uint8_t *data, uint64_t numBytes) // int shutdown(int socket, int how); +{ + struct MSG { + SocketCallHeader header; + int socket; + int how; + }; + MSG *d = (MSG*)data; + + d->how = Translate_Shutdown_How(d->how); + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = shutdown(d->socket, d->how); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; +#ifdef POSIX_SOCKET_DEBUG + printf("shutdown(socket=%d,how=%d)->%d\n", d->socket, d->how, ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + if (errorCode == 0 && d->how == SHUTDOWN_BIDIRECTIONAL) + { + // Proxy client performed bidirectional close, mark this socket as being disconnected, and disallow it + // from accessing this socket again - this close()s the socket. + CloseSocketByConnection(client_fd, d->socket); + } + } + else + { + fprintf(stderr, "shutdown(socket=%d,how=%d): Proxy client connection client_fd=%d attempted to call shutdown() on a socket fd=%d that it did not create (or has already shut down)\n", d->socket, d->how, client_fd, d->socket); + ret = errorCode = -1; + } + + struct { + int callId; + int ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = ret; + r.errno_ = (ret != 0) ? errno : 0; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Bind(int client_fd, uint8_t *data, uint64_t numBytes) // int bind(int socket, const struct sockaddr *address, socklen_t address_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + uint8_t address[]; + }; + MSG *d = (MSG*)data; + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = bind(d->socket, (sockaddr*)d->address, d->address_len); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; +#ifdef POSIX_SOCKET_DEBUG + printf("bind(socket=%d,address=%p,address_len=%d, address=\"%s\")->%d\n", d->socket, d->address, d->address_len, BufferToString(d->address, d->address_len), ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "bind(): Proxy client connection client_fd=%d attempted to call bind() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + } + + struct { + int callId; + int ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = ret; + r.errno_ = (ret != 0) ? errno : 0; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Connect(int client_fd, uint8_t *data, uint64_t numBytes) // int connect(int socket, const struct sockaddr *address, socklen_t address_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + uint8_t address[]; + }; + MSG *d = (MSG*)data; + + int actualAddressLen = MIN(d->address_len, (uint32_t)numBytes - sizeof(MSG)); + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = connect(d->socket, (sockaddr*)d->address, actualAddressLen); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; +#ifdef POSIX_SOCKET_DEBUG + printf("connect(socket=%d,address=%p,address_len=%d, address=\"%s\")->%d\n", d->socket, d->address, d->address_len, BufferToString(d->address, actualAddressLen), ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "connect(): Proxy client connection client_fd=%d attempted to call connect() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + } + + struct { + int callId; + int ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = ret; + r.errno_ = (ret != 0) ? errno : 0; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Listen(int client_fd, uint8_t *data, uint64_t numBytes) // int listen(int socket, int backlog); +{ + struct MSG { + SocketCallHeader header; + int socket; + int backlog; + }; + MSG *d = (MSG*)data; + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = listen(d->socket, d->backlog); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; +#ifdef POSIX_SOCKET_DEBUG + printf("listen(socket=%d,backlog=%d)->%d\n", d->socket, d->backlog, ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "bind(): Proxy client connection client_fd=%d attempted to call bind() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + } + + struct { + int callId; + int ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = ret; + r.errno_ = (ret != 0) ? errno : 0; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Accept(int client_fd, uint8_t *data, uint64_t numBytes) // int accept(int socket, struct sockaddr *address, socklen_t *address_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + }; + MSG *d = (MSG*)data; + + uint8_t address[MAX_SOCKADDR_SIZE] = {}; + socklen_t addressLen = (socklen_t)MAX(0, MIN(d->address_len, MAX_SOCKADDR_SIZE)); + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { +#ifdef POSIX_SOCKET_DEBUG + printf("accept(socket=%d,address=%p,address_len=%u, address=\"%s\")\n", d->socket, address, d->address_len, BufferToString(address, addressLen)); +#endif + ret = accept(d->socket, d->address_len ? (sockaddr*)address : 0, d->address_len ? &addressLen : 0); + errorCode = (ret < 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("accept returned %d (address=\"%s\")\n", ret, BufferToString(address, addressLen)); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + + if (ret > 0) + { + // New connection socket created by the proxy bridge, mark it as part of this WebSocket proxy connection. + TrackSocketUsedByConnection(client_fd, ret); + } + } + else + { + fprintf(stderr, "accept(): Proxy client connection client_fd=%d attempted to call accept() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + addressLen = 0; + } + + struct Result { + int callId; + int ret; + int errno_; + int address_len; + uint8_t address[]; + }; + + int actualAddressLen = MIN(addressLen, (socklen_t)d->address_len); + int resultSize = sizeof(Result) + actualAddressLen; + Result *r = (Result*)malloc(resultSize); + r->callId = d->header.callId; + r->ret = ret; + r->errno_ = errorCode; + r->address_len = addressLen; + memcpy(r->address, address, actualAddressLen); + SendWebSocketMessage(client_fd, r, resultSize); + free(r); +} + +void Getsockname(int client_fd, uint8_t *data, uint64_t numBytes) // int getsockname(int socket, struct sockaddr *address, socklen_t *address_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + }; + MSG *d = (MSG*)data; + + uint8_t address[MAX_SOCKADDR_SIZE]; + + socklen_t addressLen = (socklen_t)d->address_len; + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = getsockname(d->socket, (sockaddr*)address, &addressLen); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("getsockname(socket=%d,address=%p,address_len=%u)->%d (ret address: \"%s\")\n", d->socket, address, d->address_len, ret, BufferToString(address, addressLen)); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "getsockname(): Proxy client connection client_fd=%d attempted to call getsockname() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + addressLen = 0; + } + + struct Result { + int callId; + int ret; + int errno_; + int address_len; + uint8_t address[]; + }; + int actualAddressLen = MIN(addressLen, (socklen_t)d->address_len); + int resultSize = sizeof(Result) + actualAddressLen; + Result *r = (Result*)malloc(resultSize); + r->callId = d->header.callId; + r->ret = ret; + r->errno_ = errorCode; + r->address_len = addressLen; + memcpy(r->address, address, actualAddressLen); + SendWebSocketMessage(client_fd, r, resultSize); + free(r); +} + +void Getpeername(int client_fd, uint8_t *data, uint64_t numBytes) // int getpeername(int socket, struct sockaddr *address, socklen_t *address_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*socklen_t*/ address_len; + }; + MSG *d = (MSG*)data; + + uint8_t address[MAX_SOCKADDR_SIZE]; + socklen_t addressLen = (socklen_t)d->address_len; + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = getpeername(d->socket, (sockaddr*)address, &addressLen); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("getpeername(socket=%d,address=%p,address_len=%u, address=\"%s\")->%d\n", d->socket, address, d->address_len, BufferToString(address, addressLen), ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "getpeername(): Proxy client connection client_fd=%d attempted to call getpeername() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + addressLen = 0; + } + + struct Result { + int callId; + int ret; + int errno_; + int address_len; + uint8_t address[]; + }; + int actualAddressLen = MIN(addressLen, (socklen_t)d->address_len); + int resultSize = sizeof(Result) + actualAddressLen; + Result *r = (Result*)malloc(resultSize); + r->callId = d->header.callId; + r->ret = ret; + r->errno_ = errorCode; + r->address_len = addressLen; + memcpy(r->address, address, actualAddressLen); + SendWebSocketMessage(client_fd, r, resultSize); + free(r); +} + +void Send(int client_fd, uint8_t *data, uint64_t numBytes) // ssize_t/int send(int socket, const void *message, size_t length, int flags); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*size_t*/ length; + int flags; + uint8_t message[]; + }; + MSG *d = (MSG*)data; + + int actualBytes = MIN((int)numBytes - sizeof(MSG), d->length); + SEND_RET_TYPE ret; + int errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = send(d->socket, (const char *)d->message, actualBytes, d->flags); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("send(socket=%d,message=%p,length=%zd,flags=%d, data=\"%s\")->" SEND_FORMATTING_SPECIFIER "\n", d->socket, d->message, d->length, d->flags, BufferToString(d->message, d->length), ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "send(): Proxy client connection client_fd=%d attempted to call send() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + } + + struct { + int callId; + int/*ssize_t/int*/ ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = (int)ret; + r.errno_ = (ret != 0) ? errno : 0; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Recv(int client_fd, uint8_t *data, uint64_t numBytes) // ssize_t/int recv(int socket, void *buffer, size_t length, int flags); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*size_t*/ length; + int flags; + }; + MSG *d = (MSG*)data; + + uint8_t *buffer = (uint8_t*)malloc(d->length); + SEND_RET_TYPE ret; + int errorCode; + int receivedBytes; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = recv(d->socket, (char *)buffer, d->length, d->flags); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + receivedBytes = MAX(ret, 0); + +#ifdef POSIX_SOCKET_DEBUG + printf("recv(socket=%d,buffer=%p,length=%zd,flags=%d)->" SEND_FORMATTING_SPECIFIER " received \"%s\"\n", d->socket, buffer, d->length, d->flags, ret, BufferToString(buffer, receivedBytes)); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "recv(): Proxy client connection client_fd=%d attempted to call recv() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + receivedBytes = 0; + } + + struct Result { + int callId; + int/*ssize_t/int*/ ret; + int errno_; + uint8_t data[]; + }; + int resultSize = sizeof(Result) + receivedBytes; + Result *r = (Result *)malloc(resultSize); + r->callId = d->header.callId; + r->ret = (int)ret; + r->errno_ = errorCode; + memcpy(r->data, buffer, receivedBytes); + free(buffer); + SendWebSocketMessage(client_fd, r, resultSize); + free(r); +} + +void Sendto(int client_fd, uint8_t *data, uint64_t numBytes) // ssize_t/int sendto(int socket, const void *message, size_t length, int flags, const struct sockaddr *dest_addr, socklen_t 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[]; + }; + MSG *d = (MSG*)data; + + SEND_RET_TYPE ret; + int errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = sendto(d->socket, (const char *)d->message, d->length, d->flags, (sockaddr*)d->dest_addr, d->dest_len); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("sendto(socket=%d,message=%p,length=%zd,flags=%d,dest_addr=%p,dest_len=%d)->" SEND_FORMATTING_SPECIFIER "\n", d->socket, d->message, d->length, d->flags, d->dest_addr, d->dest_len, ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "sendto(): Proxy client connection client_fd=%d attempted to call sendto() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + } + + struct { + int callId; + int/*ssize_t/int*/ ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = (int)ret; + r.errno_ = (ret != 0) ? errno : 0; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Recvfrom(int client_fd, uint8_t *data, uint64_t numBytes) // ssize_t/int recvfrom(int socket, void *buffer, size_t length, int flags, struct sockaddr *address, socklen_t *address_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + uint32_t/*size_t*/ length; + int flags; + uint32_t/*socklen_t*/ address_len; + }; + MSG *d = (MSG*)data; + + uint8_t address[MAX_SOCKADDR_SIZE]; + uint8_t *buffer = (uint8_t *)malloc(d->length); + + socklen_t address_len = (socklen_t)d->address_len; + + int ret, errorCode, receivedBytes; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = recvfrom(d->socket, (char *)buffer, d->length, d->flags, (sockaddr*)address, &address_len); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; +#ifdef POSIX_SOCKET_DEBUG + printf("recvfrom(socket=%d,buffer=%p,length=%zd,flags=%d,address=%p,address_len=%u, address=\"%s\")->%d\n", d->socket, buffer, d->length, d->flags, address, d->address_len, BufferToString(address, address_len), ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + receivedBytes = MAX(ret, 0); + } + else + { + fprintf(stderr, "recvfrom(): Proxy client connection client_fd=%d attempted to call recvfrom() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + receivedBytes = 0; + address_len = 0; + } + + int actualAddressLen = MIN(address_len, (socklen_t)d->address_len); + + struct Result { + int callId; + int/*ssize_t/int*/ ret; + int errno_; + 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[]; + }; + int resultSize = sizeof(Result) + receivedBytes + actualAddressLen; + Result *r = (Result *)malloc(resultSize); + r->callId = d->header.callId; + r->ret = (int)ret; + r->errno_ = errorCode; + r->data_len = receivedBytes; + r->address_len = d->address_len; // How many bytes would have been needed to fit the whole sender address, not the actual size provided + memcpy(r->data_and_address, buffer, receivedBytes); + memcpy(r->data_and_address + receivedBytes, address, actualAddressLen); + SendWebSocketMessage(client_fd, r, resultSize); + free(r); +} + +void Sendmsg(int client_fd, uint8_t *data, uint64_t numBytes) // ssize_t/int sendmsg(int socket, const struct msghdr *message, int flags); +{ + printf("TODO implement sendmsg()\n"); +#ifdef POSIX_SOCKET_DEBUG +// printf("sendmsg(socket=%d,message=%p,flags=%d)\n", d->socket, d->message, d->flags); +#endif + + // TODO +} + +void Recvmsg(int client_fd, uint8_t *data, uint64_t numBytes) // ssize_t/int recvmsg(int socket, struct msghdr *message, int flags); +{ + printf("TODO implement recvmsg()\n"); +#ifdef POSIX_SOCKET_DEBUG +// printf("recvmsg(socket=%d,message=%p,flags=%d)\n", d->socket, d->message, d->flags); +#endif +} + +void Getsockopt(int client_fd, uint8_t *data, uint64_t numBytes) // int getsockopt(int socket, int level, int option_name, void *option_value, socklen_t *option_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + int level; + int option_name; + uint32_t/*socklen_t*/ option_len; + }; + MSG *d = (MSG*)data; + + uint8_t option_value[MAX_OPTIONVALUE_SIZE]; + + d->level = Translate_Socket_Level(d->level); + d->option_name = Translate_SOL_SOCKET_option(d->option_name); + + socklen_t option_len = (socklen_t)d->option_len; + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = getsockopt(d->socket, d->level, d->option_name, (char*)option_value, &option_len); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("getsockopt(socket=%d,level=%d,option_name=%d,option_value=%p,option_len=%u, optionData=\"%s\")->%d\n", d->socket, d->level, d->option_name, option_value, d->option_len, BufferToString(option_value, option_len), ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "getsockopt(): Proxy client connection client_fd=%d attempted to call getsockopt() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + option_len = 0; + } + + struct Result { + int callId; + int ret; + int errno_; + int option_len; + uint8_t option_value[]; + }; + + int actualOptionLen = MIN(option_len, (socklen_t)d->option_len); + int resultSize = sizeof(Result) + actualOptionLen; + Result *r = (Result*)malloc(resultSize); + r->callId = d->header.callId; + r->ret = ret; + r->errno_ = errorCode; + r->option_len = option_len; + memcpy(r->option_value, option_value, actualOptionLen); + SendWebSocketMessage(client_fd, r, resultSize); + free(r); +} + +void Setsockopt(int client_fd, uint8_t *data, uint64_t numBytes) // int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len); +{ + struct MSG { + SocketCallHeader header; + int socket; + int level; + int option_name; + int option_len; + uint8_t option_value[]; + }; + MSG *d = (MSG*)data; + int actualOptionLen = MIN(d->option_len, (int)(numBytes - sizeof(MSG))); + + d->level = Translate_Socket_Level(d->level); + switch(d->level) + { + case SOL_SOCKET: d->option_name = Translate_SOL_SOCKET_option(d->option_name); break; + case IPPROTO_TCP: d->option_name = Translate_IPPROTO_TCP_option(d->option_name); break; + default: + fprintf(stderr, "Unknown socket level %d, unable to translate socket option\n", d->level); + break; + } + + int ret, errorCode; + + if (IsSocketPartOfConnection(client_fd, d->socket)) + { + ret = setsockopt(d->socket, d->level, d->option_name, (const char *)d->option_value, actualOptionLen); + errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("setsockopt(socket=%d,level=%d,option_name=%d,option_value=%p,option_len=%d, optionData=\"%s\")->%d\n", d->socket, d->level, d->option_name, d->option_value, d->option_len, BufferToString(d->option_value, actualOptionLen), ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + } + else + { + fprintf(stderr, "setsockopt(): Proxy client connection client_fd=%d attempted to call setsockopt() on a socket fd=%d that it did not create (or has already shut down)\n", client_fd, d->socket); + ret = errorCode = -1; + } + + struct { + int callId; + int ret; + int errno_; + } r; + r.callId = d->header.callId; + r.ret = ret; + r.errno_ = (ret != 0) ? errno : 0; + SendWebSocketMessage(client_fd, &r, sizeof(r)); +} + +void Getaddrinfo(int client_fd, uint8_t *data, uint64_t numBytes) // int getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res); +{ +#define MAX_NODE_LEN 2048 +#define MAX_SERVICE_LEN 128 + + struct MSG { + 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; + }; + MSG *d = (MSG*)data; + + addrinfo hints; + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = d->ai_flags; + hints.ai_family = d->ai_family; + hints.ai_socktype = d->ai_socktype; + hints.ai_protocol = d->ai_protocol; + + addrinfo *res = 0; + int ret = getaddrinfo(d->node, d->service, d->hasHints ? &hints : 0, &res); + int errorCode = (ret != 0) ? GET_SOCKET_ERROR() : 0; + +#ifdef POSIX_SOCKET_DEBUG + printf("getaddrinfo(node=%s,service=%s,hasHints=%d,ai_flags=%d,ai_family=%d,ai_socktype=%d,ai_protocol=%d)->%d\n", d->node, d->service, d->hasHints, d->ai_flags, d->ai_family, d->ai_socktype, d->ai_protocol, ret); + if (errorCode) PRINT_SOCKET_ERROR(errorCode); +#endif + + char ai_canonname[MAX_NODE_LEN] = {}; + int ai_addrTotalLen = 0; + int addrCount = 0; + + if (ret == 0) + { + if (res && res->ai_canonname) + { + if (strlen(res->ai_canonname) >= MAX_NODE_LEN) printf("Warning: Truncated res->ai_canonname to %d bytes (was %s)\n", MAX_NODE_LEN, res->ai_canonname); + strncpy(ai_canonname, res->ai_canonname, MAX_NODE_LEN-1); + } + + addrinfo *ai = res; + while(ai) + { + ai_addrTotalLen += ai->ai_addrlen; + ++addrCount; + ai = ai->ai_next; + } + } + + 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 { + int callId; + int ret; + int errno_; + char ai_canonname[MAX_NODE_LEN]; + int addrCount; + uint8_t /*ResAddrinfo[]*/ addr[]; + }; + + int resultSize = sizeof(Result) + sizeof(ResAddrinfo)*addrCount + ai_addrTotalLen; + Result *r = (Result*)malloc(resultSize); + + memset(r, 0, resultSize); + r->callId = d->header.callId; + r->ret = ret; + r->errno_ = errorCode; + strncpy(r->ai_canonname, ai_canonname, MAX_NODE_LEN-1); + r->addrCount = addrCount; + + addrinfo *ai = res; + int offset = 0; + while(ai) + { + ResAddrinfo *o = (ResAddrinfo*)(r->addr + offset); + o->ai_flags = ai->ai_flags; + o->ai_family = ai->ai_family; + o->ai_socktype = ai->ai_socktype; + o->ai_protocol = ai->ai_protocol; + o->ai_addrlen = ai->ai_addrlen; + memcpy(o->ai_addr, ai->ai_addr, ai->ai_addrlen); + offset += sizeof(ResAddrinfo) + ai->ai_addrlen; + ai = ai->ai_next; + } + if (res) freeaddrinfo(res); + + SendWebSocketMessage(client_fd, r, resultSize); + + free(r); +} + +void Getnameinfo(int client_fd, uint8_t *data, uint64_t numBytes) // int getnameinfo(const struct sockaddr *addr, socklen_t addrlen, char *host, socklen_t hostlen, char *serv, socklen_t servlen, int flags); +{ + fprintf(stderr, "TODO getnameinfo() unimplemented!\n"); +} + +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; +} + +struct MessageArg +{ + int client_fd; + uint8_t *payload; + uint64_t numBytes; +}; + +void ProcessWebSocketMessageSynchronouslyInCurrentThread(int client_fd, uint8_t *payload, uint64_t numBytes); + +THREAD_RETURN_T message_processing_thread(void *arg) +{ + MessageArg *msg = (MessageArg*)arg; + assert(msg); + assert(msg->client_fd); + ProcessWebSocketMessageSynchronouslyInCurrentThread(msg->client_fd, msg->payload, msg->numBytes); + free(msg->payload); + free(msg); + EXIT_THREAD(0); +} + +// Offloads the processing of the given message to a background thread. +void ProcessWebSocketMessageAsynchronouslyInBackgroundThread(int client_fd, uint8_t *payload, uint64_t numBytes) +{ + MessageArg *arg = (MessageArg*)malloc(sizeof(MessageArg)); + arg->client_fd = client_fd; + arg->payload = (uint8_t*)memdup(payload, (size_t)numBytes); + arg->numBytes = numBytes; + THREAD_T thread; + // TODO: Instead of unconditionally always creating a thread here, create a thread pool and push messages to it. + // (leaving this as a future optimization because not sure if it matters here much at all for performance) + CREATE_THREAD(thread, message_processing_thread, arg); +} + +void ProcessWebSocketMessageSynchronouslyInCurrentThread(int client_fd, uint8_t *payload, uint64_t numBytes) +{ + assert(numBytes >= sizeof(SocketCallHeader)); // Already validated in ProcessWebSocketMessage() before coming here, so we should be good. + SocketCallHeader *header = (SocketCallHeader*)payload; + switch(header->function) + { + case POSIX_SOCKET_MSG_SOCKET: Socket(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_SOCKETPAIR: Socketpair(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_SHUTDOWN: Shutdown(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_BIND: Bind(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_CONNECT: Connect(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_LISTEN: Listen(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_ACCEPT: Accept(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_GETSOCKNAME: Getsockname(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_GETPEERNAME: Getpeername(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_SEND: Send(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_RECV: Recv(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_SENDTO: Sendto(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_RECVFROM: Recvfrom(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_SENDMSG: Sendmsg(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_RECVMSG: Recvmsg(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_GETSOCKOPT: Getsockopt(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_SETSOCKOPT: Setsockopt(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_GETADDRINFO: Getaddrinfo(client_fd, payload, numBytes); break; + case POSIX_SOCKET_MSG_GETNAMEINFO: Getnameinfo(client_fd, payload, numBytes); break; + default: + printf("Unknown POSIX_SOCKET_MSG %u received!\n", header->function); + break; + } +} + +void ProcessWebSocketMessage(int client_fd, uint8_t *payload, uint64_t numBytes) +{ + if (numBytes < sizeof(SocketCallHeader)) + { + printf("Received too small sockets call message! size: %d bytes, expected at least %d bytes\n", (int)numBytes, (int)sizeof(SocketCallHeader)); + return; + } + SocketCallHeader *header = (SocketCallHeader*)payload; + if (header->function == POSIX_SOCKET_MSG_RECV || header->function == POSIX_SOCKET_MSG_RECVFROM || header->function == POSIX_SOCKET_MSG_RECVMSG || header->function == POSIX_SOCKET_MSG_CONNECT || header->function == POSIX_SOCKET_MSG_LISTEN) + { + // Synchonous/blocking recv()s can halt indefinitely until a message is actually received. An application might + // be send()ing messages in one thread while using another thread to wait for recv(). Therefore run these potentially + // blocking recv()s in a separate thread. The nonblocking operations can run synchronously in calling thread (they could + // also run in a background thread, but for performance, do not offload them since it is not necessary) + ProcessWebSocketMessageAsynchronouslyInBackgroundThread(client_fd, payload, numBytes); + } + else + { + ProcessWebSocketMessageSynchronouslyInCurrentThread(client_fd, payload, numBytes); + } +} diff --git a/tools/websocket_to_posix_proxy/src/websocket_to_posix_proxy.h b/tools/websocket_to_posix_proxy/src/websocket_to_posix_proxy.h new file mode 100644 index 0000000000000..85b3bc34ff2bf --- /dev/null +++ b/tools/websocket_to_posix_proxy/src/websocket_to_posix_proxy.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +uint64_t ntoh64(uint64_t x); +#define hton64 ntoh64 + +void WebSocketMessageUnmaskPayload(uint8_t *payload, uint64_t payloadLength, uint32_t maskingKey); +void ProcessWebSocketMessage(int client_fd, uint8_t *payload, uint64_t numBytes); + +#ifdef _MSC_VER +#pragma pack(push,1) +#endif + +struct +#if defined(__GNUC__) +__attribute__ ((packed, aligned(1))) +#endif + +WebSocketMessageHeader +{ + unsigned opcode : 4; + unsigned rsv : 3; + unsigned fin : 1; + unsigned payloadLength : 7; + unsigned mask : 1; +}; + +#ifdef _MSC_VER +__pragma(pack(pop)) +#endif