From 3b40a925483610215b10313d080c885384753e2a Mon Sep 17 00:00:00 2001 From: Lauri Kasanen Date: Tue, 26 Jul 2022 10:38:14 +0000 Subject: [PATCH] Udp --- CMakeLists.txt | 3 + common/network/CMakeLists.txt | 17 + common/network/GetAPI.h | 10 +- common/network/GetAPIMessager.cxx | 16 + common/network/TcpSocket.cxx | 49 ++ common/network/Udp.cxx | 155 ++++++ common/network/Udp.h | 53 ++ common/network/iceip.cxx | 185 ++++++ common/network/iceip.h | 23 + common/network/websocket.c | 10 - common/network/webudp/CHANGELOG.md | 38 ++ common/network/webudp/CRC32.cpp | 124 +++++ common/network/webudp/CRC32.h | 7 + common/network/webudp/LICENSE | 21 + common/network/webudp/README.md | 20 + common/network/webudp/Wu.cpp | 771 ++++++++++++++++++++++++++ common/network/webudp/Wu.h | 76 +++ common/network/webudp/WuArena.cpp | 26 + common/network/webudp/WuArena.h | 14 + common/network/webudp/WuBufferOp.h | 48 ++ common/network/webudp/WuClock.h | 34 ++ common/network/webudp/WuCrypto.cpp | 65 +++ common/network/webudp/WuCrypto.h | 23 + common/network/webudp/WuHost.h | 36 ++ common/network/webudp/WuHostEpoll.cpp | 268 +++++++++ common/network/webudp/WuHostNull.cpp | 11 + common/network/webudp/WuHttp.h | 9 + common/network/webudp/WuMath.h | 15 + common/network/webudp/WuNetwork.cpp | 58 ++ common/network/webudp/WuNetwork.h | 9 + common/network/webudp/WuPool.cpp | 60 ++ common/network/webudp/WuPool.h | 10 + common/network/webudp/WuQueue.cpp | 58 ++ common/network/webudp/WuQueue.h | 16 + common/network/webudp/WuRng.cpp | 51 ++ common/network/webudp/WuRng.h | 14 + common/network/webudp/WuSctp.cpp | 169 ++++++ common/network/webudp/WuSctp.h | 100 ++++ common/network/webudp/WuSdp.cpp | 152 +++++ common/network/webudp/WuSdp.h | 24 + common/network/webudp/WuString.cpp | 19 + common/network/webudp/WuString.h | 6 + common/network/webudp/WuStun.cpp | 134 +++++ common/network/webudp/WuStun.h | 57 ++ common/network/webudp/package.json | 19 + common/rdr/ZlibOutStream.cxx | 6 + common/rdr/ZlibOutStream.h | 2 + common/rfb/ConnParams.cxx | 1 + common/rfb/ConnParams.h | 2 + common/rfb/EncodeManager.cxx | 12 +- common/rfb/SConnection.cxx | 5 +- common/rfb/SConnection.h | 4 +- common/rfb/SMsgHandler.cxx | 1 - common/rfb/SMsgHandler.h | 3 + common/rfb/SMsgReader.cxx | 28 + common/rfb/SMsgReader.h | 2 + common/rfb/SMsgWriter.cxx | 57 +- common/rfb/SMsgWriter.h | 5 +- common/rfb/ServerCore.cxx | 10 + common/rfb/ServerCore.h | 2 + common/rfb/TightEncoder.cxx | 16 +- common/rfb/TightEncoderBPP.cxx | 14 +- common/rfb/TightJPEGEncoder.cxx | 4 +- common/rfb/TightWEBPEncoder.cxx | 4 +- common/rfb/VNCSConnectionST.cxx | 37 +- common/rfb/VNCSConnectionST.h | 5 + common/rfb/VNCServerST.cxx | 44 +- common/rfb/VNCServerST.h | 1 - common/rfb/encodings.h | 1 + common/rfb/msgTypes.h | 3 + unix/xserver/hw/vnc/Xvnc.man | 9 + unix/xserver/hw/vnc/vncExtInit.cc | 5 +- 72 files changed, 3314 insertions(+), 52 deletions(-) create mode 100644 common/network/Udp.cxx create mode 100644 common/network/Udp.h create mode 100644 common/network/iceip.cxx create mode 100644 common/network/iceip.h create mode 100644 common/network/webudp/CHANGELOG.md create mode 100644 common/network/webudp/CRC32.cpp create mode 100644 common/network/webudp/CRC32.h create mode 100644 common/network/webudp/LICENSE create mode 100644 common/network/webudp/README.md create mode 100644 common/network/webudp/Wu.cpp create mode 100644 common/network/webudp/Wu.h create mode 100644 common/network/webudp/WuArena.cpp create mode 100644 common/network/webudp/WuArena.h create mode 100644 common/network/webudp/WuBufferOp.h create mode 100644 common/network/webudp/WuClock.h create mode 100644 common/network/webudp/WuCrypto.cpp create mode 100644 common/network/webudp/WuCrypto.h create mode 100644 common/network/webudp/WuHost.h create mode 100644 common/network/webudp/WuHostEpoll.cpp create mode 100644 common/network/webudp/WuHostNull.cpp create mode 100644 common/network/webudp/WuHttp.h create mode 100644 common/network/webudp/WuMath.h create mode 100644 common/network/webudp/WuNetwork.cpp create mode 100644 common/network/webudp/WuNetwork.h create mode 100644 common/network/webudp/WuPool.cpp create mode 100644 common/network/webudp/WuPool.h create mode 100644 common/network/webudp/WuQueue.cpp create mode 100644 common/network/webudp/WuQueue.h create mode 100644 common/network/webudp/WuRng.cpp create mode 100644 common/network/webudp/WuRng.h create mode 100644 common/network/webudp/WuSctp.cpp create mode 100644 common/network/webudp/WuSctp.h create mode 100644 common/network/webudp/WuSdp.cpp create mode 100644 common/network/webudp/WuSdp.h create mode 100644 common/network/webudp/WuString.cpp create mode 100644 common/network/webudp/WuString.h create mode 100644 common/network/webudp/WuStun.cpp create mode 100644 common/network/webudp/WuStun.h create mode 100644 common/network/webudp/package.json diff --git a/CMakeLists.txt b/CMakeLists.txt index c32884a..ab5725d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -79,6 +79,9 @@ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fopenmp") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fopenmp") +# Enable C++ 11 +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=gnu++11") + # Tell the compiler to be stringent set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall -Wformat=2") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wformat=2") diff --git a/common/network/CMakeLists.txt b/common/network/CMakeLists.txt index 4f91537..642fbf6 100644 --- a/common/network/CMakeLists.txt +++ b/common/network/CMakeLists.txt @@ -3,12 +3,29 @@ include_directories(${CMAKE_SOURCE_DIR}/common ${CMAKE_SOURCE_DIR}/unix/kasmvncp set(NETWORK_SOURCES GetAPIMessager.cxx Blacklist.cxx + iceip.cxx Socket.cxx TcpSocket.cxx + Udp.cxx cJSON.c jsonescape.c websocket.c websockify.c + + webudp/CRC32.cpp + webudp/WuArena.cpp + webudp/Wu.cpp + webudp/WuCrypto.cpp + webudp/WuHostEpoll.cpp + webudp/WuNetwork.cpp + webudp/WuPool.cpp + webudp/WuQueue.cpp + webudp/WuRng.cpp + webudp/WuSctp.cpp + webudp/WuSdp.cpp + webudp/WuString.cpp + webudp/WuStun.cpp + ${CMAKE_SOURCE_DIR}/unix/kasmvncpasswd/kasmpasswd.c) if(NOT WIN32) diff --git a/common/network/GetAPI.h b/common/network/GetAPI.h index 8bed216..f02c75e 100644 --- a/common/network/GetAPI.h +++ b/common/network/GetAPI.h @@ -65,6 +65,7 @@ namespace network { void netGetFrameStats(char *buf, uint32_t len); void netResetFrameStatsCall(); uint8_t netServerFrameStatsReady(); + void netUdpUpgrade(void *client, uint32_t ip); enum USER_ACTION { NONE, @@ -72,6 +73,7 @@ namespace network { WANT_FRAME_STATS_ALL, WANT_FRAME_STATS_OWNER, WANT_FRAME_STATS_SPECIFIC, + UDP_UPGRADE }; uint8_t netRequestFrameStats(USER_ACTION what, const char *client); @@ -81,7 +83,13 @@ namespace network { struct action_data { enum USER_ACTION action; - kasmpasswd_entry_t data; + union { + kasmpasswd_entry_t data; + struct { + void *client; + uint32_t ip; + } udp; + }; }; pthread_mutex_t userMutex; diff --git a/common/network/GetAPIMessager.cxx b/common/network/GetAPIMessager.cxx index 405cbc5..0fb2ee0 100644 --- a/common/network/GetAPIMessager.cxx +++ b/common/network/GetAPIMessager.cxx @@ -790,3 +790,19 @@ uint8_t GetAPIMessager::netServerFrameStatsReady() { return ret; } + +void GetAPIMessager::netUdpUpgrade(void *client, uint32_t ip) { + // Return 1 for success + action_data act; + act.action = UDP_UPGRADE; + act.udp.client = client; + act.udp.ip = ip; + + // Send it in + if (pthread_mutex_lock(&userMutex)) + return; + + actionQueue.push_back(act); + + pthread_mutex_unlock(&userMutex); +} diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx index cb82c3c..87bf363 100644 --- a/common/network/TcpSocket.cxx +++ b/common/network/TcpSocket.cxx @@ -42,10 +42,14 @@ #include #include #include +#include +#include +#include #include "websocket.h" #include #include +#include #include #include #include @@ -541,6 +545,45 @@ static uint8_t serverFrameStatsReadyCb(void *messager) return msgr->netServerFrameStatsReady(); } +#if OPENSSL_VERSION_NUMBER < 0x1010000f + +static pthread_mutex_t *sslmutex; + +static void openssl_lock(int mode, int n, const char *, int) +{ + if (mode & CRYPTO_LOCK) + pthread_mutex_lock(&sslmutex[n]); + else + pthread_mutex_unlock(&sslmutex[n]); +} + +static unsigned long openssl_id() +{ + return pthread_self(); +} + +#endif + +static void openssl_threads() { + + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + ERR_load_BIO_strings(); + ERR_load_crypto_strings(); + +#if OPENSSL_VERSION_NUMBER < 0x1010000f + + sslmutex = (pthread_mutex_t *) calloc(CRYPTO_num_locks(), sizeof(pthread_mutex_t)); + unsigned i; + for (i = 0; i < (unsigned) CRYPTO_num_locks(); i++) + pthread_mutex_init(&sslmutex[i], NULL); + + CRYPTO_set_locking_callback(openssl_lock); + CRYPTO_set_id_callback(openssl_id); + +#endif +} WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen, @@ -650,8 +693,14 @@ WebsocketListener::WebsocketListener(const struct sockaddr *listenaddr, settings.getClientFrameStatsNumCb = getClientFrameStatsNumCb; settings.serverFrameStatsReadyCb = serverFrameStatsReadyCb; + openssl_threads(); + pthread_t tid; pthread_create(&tid, NULL, start_server, NULL); + + uint16_t *nport = (uint16_t *) calloc(1, sizeof(uint16_t)); + *nport = ntohs(sa.u.sin.sin_port); + pthread_create(&tid, NULL, udpserver, nport); } Socket* WebsocketListener::createSocket(int fd) { diff --git a/common/network/Udp.cxx b/common/network/Udp.cxx new file mode 100644 index 0000000..ffcc825 --- /dev/null +++ b/common/network/Udp.cxx @@ -0,0 +1,155 @@ +/* Copyright (C) Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace network; + +static rfb::LogWriter vlog("WebUdp"); +static WuHost *host = NULL; + +rfb::IntParameter udpSize("udpSize", "UDP packet data size", 1300, 500, 1400); + +extern settings_t settings; + +static void udperr(const char *msg, void *) { + vlog.error("%s", msg); +} + +static void udpdebug(const char *msg, void *) { + vlog.debug("%s", msg); +} + +void *udpserver(void *nport) { + + WuHost *myhost = NULL; + int ret = WuHostCreate(rfb::Server::publicIP, *(uint16_t *) nport, 16, &myhost); + if (ret != WU_OK) { + vlog.error("Failed to create WebUDP host"); + return NULL; + } + + __sync_bool_compare_and_swap(&host, host, myhost); + + GetAPIMessager *msgr = (GetAPIMessager *) settings.messager; + + WuHostSetErrorCallback(host, udperr); + WuHostSetDebugCallback(host, udpdebug); + + while (1) { + WuAddress addr; + WuEvent e; + if (!WuHostServe(host, &e, 2000)) + continue; + + switch (e.type) { + case WuEvent_ClientJoin: + vlog.info("client join"); + addr = WuClientGetAddress(e.client); + msgr->netUdpUpgrade(e.client, htonl(addr.host)); + break; + case WuEvent_ClientLeave: + vlog.info("client leave"); + WuHostRemoveClient(host, e.client); + break; + default: + vlog.error("client sent data, this is unexpected"); + break; + } + } + + return NULL; +} + +// Send one packet, split into N UDP-sized pieces +static uint8_t udpsend(WuClient *client, const uint8_t *data, unsigned len, uint32_t *id) { + const uint32_t DATA_MAX = udpSize; + + uint8_t buf[1400 + sizeof(uint32_t) * 4]; + const uint32_t pieces = (len / DATA_MAX) + ((len % DATA_MAX) ? 1 : 0); + + uint32_t i; + + for (i = 0; i < pieces; i++) { + const unsigned curlen = len > DATA_MAX ? DATA_MAX : len; + const uint32_t hash = XXH64(data, curlen, 0); + + memcpy(buf, id, sizeof(uint32_t)); + memcpy(&buf[4], &i, sizeof(uint32_t)); + memcpy(&buf[8], &pieces, sizeof(uint32_t)); + memcpy(&buf[12], &hash, sizeof(uint32_t)); + + memcpy(&buf[16], data, curlen); + data += curlen; + len -= curlen; + + if (WuHostSendBinary(host, client, buf, curlen + sizeof(uint32_t) * 4) < 0) + return 1; + } + + (*id)++; + + return 0; +} + +UdpStream::UdpStream(): OutStream(), client(NULL), total_len(0), id(0) { + ptr = data; + end = data + UDPSTREAM_BUFSIZE; + + srand(time(NULL)); +} + +void UdpStream::flush() { + const unsigned len = ptr - data; + total_len += len; + + if (client) { + if (udpsend(client, data, len, &id)) + vlog.error("Error sending udp, client gone?"); + } else { + vlog.error("Tried to send udp without a client"); + } + + ptr = data; +} + +void UdpStream::overrun(size_t needed) { + vlog.error("Udp buffer overrun"); + abort(); +} + +void wuGotHttp(const char msg[], const uint32_t msglen, char resp[]) { + WuGotHttp(host, msg, msglen, resp); +} diff --git a/common/network/Udp.h b/common/network/Udp.h new file mode 100644 index 0000000..2ed73b0 --- /dev/null +++ b/common/network/Udp.h @@ -0,0 +1,53 @@ +/* Copyright (C) 2022 Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifndef __NETWORK_UDP_H__ +#define __NETWORK_UDP_H__ + +#include +#include + +void *udpserver(void *unused); +typedef struct WuClient WuClient; + +namespace network { + + #define UDPSTREAM_BUFSIZE (1024 * 1024) + + class UdpStream: public rdr::OutStream { + public: + UdpStream(); + virtual void flush(); + virtual size_t length() { return total_len; } + virtual void overrun(size_t needed); + + void setClient(WuClient *cli) { + client = cli; + } + private: + uint8_t data[UDPSTREAM_BUFSIZE]; + WuClient *client; + size_t total_len; + uint32_t id; + }; +} + +extern "C" void wuGotHttp(const char msg[], const uint32_t msglen, char resp[]); + + +#endif // __NETWORK_UDP_H__ diff --git a/common/network/iceip.cxx b/common/network/iceip.cxx new file mode 100644 index 0000000..2dc23b7 --- /dev/null +++ b/common/network/iceip.cxx @@ -0,0 +1,185 @@ +/* Copyright (C) Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +static rfb::LogWriter vlog("ICE"); + +// Default port 3478 +static const char * const servers[] = { + "stun.l.google.com:19302", + "stun1.l.google.com:19302", + "stun2.l.google.com:19302", + "stun3.l.google.com:19302", + "stun4.l.google.com:19302", + "stun.voipbuster.com", + "stun.voipstunt.com", +}; + +static bool tryserver(const char * const srv, const int sock) { + + unsigned port = 3478; + char addr[PATH_MAX]; + char buf[PATH_MAX]; + + const char *colon = strchr(srv, ':'); + if (colon) { + memcpy(addr, srv, colon - srv); + addr[colon - srv] = '\0'; + + colon++; + port = atoi(colon); + } else { + strcpy(addr, srv); + } + + vlog.debug("Trying '%s', port %u", addr, port); + + struct hostent *ent = gethostbyname2(addr, AF_INET); + if (!ent) + return false; + + struct sockaddr_in dst; + dst.sin_family = AF_INET; + dst.sin_port = htons(port); + memcpy(&dst.sin_addr, ent->h_addr, 4); + //vlog.info("Got %s, addr %s", ent->h_name, inet_ntoa(in)); + + // Build up a binding request packet + buf[0] = 0; + buf[1] = 1; // type + buf[2] = buf[3] = 0; // length + + uint32_t tid[4]; // transaction id, 128 bits + tid[0] = rand(); + tid[1] = rand(); + tid[2] = rand(); + tid[3] = rand(); + + memcpy(&buf[4], &tid[0], 4); + memcpy(&buf[8], &tid[1], 4); + memcpy(&buf[12], &tid[2], 4); + memcpy(&buf[16], &tid[3], 4); + + if (sendto(sock, buf, 20, 0, (const struct sockaddr *) &dst, + sizeof(struct sockaddr_in)) != 20) + return false; + + // Wait up to 10s for a reply, standard says that's the wait + struct pollfd pfd; + pfd.fd = sock; + pfd.events = POLLIN; + if (poll(&pfd, 1, 10 * 1000) <= 0) + return false; + + struct sockaddr_in from; + socklen_t socklen = sizeof(struct sockaddr_in); + int len = recvfrom(sock, buf, PATH_MAX, 0, (struct sockaddr *) &from, + &socklen); + if (len < 20) + return false; + if (memcmp(&from.sin_addr, &dst.sin_addr, sizeof(struct in_addr))) + return false; + + int i; +/* vlog.info("Got %u bytes", len); + for (i = 0; i < len; i++) + vlog.info("0x%02x,", buf[i]);*/ + + if (buf[0] != 1 || buf[1] != 1) + return false; // type not binding response + + // Parse attrs + for (i = 20; i < len;) { + uint16_t type, attrlen; + memcpy(&type, &buf[i], 2); + i += 2; + memcpy(&attrlen, &buf[i], 2); + i += 2; + + type = ntohs(type); + attrlen = ntohs(attrlen); + if (type != 1) { + // Not mapped-address + i += attrlen; + continue; + } + + // Yay, we got a response + i += 4; + struct in_addr in; + memcpy(&in.s_addr, &buf[i], 4); + + rfb::Server::publicIP.setParam(inet_ntoa(in)); + + vlog.info("My public IP is %s", (const char *) rfb::Server::publicIP); + + return true; + } + + return false; +} + +void getPublicIP() { + if (rfb::Server::publicIP[0]) { + vlog.info("Using public IP %s from args", + (const char *) rfb::Server::publicIP); + return; + } + + srand(time(NULL)); + + vlog.info("Querying public IP..."); + + const int sock = socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) + abort(); + + unsigned i; + for (i = 0; i < sizeof(servers) / sizeof(servers[0]); i++) { + if (tryserver(servers[i], sock)) + break; + vlog.info("STUN server %u didn't work, trying next...", i); + } + + close(sock); + + if (!rfb::Server::publicIP[0]) { + vlog.error("Failed to get public IP, please specify it with -publicIP"); + exit(1); + } +} diff --git a/common/network/iceip.h b/common/network/iceip.h new file mode 100644 index 0000000..680b8f6 --- /dev/null +++ b/common/network/iceip.h @@ -0,0 +1,23 @@ +/* Copyright (C) 2022 Kasm + * + * This is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This software is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this software; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, + * USA. + */ +#ifndef __ICEIP_H__ +#define __ICEIP_H__ + +void getPublicIP(); + +#endif diff --git a/common/network/websocket.c b/common/network/websocket.c index 4005d95..54707c4 100644 --- a/common/network/websocket.c +++ b/common/network/websocket.c @@ -41,7 +41,6 @@ * * Warning: not thread safe */ -int ssl_initialized = 0; int pipe_error = 0; settings_t settings; @@ -272,15 +271,6 @@ ws_ctx_t *ws_socket_ssl(ws_ctx_t *ctx, int socket, const char * certfile, const use_keyfile = certfile; } - // Initialize the library - if (! ssl_initialized) { - SSL_library_init(); - OpenSSL_add_all_algorithms(); - SSL_load_error_strings(); - ssl_initialized = 1; - - } - ctx->ssl_ctx = SSL_CTX_new(SSLv23_server_method()); if (ctx->ssl_ctx == NULL) { ERR_print_errors_fp(stderr); diff --git a/common/network/webudp/CHANGELOG.md b/common/network/webudp/CHANGELOG.md new file mode 100644 index 0000000..9759179 --- /dev/null +++ b/common/network/webudp/CHANGELOG.md @@ -0,0 +1,38 @@ +## 0.6.1 (06.06.2020) +- Fixed WuHostNull build. +- Use 2048 bit RSA keys (fixes `SSL_CTX_use_certificate:ee key too small`). + +## 0.6.0 (10.04.2020) +- Allow OpenSSL 1.1. +- Node addon: upgrade nan, now builds with node 12. +- Fixed compiler warnings. + +## 0.5.0 (27.10.2018) +- Switch to a newer SDP format. + Newer Chrome versions can connect to the server again. +- Update required CMake version to 3.7. + +## 0.4.1 (06.10.2018) +- Fix compilation with g++ 7. + +## 0.4.0 (02.10.2018) +- Add C API. +- WuHost now has an explicit timeout parameter. +- Remove ES6 'let' from wusocket.js. + +## 0.3.0 (16.07.2018) +- Fix potential out of bounds read when sending SDP response. + +## 0.2.0 (12.01.2018) +- Add DTLS 1.2 support. Requires at least OpenSSL 1.0.2. + +## 0.1.1 (01.01.2018) +- Fix WuConf uninitialized maxClients parameter. + +## 0.1.0 (30.12.2017) +- Remove the old default epoll implementation. +- Split the core logic into a separate library. +- Add a new epoll host. +- Add a Node.js host. +- Add fuzz tests. +- Add a Node.js example. diff --git a/common/network/webudp/CRC32.cpp b/common/network/webudp/CRC32.cpp new file mode 100644 index 0000000..4b60921 --- /dev/null +++ b/common/network/webudp/CRC32.cpp @@ -0,0 +1,124 @@ +#include "CRC32.h" + +static const uint32_t crc32Stun[] = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, + 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, + 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, + 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, + 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, + 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, + 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, + 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, + 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, + 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, + 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, + 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, + 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, + 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, + 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, + 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, + 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, + 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, + 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, + 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, + 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, + 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, + 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, + 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, + 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, + 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, + 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, + 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, + 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, + 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, + 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, + 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, + 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d}; + +static const unsigned long crc32Sctp[256] = { + 0x00000000, 0xF26B8303, 0xE13B70F7, 0x1350F3F4, 0xC79A971F, 0x35F1141C, + 0x26A1E7E8, 0xD4CA64EB, 0x8AD958CF, 0x78B2DBCC, 0x6BE22838, 0x9989AB3B, + 0x4D43CFD0, 0xBF284CD3, 0xAC78BF27, 0x5E133C24, 0x105EC76F, 0xE235446C, + 0xF165B798, 0x030E349B, 0xD7C45070, 0x25AFD373, 0x36FF2087, 0xC494A384, + 0x9A879FA0, 0x68EC1CA3, 0x7BBCEF57, 0x89D76C54, 0x5D1D08BF, 0xAF768BBC, + 0xBC267848, 0x4E4DFB4B, 0x20BD8EDE, 0xD2D60DDD, 0xC186FE29, 0x33ED7D2A, + 0xE72719C1, 0x154C9AC2, 0x061C6936, 0xF477EA35, 0xAA64D611, 0x580F5512, + 0x4B5FA6E6, 0xB93425E5, 0x6DFE410E, 0x9F95C20D, 0x8CC531F9, 0x7EAEB2FA, + 0x30E349B1, 0xC288CAB2, 0xD1D83946, 0x23B3BA45, 0xF779DEAE, 0x05125DAD, + 0x1642AE59, 0xE4292D5A, 0xBA3A117E, 0x4851927D, 0x5B016189, 0xA96AE28A, + 0x7DA08661, 0x8FCB0562, 0x9C9BF696, 0x6EF07595, 0x417B1DBC, 0xB3109EBF, + 0xA0406D4B, 0x522BEE48, 0x86E18AA3, 0x748A09A0, 0x67DAFA54, 0x95B17957, + 0xCBA24573, 0x39C9C670, 0x2A993584, 0xD8F2B687, 0x0C38D26C, 0xFE53516F, + 0xED03A29B, 0x1F682198, 0x5125DAD3, 0xA34E59D0, 0xB01EAA24, 0x42752927, + 0x96BF4DCC, 0x64D4CECF, 0x77843D3B, 0x85EFBE38, 0xDBFC821C, 0x2997011F, + 0x3AC7F2EB, 0xC8AC71E8, 0x1C661503, 0xEE0D9600, 0xFD5D65F4, 0x0F36E6F7, + 0x61C69362, 0x93AD1061, 0x80FDE395, 0x72966096, 0xA65C047D, 0x5437877E, + 0x4767748A, 0xB50CF789, 0xEB1FCBAD, 0x197448AE, 0x0A24BB5A, 0xF84F3859, + 0x2C855CB2, 0xDEEEDFB1, 0xCDBE2C45, 0x3FD5AF46, 0x7198540D, 0x83F3D70E, + 0x90A324FA, 0x62C8A7F9, 0xB602C312, 0x44694011, 0x5739B3E5, 0xA55230E6, + 0xFB410CC2, 0x092A8FC1, 0x1A7A7C35, 0xE811FF36, 0x3CDB9BDD, 0xCEB018DE, + 0xDDE0EB2A, 0x2F8B6829, 0x82F63B78, 0x709DB87B, 0x63CD4B8F, 0x91A6C88C, + 0x456CAC67, 0xB7072F64, 0xA457DC90, 0x563C5F93, 0x082F63B7, 0xFA44E0B4, + 0xE9141340, 0x1B7F9043, 0xCFB5F4A8, 0x3DDE77AB, 0x2E8E845F, 0xDCE5075C, + 0x92A8FC17, 0x60C37F14, 0x73938CE0, 0x81F80FE3, 0x55326B08, 0xA759E80B, + 0xB4091BFF, 0x466298FC, 0x1871A4D8, 0xEA1A27DB, 0xF94AD42F, 0x0B21572C, + 0xDFEB33C7, 0x2D80B0C4, 0x3ED04330, 0xCCBBC033, 0xA24BB5A6, 0x502036A5, + 0x4370C551, 0xB11B4652, 0x65D122B9, 0x97BAA1BA, 0x84EA524E, 0x7681D14D, + 0x2892ED69, 0xDAF96E6A, 0xC9A99D9E, 0x3BC21E9D, 0xEF087A76, 0x1D63F975, + 0x0E330A81, 0xFC588982, 0xB21572C9, 0x407EF1CA, 0x532E023E, 0xA145813D, + 0x758FE5D6, 0x87E466D5, 0x94B49521, 0x66DF1622, 0x38CC2A06, 0xCAA7A905, + 0xD9F75AF1, 0x2B9CD9F2, 0xFF56BD19, 0x0D3D3E1A, 0x1E6DCDEE, 0xEC064EED, + 0xC38D26C4, 0x31E6A5C7, 0x22B65633, 0xD0DDD530, 0x0417B1DB, 0xF67C32D8, + 0xE52CC12C, 0x1747422F, 0x49547E0B, 0xBB3FFD08, 0xA86F0EFC, 0x5A048DFF, + 0x8ECEE914, 0x7CA56A17, 0x6FF599E3, 0x9D9E1AE0, 0xD3D3E1AB, 0x21B862A8, + 0x32E8915C, 0xC083125F, 0x144976B4, 0xE622F5B7, 0xF5720643, 0x07198540, + 0x590AB964, 0xAB613A67, 0xB831C993, 0x4A5A4A90, 0x9E902E7B, 0x6CFBAD78, + 0x7FAB5E8C, 0x8DC0DD8F, 0xE330A81A, 0x115B2B19, 0x020BD8ED, 0xF0605BEE, + 0x24AA3F05, 0xD6C1BC06, 0xC5914FF2, 0x37FACCF1, 0x69E9F0D5, 0x9B8273D6, + 0x88D28022, 0x7AB90321, 0xAE7367CA, 0x5C18E4C9, 0x4F48173D, 0xBD23943E, + 0xF36E6F75, 0x0105EC76, 0x12551F82, 0xE03E9C81, 0x34F4F86A, 0xC69F7B69, + 0xD5CF889D, 0x27A40B9E, 0x79B737BA, 0x8BDCB4B9, 0x988C474D, 0x6AE7C44E, + 0xBE2DA0A5, 0x4C4623A6, 0x5F16D052, 0xAD7D5351}; + +uint32_t StunCRC32(const void* data, int32_t len) { + uint32_t crc = 0xffffffff; + + const uint8_t* p = (const uint8_t*)data; + + while (len--) { + uint32_t lkp = crc32Stun[(crc ^ *p++) & 0xFF]; + crc = lkp ^ (crc >> 8); + } + + return crc ^ 0xffffffff; +} + +#define CRC32C(c, d) (c = (c >> 8) ^ (crc32Sctp)[(c ^ (d)) & 0xFF]) + +uint32_t SctpCRC32(const void* data, int32_t len) { + uint32_t crc = 0xFFFFFFFF; + + const uint8_t* p = (const uint8_t*)data; + + for (int32_t i = 0; i < len; i++) { + CRC32C(crc, p[i]); + } + + uint32_t result = ~crc; + uint8_t byte0 = result & 0xff; + uint8_t byte1 = (result >> 8) & 0xff; + uint8_t byte2 = (result >> 16) & 0xff; + uint8_t byte3 = (result >> 24) & 0xff; + result = ((byte0 << 24) | (byte1 << 16) | (byte2 << 8) | byte3); + return result; +} diff --git a/common/network/webudp/CRC32.h b/common/network/webudp/CRC32.h new file mode 100644 index 0000000..bb00af4 --- /dev/null +++ b/common/network/webudp/CRC32.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include + +uint32_t StunCRC32(const void* data, int32_t len); +uint32_t SctpCRC32(const void* data, int32_t len); diff --git a/common/network/webudp/LICENSE b/common/network/webudp/LICENSE new file mode 100644 index 0000000..def73f9 --- /dev/null +++ b/common/network/webudp/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Siim Kallas + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/common/network/webudp/README.md b/common/network/webudp/README.md new file mode 100644 index 0000000..3b3f295 --- /dev/null +++ b/common/network/webudp/README.md @@ -0,0 +1,20 @@ +# WebUDP +WebRTC datachannel library and server + +[Echo server demo](https://www.vektor.space/webudprtt.html) (Chrome, Firefox, Safari 11+) + +The library implements a minimal subset of WebRTC to achieve unreliable and out of order UDP transfer for browser clients. The core library (Wu) is platform independent. Refer to WuHostEpoll or WuHostNode for usage. + +## Building +```bash +mkdir build && cd build +cmake .. +make +``` + +### Host platforms +* Linux (epoll) +* Node.js ```-DWITH_NODE=ON``` + +### Issues +* Firefox doesn't connect to a server running on localhost. Bind a different interface. diff --git a/common/network/webudp/Wu.cpp b/common/network/webudp/Wu.cpp new file mode 100644 index 0000000..c9430a5 --- /dev/null +++ b/common/network/webudp/Wu.cpp @@ -0,0 +1,771 @@ +#include "Wu.h" +#include +#include +#include +#include +#include +#include "WuArena.h" +#include "WuClock.h" +#include "WuCrypto.h" +#include "WuMath.h" +#include "WuPool.h" +#include "WuQueue.h" +#include "WuRng.h" +#include "WuSctp.h" +#include "WuSdp.h" +#include "WuStun.h" + +struct Wu { + WuArena* arena; + double time; + double dt; + char host[256]; + uint16_t port; + WuQueue* pendingEvents; + int32_t maxClients; + int32_t numClients; + + WuPool* clientPool; + WuClient** clients; + ssl_ctx_st* sslCtx; + + char certFingerprint[96]; + + char errBuf[512]; + void* userData; + WuErrorFn errorCallback; + WuErrorFn debugCallback; + WuWriteFn writeUdpData; +}; + +const double kMaxClientTtl = 9.0; +const double heartbeatInterval = 4.0; +const int kDefaultMTU = 1400; + +static void DefaultErrorCallback(const char*, void*) {} +static void WriteNothing(const uint8_t*, size_t, const WuClient*, void*) {} + +enum DataChannelMessageType { DCMessage_Ack = 0x02, DCMessage_Open = 0x03 }; + +enum DataChanProtoIdentifier { + DCProto_Control = 50, + DCProto_String = 51, + DCProto_Binary = 53, + DCProto_EmptyString = 56, + DCProto_EmptyBinary = 57 +}; + +struct DataChannelPacket { + uint8_t messageType; + + union { + struct { + uint8_t channelType; + uint16_t priority; + uint32_t reliability; + } open; + } as; +}; + +enum WuClientState { + WuClient_Dead, + WuClient_WaitingRemoval, + WuClient_DTLSHandshake, + WuClient_SCTPEstablished, + WuClient_DataChannelOpen +}; + +static int32_t ParseDataChannelControlPacket(const uint8_t* buf, size_t len, + DataChannelPacket* packet) { + ReadScalarSwapped(buf, &packet->messageType); + return 0; +} + +void WuReportError(Wu* wu, const char* description) { + wu->errorCallback(description, wu->userData); +} + +void WuReportDebug(Wu* wu, const char* description) { + wu->debugCallback(description, wu->userData); +} + +struct WuClient { + StunUserIdentifier serverUser; + StunUserIdentifier serverPassword; + StunUserIdentifier remoteUser; + StunUserIdentifier remoteUserPassword; + WuAddress address; + WuClientState state; + uint16_t localSctpPort; + uint16_t remoteSctpPort; + uint32_t sctpVerificationTag; + uint32_t remoteTsn; + uint32_t tsn; + double ttl; + double nextHeartbeat; + + SSL* ssl; + BIO* inBio; + BIO* outBio; + + void* user; +}; + +void WuClientSetUserData(WuClient* client, void* user) { client->user = user; } + +void* WuClientGetUserData(const WuClient* client) { return client->user; } + +static void WuClientFinish(WuClient* client) { + SSL_free(client->ssl); + client->ssl = NULL; + client->inBio = NULL; + client->outBio = NULL; + client->state = WuClient_Dead; +} + +static void WuClientStart(const Wu* wu, WuClient* client) { + client->state = WuClient_DTLSHandshake; + client->remoteSctpPort = 0; + client->sctpVerificationTag = 0; + client->remoteTsn = 0; + client->tsn = 1; + client->ttl = kMaxClientTtl; + client->nextHeartbeat = heartbeatInterval; + client->user = NULL; + + client->ssl = SSL_new(wu->sslCtx); + + client->inBio = BIO_new(BIO_s_mem()); + BIO_set_mem_eof_return(client->inBio, -1); + client->outBio = BIO_new(BIO_s_mem()); + BIO_set_mem_eof_return(client->outBio, -1); + SSL_set_bio(client->ssl, client->inBio, client->outBio); + SSL_set_options(client->ssl, SSL_OP_SINGLE_ECDH_USE); + SSL_set_options(client->ssl, SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); + SSL_set_tmp_ecdh(client->ssl, EC_KEY_new_by_curve_name(NID_X9_62_prime256v1)); + SSL_set_accept_state(client->ssl); + SSL_set_mtu(client->ssl, kDefaultMTU); +} + +static void WuSendSctp(const Wu* wu, WuClient* client, const SctpPacket* packet, + const SctpChunk* chunks, int32_t numChunks); + +static WuClient* WuNewClient(Wu* wu) { + WuClient* client = (WuClient*)WuPoolAcquire(wu->clientPool); + + if (client) { + memset(client, 0, sizeof(WuClient)); + WuClientStart(wu, client); + wu->clients[wu->numClients++] = client; + return client; + } + + return NULL; +} + +static void WuPushEvent(Wu* wu, WuEvent evt) { + WuQueuePush(wu->pendingEvents, &evt); +} + +static void WuSendSctpShutdown(Wu* wu, WuClient* client) { + SctpPacket response; + response.sourcePort = client->localSctpPort; + response.destionationPort = client->remoteSctpPort; + response.verificationTag = client->sctpVerificationTag; + + SctpChunk rc; + rc.type = Sctp_Shutdown; + rc.flags = 0; + rc.length = SctpChunkLength(sizeof(rc.as.shutdown.cumulativeTsnAck)); + rc.as.shutdown.cumulativeTsnAck = client->remoteTsn; + + WuSendSctp(wu, client, &response, &rc, 1); +} + +void WuRemoveClient(Wu* wu, WuClient* client) { + for (int32_t i = 0; i < wu->numClients; i++) { + if (wu->clients[i] == client) { + WuSendSctpShutdown(wu, client); + WuClientFinish(client); + WuPoolRelease(wu->clientPool, client); + wu->clients[i] = wu->clients[wu->numClients - 1]; + wu->numClients--; + return; + } + } +} + +static WuClient* WuFindClient(Wu* wu, const WuAddress* address) { + for (int32_t i = 0; i < wu->numClients; i++) { + WuClient* client = wu->clients[i]; + if (client->address.host == address->host && + client->address.port == address->port) { + return client; + } + } + + return NULL; +} + +static WuClient* WuFindClientByCreds(Wu* wu, const StunUserIdentifier* svUser, + const StunUserIdentifier* clUser) { + for (int32_t i = 0; i < wu->numClients; i++) { + WuClient* client = wu->clients[i]; + if (StunUserIdentifierEqual(&client->serverUser, svUser) && + StunUserIdentifierEqual(&client->remoteUser, clUser)) { + return client; + } + } + + return NULL; +} + +static void WuClientSendPendingDTLS(const Wu* wu, WuClient* client) { + uint8_t sendBuffer[4096]; + + while (BIO_ctrl_pending(client->outBio) > 0) { + int bytes = BIO_read(client->outBio, sendBuffer, sizeof(sendBuffer)); + if (bytes > 0) { + wu->writeUdpData(sendBuffer, bytes, client, wu->userData); + } + } +} + +static void TLSSend(const Wu* wu, WuClient* client, const void* data, + int32_t length) { + if (client->state < WuClient_DTLSHandshake || + !SSL_is_init_finished(client->ssl)) { + return; + } + + SSL_write(client->ssl, data, length); + WuClientSendPendingDTLS(wu, client); +} + +static void WuSendSctp(const Wu* wu, WuClient* client, const SctpPacket* packet, + const SctpChunk* chunks, int32_t numChunks) { + uint8_t outBuffer[4096]; + memset(outBuffer, 0, sizeof(outBuffer)); + size_t bytesWritten = SerializeSctpPacket(packet, chunks, numChunks, + outBuffer, sizeof(outBuffer)); + TLSSend(wu, client, outBuffer, bytesWritten); +} + +static void WuHandleSctp(Wu* wu, WuClient* client, const uint8_t* buf, + int32_t len) { + const size_t maxChunks = 8; + SctpChunk chunks[maxChunks]; + SctpPacket sctpPacket; + size_t nChunk = 0; + + if (!ParseSctpPacket(buf, len, &sctpPacket, chunks, maxChunks, &nChunk)) { + return; + } + + for (size_t n = 0; n < nChunk; n++) { + SctpChunk* chunk = &chunks[n]; + if (chunk->type == Sctp_Data) { + auto* dataChunk = &chunk->as.data; + const uint8_t* userDataBegin = dataChunk->userData; + const int32_t userDataLength = dataChunk->userDataLength; + + client->remoteTsn = Max(chunk->as.data.tsn, client->remoteTsn); + client->ttl = kMaxClientTtl; + + if (dataChunk->protoId == DCProto_Control) { + DataChannelPacket packet; + ParseDataChannelControlPacket(userDataBegin, userDataLength, &packet); + if (packet.messageType == DCMessage_Open) { + client->remoteSctpPort = sctpPacket.sourcePort; + uint8_t outType = DCMessage_Ack; + SctpPacket response; + response.sourcePort = sctpPacket.destionationPort; + response.destionationPort = sctpPacket.sourcePort; + response.verificationTag = client->sctpVerificationTag; + + SctpChunk rc; + rc.type = Sctp_Data; + rc.flags = kSctpFlagCompleteUnreliable; + rc.length = SctpDataChunkLength(1); + + auto* dc = &rc.as.data; + dc->tsn = client->tsn++; + dc->streamId = chunk->as.data.streamId; + dc->streamSeq = 0; + dc->protoId = DCProto_Control; + dc->userData = &outType; + dc->userDataLength = 1; + + if (client->state != WuClient_DataChannelOpen) { + client->state = WuClient_DataChannelOpen; + WuEvent event; + event.type = WuEvent_ClientJoin; + event.client = client; + WuPushEvent(wu, event); + } + + WuSendSctp(wu, client, &response, &rc, 1); + } + } else if (dataChunk->protoId == DCProto_String) { + WuEvent evt; + evt.type = WuEvent_TextData; + evt.client = client; + evt.data = dataChunk->userData; + evt.length = dataChunk->userDataLength; + WuPushEvent(wu, evt); + } else if (dataChunk->protoId == DCProto_Binary) { + WuEvent evt; + evt.type = WuEvent_BinaryData; + evt.client = client; + evt.data = dataChunk->userData; + evt.length = dataChunk->userDataLength; + WuPushEvent(wu, evt); + } + + SctpPacket sack; + sack.sourcePort = sctpPacket.destionationPort; + sack.destionationPort = sctpPacket.sourcePort; + sack.verificationTag = client->sctpVerificationTag; + + SctpChunk rc; + rc.type = Sctp_Sack; + rc.flags = 0; + rc.length = SctpChunkLength(12); + rc.as.sack.cumulativeTsnAck = client->remoteTsn; + rc.as.sack.advRecvWindow = kSctpDefaultBufferSpace; + rc.as.sack.numGapAckBlocks = 0; + rc.as.sack.numDupTsn = 0; + + WuSendSctp(wu, client, &sack, &rc, 1); + } else if (chunk->type == Sctp_Init) { + SctpPacket response; + response.sourcePort = sctpPacket.destionationPort; + response.destionationPort = sctpPacket.sourcePort; + response.verificationTag = chunk->as.init.initiateTag; + client->sctpVerificationTag = response.verificationTag; + client->remoteTsn = chunk->as.init.initialTsn - 1; + + SctpChunk rc; + rc.type = Sctp_InitAck; + rc.flags = 0; + rc.length = kSctpMinInitAckLength; + + rc.as.init.initiateTag = WuRandomU32(); + rc.as.init.windowCredit = kSctpDefaultBufferSpace; + rc.as.init.numOutboundStreams = chunk->as.init.numInboundStreams; + rc.as.init.numInboundStreams = chunk->as.init.numOutboundStreams; + rc.as.init.initialTsn = client->tsn; + + WuSendSctp(wu, client, &response, &rc, 1); + break; + } else if (chunk->type == Sctp_CookieEcho) { + if (client->state < WuClient_SCTPEstablished) { + client->state = WuClient_SCTPEstablished; + } + SctpPacket response; + response.sourcePort = sctpPacket.destionationPort; + response.destionationPort = sctpPacket.sourcePort; + response.verificationTag = client->sctpVerificationTag; + + SctpChunk rc; + rc.type = Sctp_CookieAck; + rc.flags = 0; + rc.length = SctpChunkLength(0); + + WuSendSctp(wu, client, &response, &rc, 1); + } else if (chunk->type == Sctp_Heartbeat) { + SctpPacket response; + response.sourcePort = sctpPacket.destionationPort; + response.destionationPort = sctpPacket.sourcePort; + response.verificationTag = client->sctpVerificationTag; + + SctpChunk rc; + rc.type = Sctp_HeartbeatAck; + rc.flags = 0; + rc.length = chunk->length; + rc.as.heartbeat.heartbeatInfoLen = chunk->as.heartbeat.heartbeatInfoLen; + rc.as.heartbeat.heartbeatInfo = chunk->as.heartbeat.heartbeatInfo; + + client->ttl = kMaxClientTtl; + + WuSendSctp(wu, client, &response, &rc, 1); + } else if (chunk->type == Sctp_HeartbeatAck) { + client->ttl = kMaxClientTtl; + } else if (chunk->type == Sctp_Abort) { + client->state = WuClient_WaitingRemoval; + return; + } else if (chunk->type == Sctp_Sack) { + client->ttl = kMaxClientTtl; + + auto* sack = &chunk->as.sack; + if (sack->numGapAckBlocks > 0) { + SctpPacket fwdResponse; + fwdResponse.sourcePort = sctpPacket.destionationPort; + fwdResponse.destionationPort = sctpPacket.sourcePort; + fwdResponse.verificationTag = client->sctpVerificationTag; + + SctpChunk fwdTsnChunk; + fwdTsnChunk.type = SctpChunk_ForwardTsn; + fwdTsnChunk.flags = 0; + fwdTsnChunk.length = SctpChunkLength(4); + fwdTsnChunk.as.forwardTsn.newCumulativeTsn = client->tsn; + WuSendSctp(wu, client, &fwdResponse, &fwdTsnChunk, 1); + } + } + } +} + +static void WuReceiveDTLSPacket(Wu* wu, const uint8_t* data, size_t length, + const WuAddress* address) { + WuClient* client = WuFindClient(wu, address); + if (!client) { + WuReportDebug(wu, "DTLS: No client found"); + return; + } + + BIO_write(client->inBio, data, length); + + if (!SSL_is_init_finished(client->ssl)) { + int r = SSL_do_handshake(client->ssl); + + if (r <= 0) { + r = SSL_get_error(client->ssl, r); + if (SSL_ERROR_WANT_READ == r) { + WuClientSendPendingDTLS(wu, client); + } else if (SSL_ERROR_NONE != r) { + char* error = ERR_error_string(r, NULL); + if (error) { + WuReportError(wu, error); + } + } + } + } else { + WuClientSendPendingDTLS(wu, client); + + while (BIO_ctrl_pending(client->inBio) > 0) { + uint8_t receiveBuffer[8092]; + int bytes = SSL_read(client->ssl, receiveBuffer, sizeof(receiveBuffer)); + + if (bytes > 0) { + uint8_t* buf = (uint8_t*)WuArenaAcquire(wu->arena, bytes); + memcpy(buf, receiveBuffer, bytes); + WuHandleSctp(wu, client, buf, bytes); + } + } + } +} + +static void WuHandleStun(Wu* wu, const StunPacket* packet, + const WuAddress* remote) { + WuClient* client = + WuFindClientByCreds(wu, &packet->serverUser, &packet->remoteUser); + + if (!client) { + WuReportDebug(wu, "Stun: No client found"); + // TODO: Send unauthorized + return; + } + + StunPacket outPacket; + outPacket.type = Stun_SuccessResponse; + memcpy(outPacket.transactionId, packet->transactionId, + kStunTransactionIdLength); + outPacket.xorMappedAddress.family = Stun_IPV4; + outPacket.xorMappedAddress.port = ByteSwap(remote->port ^ kStunXorMagic); + outPacket.xorMappedAddress.address.ipv4 = + ByteSwap(remote->host ^ kStunCookie); + + uint8_t stunResponse[512]; + size_t serializedSize = + SerializeStunPacket(&outPacket, client->serverPassword.identifier, + client->serverPassword.length, stunResponse, 512); + + client->localSctpPort = remote->port; + client->address = *remote; + + wu->writeUdpData(stunResponse, serializedSize, client, wu->userData); +} + +static void WuPurgeDeadClients(Wu* wu) { + for (int32_t i = 0; i < wu->numClients; i++) { + WuClient* client = wu->clients[i]; + if (client->ttl <= 0.0 || client->state == WuClient_WaitingRemoval) { + + if (client->ttl <= 0.0) + WuReportDebug(wu, "Removing dead client due to no messages in 9s"); + else + WuReportDebug(wu, "Removing client due to its own request"); + + WuEvent evt; + evt.type = WuEvent_ClientLeave; + evt.client = client; + WuPushEvent(wu, evt); + } + } +} + +static int32_t WuCryptoInit(Wu* wu) { + + wu->sslCtx = SSL_CTX_new(DTLS_server_method()); + if (!wu->sslCtx) { + ERR_print_errors_fp(stderr); + return 0; + } + + int sslStatus = + SSL_CTX_set_cipher_list(wu->sslCtx, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH"); + if (sslStatus != 1) { + ERR_print_errors_fp(stderr); + return 0; + } + + SSL_CTX_set_verify(wu->sslCtx, SSL_VERIFY_NONE, NULL); + + WuCert cert; + + sslStatus = SSL_CTX_use_PrivateKey(wu->sslCtx, cert.key); + + if (sslStatus != 1) { + ERR_print_errors_fp(stderr); + return 0; + } + + sslStatus = SSL_CTX_use_certificate(wu->sslCtx, cert.x509); + + if (sslStatus != 1) { + ERR_print_errors_fp(stderr); + return 0; + } + + sslStatus = SSL_CTX_check_private_key(wu->sslCtx); + + if (sslStatus != 1) { + ERR_print_errors_fp(stderr); + return 0; + } + + SSL_CTX_set_options(wu->sslCtx, SSL_OP_NO_QUERY_MTU); + + memcpy(wu->certFingerprint, cert.fingerprint, sizeof(cert.fingerprint)); + + return 1; +} + +int32_t WuCreate(const char* host, uint16_t port, int maxClients, Wu** wu) { + *wu = NULL; + + Wu* ctx = (Wu*)calloc(1, sizeof(Wu)); + + if (!ctx) { + return WU_OUT_OF_MEMORY; + } + + ctx->arena = (WuArena*)calloc(1, sizeof(WuArena)); + + if (!ctx->arena) { + WuDestroy(ctx); + return WU_OUT_OF_MEMORY; + } + + WuArenaInit(ctx->arena, 1 << 20); + + ctx->time = MsNow() * 0.001; + ctx->port = port; + ctx->pendingEvents = WuQueueCreate(sizeof(WuEvent), 1024); + ctx->errorCallback = DefaultErrorCallback; + ctx->debugCallback = DefaultErrorCallback; + ctx->writeUdpData = WriteNothing; + + strncpy(ctx->host, host, sizeof(ctx->host)); + + if (!WuCryptoInit(ctx)) { + WuDestroy(ctx); + return WU_ERROR; + } + + ctx->maxClients = maxClients <= 0 ? 256 : maxClients; + ctx->clientPool = WuPoolCreate(sizeof(WuClient), ctx->maxClients); + ctx->clients = (WuClient**)calloc(ctx->maxClients, sizeof(WuClient*)); + + *wu = ctx; + return WU_OK; +} + +static void WuSendHeartbeat(Wu* wu, WuClient* client) { + SctpPacket packet; + packet.sourcePort = wu->port; + packet.destionationPort = client->remoteSctpPort; + packet.verificationTag = client->sctpVerificationTag; + + SctpChunk rc; + rc.type = Sctp_Heartbeat; + rc.flags = kSctpFlagCompleteUnreliable; + rc.length = SctpChunkLength(4 + 8); + rc.as.heartbeat.heartbeatInfo = (const uint8_t*)&wu->time; + rc.as.heartbeat.heartbeatInfoLen = sizeof(wu->time); + + WuSendSctp(wu, client, &packet, &rc, 1); +} + +static void WuUpdateClients(Wu* wu) { + double t = MsNow() * 0.001; + wu->dt = t - wu->time; + wu->time = t; + + for (int32_t i = 0; i < wu->numClients; i++) { + WuClient* client = wu->clients[i]; + client->ttl -= wu->dt; + client->nextHeartbeat -= wu->dt; + + if (client->nextHeartbeat <= 0.0) { + client->nextHeartbeat = heartbeatInterval; + WuSendHeartbeat(wu, client); + } + + WuClientSendPendingDTLS(wu, client); + } +} + +int32_t WuUpdate(Wu* wu, WuEvent* evt) { + if (WuQueuePop(wu->pendingEvents, evt)) { + return 1; + } + + WuUpdateClients(wu); + WuArenaReset(wu->arena); + + WuPurgeDeadClients(wu); + + return 0; +} + +static int32_t WuSendData(Wu* wu, WuClient* client, const uint8_t* data, + int32_t length, DataChanProtoIdentifier proto) { + if (client->state < WuClient_DataChannelOpen) { + return -1; + } + + SctpPacket packet; + packet.sourcePort = wu->port; + packet.destionationPort = client->remoteSctpPort; + packet.verificationTag = client->sctpVerificationTag; + + SctpChunk rc; + rc.type = Sctp_Data; + rc.flags = kSctpFlagCompleteUnreliable; + rc.length = SctpDataChunkLength(length); + + auto* dc = &rc.as.data; + dc->tsn = client->tsn++; + dc->streamId = 0; // TODO: Does it matter? + dc->streamSeq = 0; + dc->protoId = proto; + dc->userData = data; + dc->userDataLength = length; + + WuSendSctp(wu, client, &packet, &rc, 1); + return 0; +} + +int32_t WuSendText(Wu* wu, WuClient* client, const char* text, int32_t length) { + return WuSendData(wu, client, (const uint8_t*)text, length, DCProto_String); +} + +int32_t WuSendBinary(Wu* wu, WuClient* client, const uint8_t* data, + int32_t length) { + return WuSendData(wu, client, data, length, DCProto_Binary); +} + +SDPResult WuExchangeSDP(Wu* wu, const char* sdp, int32_t length) { + ICESdpFields iceFields; + if (!ParseSdp(sdp, length, &iceFields)) { + return {WuSDPStatus_InvalidSDP, NULL, NULL, 0}; + } + + WuClient* client = WuNewClient(wu); + + if (!client) { + return {WuSDPStatus_MaxClients, NULL, NULL, 0}; + } + + client->serverUser.length = 4; + WuRandomString((char*)client->serverUser.identifier, + client->serverUser.length); + client->serverPassword.length = 24; + WuRandomString((char*)client->serverPassword.identifier, + client->serverPassword.length); + memcpy(client->remoteUser.identifier, iceFields.ufrag.value, + Min(iceFields.ufrag.length, kMaxStunIdentifierLength)); + client->remoteUser.length = iceFields.ufrag.length; + memcpy(client->remoteUserPassword.identifier, iceFields.password.value, + Min(iceFields.password.length, kMaxStunIdentifierLength)); + + int sdpLength = 0; + const char* responseSdp = GenerateSDP( + wu->arena, wu->certFingerprint, wu->host, wu->port, + (char*)client->serverUser.identifier, client->serverUser.length, + (char*)client->serverPassword.identifier, client->serverPassword.length, + &iceFields, &sdpLength); + + if (!responseSdp) { + return {WuSDPStatus_Error, NULL, NULL, 0}; + } + + return {WuSDPStatus_Success, client, responseSdp, sdpLength}; +} + +void WuSetUserData(Wu* wu, void* userData) { wu->userData = userData; } + +void WuHandleUDP(Wu* wu, const WuAddress* remote, const uint8_t* data, + int32_t length) { + StunPacket stunPacket; + if (ParseStun(data, length, &stunPacket)) { + //WuReportDebug(wu, "Received stun packet"); + WuHandleStun(wu, &stunPacket, remote); + } else { + //WuReportDebug(wu, "Received DTLS packet"); + WuReceiveDTLSPacket(wu, data, length, remote); + } +} + +void WuSetUDPWriteFunction(Wu* wu, WuWriteFn write) { + wu->writeUdpData = write; +} + +WuAddress WuClientGetAddress(const WuClient* client) { return client->address; } + +void WuSetErrorCallback(Wu* wu, WuErrorFn callback) { + if (callback) { + wu->errorCallback = callback; + } else { + wu->errorCallback = DefaultErrorCallback; + } +} + +void WuSetDebugCallback(Wu* wu, WuErrorFn callback) { + if (callback) { + wu->debugCallback = callback; + } else { + wu->debugCallback = DefaultErrorCallback; + } +} + +void WuDestroy(Wu* wu) { + if (!wu) { + return; + } + + free(wu); +} + +WuClient* WuFindClient(const Wu* wu, WuAddress address) { + for (int32_t i = 0; i < wu->numClients; i++) { + WuClient* c = wu->clients[i]; + + if (c->address.host == address.host && c->address.port == address.port) { + return c; + } + } + + return NULL; +} diff --git a/common/network/webudp/Wu.h b/common/network/webudp/Wu.h new file mode 100644 index 0000000..7bea66a --- /dev/null +++ b/common/network/webudp/Wu.h @@ -0,0 +1,76 @@ +#pragma once + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define WU_OK 0 +#define WU_ERROR 1 +#define WU_OUT_OF_MEMORY 2 + +typedef struct WuClient WuClient; +typedef struct Wu Wu; +typedef void (*WuErrorFn)(const char* err, void* userData); +typedef void (*WuWriteFn)(const uint8_t* data, size_t length, + const WuClient* client, void* userData); + +typedef enum { + WuEvent_BinaryData, + WuEvent_ClientJoin, + WuEvent_ClientLeave, + WuEvent_TextData +} WuEventType; + +typedef enum { + WuSDPStatus_Success, + WuSDPStatus_InvalidSDP, + WuSDPStatus_MaxClients, + WuSDPStatus_Error +} WuSDPStatus; + +typedef struct { + WuEventType type; + WuClient* client; + const uint8_t* data; + int32_t length; +} WuEvent; + +typedef struct { + WuSDPStatus status; + WuClient* client; + const char* sdp; + int32_t sdpLength; +} SDPResult; + +typedef struct { + uint32_t host; + uint16_t port; +} WuAddress; + +int32_t WuCreate(const char* host, uint16_t port, int maxClients, Wu** wu); +void WuDestroy(Wu* wu); +int32_t WuUpdate(Wu* wu, WuEvent* evt); +int32_t WuSendText(Wu* wu, WuClient* client, const char* text, int32_t length); +int32_t WuSendBinary(Wu* wu, WuClient* client, const uint8_t* data, + int32_t length); +void WuReportError(Wu* wu, const char* error); +void WuReportDebug(Wu* wu, const char* error); +void WuRemoveClient(Wu* wu, WuClient* client); +void WuClientSetUserData(WuClient* client, void* user); +void* WuClientGetUserData(const WuClient* client); +SDPResult WuExchangeSDP(Wu* wu, const char* sdp, int32_t length); +void WuHandleUDP(Wu* wu, const WuAddress* remote, const uint8_t* data, + int32_t length); +void WuSetUDPWriteFunction(Wu* wu, WuWriteFn write); +void WuSetUserData(Wu* wu, void* userData); +void WuSetErrorCallback(Wu* wu, WuErrorFn callback); +void WuSetDebugCallback(Wu* wu, WuErrorFn callback); +WuAddress WuClientGetAddress(const WuClient* client); +WuClient* WuFindClient(const Wu* wu, WuAddress address); + +#ifdef __cplusplus +} +#endif diff --git a/common/network/webudp/WuArena.cpp b/common/network/webudp/WuArena.cpp new file mode 100644 index 0000000..0907112 --- /dev/null +++ b/common/network/webudp/WuArena.cpp @@ -0,0 +1,26 @@ +#include "WuArena.h" +#include +#include + +void WuArenaInit(WuArena* arena, int32_t capacity) { + arena->memory = (uint8_t*)calloc(capacity, 1); + arena->length = 0; + arena->capacity = capacity; +} + +void* WuArenaAcquire(WuArena* arena, int32_t blockSize) { + assert(blockSize > 0); + int32_t remain = arena->capacity - arena->length; + + if (remain >= blockSize) { + uint8_t* m = arena->memory + arena->length; + arena->length += blockSize; + return m; + } + + return NULL; +} + +void WuArenaReset(WuArena* arena) { arena->length = 0; } + +void WuArenaDestroy(WuArena* arena) { free(arena->memory); } diff --git a/common/network/webudp/WuArena.h b/common/network/webudp/WuArena.h new file mode 100644 index 0000000..a89f7c6 --- /dev/null +++ b/common/network/webudp/WuArena.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +struct WuArena { + uint8_t* memory; + int32_t length; + int32_t capacity; +}; + +void WuArenaInit(WuArena* arena, int32_t capacity); +void* WuArenaAcquire(WuArena* arena, int32_t blockSize); +void WuArenaReset(WuArena* arena); +void WuArenaDestroy(WuArena* arena); diff --git a/common/network/webudp/WuBufferOp.h b/common/network/webudp/WuBufferOp.h new file mode 100644 index 0000000..ba1b21a --- /dev/null +++ b/common/network/webudp/WuBufferOp.h @@ -0,0 +1,48 @@ +#pragma once +#include +#include +#include + +template +T ByteSwap(T v) { + if (sizeof(T) == 1) { + return v; + } else if (sizeof(T) == 2) { + return __builtin_bswap16(uint16_t(v)); + } else if (sizeof(T) == 4) { + return __builtin_bswap32(uint32_t(v)); + } else if (sizeof(T) == 8) { + return __builtin_bswap64(uint64_t(v)); + } else { + assert(0); + return 0; + } +} + +template +size_t WriteScalar(uint8_t* dest, T v) { + *((T*)dest) = v; + return sizeof(T); +} + +template +int32_t ReadScalar(const uint8_t* src, T* v) { + *v = *(const T*)src; + return sizeof(T); +} + +template +size_t WriteScalarSwapped(uint8_t* dest, T v) { + *((T*)dest) = ByteSwap(v); + return sizeof(T); +} + +template +int32_t ReadScalarSwapped(const uint8_t* src, T* v) { + *v = ByteSwap(*(const T*)src); + return sizeof(T); +} + +inline int32_t PadSize(int32_t numBytes, int32_t alignBytes) { + return ((numBytes + alignBytes - 1) & ~(alignBytes - 1)) - numBytes; +} diff --git a/common/network/webudp/WuClock.h b/common/network/webudp/WuClock.h new file mode 100644 index 0000000..38f7f50 --- /dev/null +++ b/common/network/webudp/WuClock.h @@ -0,0 +1,34 @@ +#pragma once +#ifdef _WIN32 +#include +#else +#include +#endif +#include + +inline int64_t HpCounter() { +#ifdef _WIN32 + LARGE_INTEGER li; + QueryPerformanceCounter(&li); + int64_t i64 = li.QuadPart; +#else + struct timeval t; + gettimeofday(&t, 0); + int64_t i64 = t.tv_sec * int64_t(1000000) + t.tv_usec; +#endif + return i64; +} + +inline int64_t HpFreq() { +#ifdef _WIN32 + LARGE_INTEGER li; + QueryPerformanceFrequency(&li); + return li.QuadPart; +#else + return int64_t(1000000); +#endif +} + +inline double MsNow() { + return double(HpCounter()) * 1000.0 / double(HpFreq()); +} diff --git a/common/network/webudp/WuCrypto.cpp b/common/network/webudp/WuCrypto.cpp new file mode 100644 index 0000000..242ddb7 --- /dev/null +++ b/common/network/webudp/WuCrypto.cpp @@ -0,0 +1,65 @@ +#include "WuCrypto.h" +#include +#include +#include +#include "WuRng.h" + +WuSHA1Digest WuSHA1(const uint8_t* src, size_t len, const void* key, + size_t keyLen) { + WuSHA1Digest digest; + HMAC(EVP_sha1(), key, keyLen, src, len, digest.bytes, NULL); + + return digest; +} + +WuCert::WuCert() : key(EVP_PKEY_new()), x509(X509_new()) { + RSA* rsa = RSA_new(); + BIGNUM* n = BN_new(); + BN_set_word(n, RSA_F4); + + if (!RAND_status()) { + uint64_t seed = WuRandomU64(); + RAND_seed(&seed, sizeof(seed)); + } + + RSA_generate_key_ex(rsa, 2048, n, NULL); + EVP_PKEY_assign_RSA(key, rsa); + + BIGNUM* serial = BN_new(); + X509_NAME* name = X509_NAME_new(); + X509_set_pubkey(x509, key); + BN_pseudo_rand(serial, 64, 0, 0); + + X509_set_version(x509, 0L); + X509_NAME_add_entry_by_NID(name, NID_commonName, MBSTRING_UTF8, + (unsigned char*)"wusocket", -1, -1, 0); + X509_set_subject_name(x509, name); + X509_set_issuer_name(x509, name); + X509_gmtime_adj(X509_get_notBefore(x509), 0); + X509_gmtime_adj(X509_get_notAfter(x509), 365 * 24 * 3600); + X509_sign(x509, key, EVP_sha1()); + + unsigned int len = 32; + uint8_t buf[32] = {0}; + X509_digest(x509, EVP_sha256(), buf, &len); + + assert(len == 32); + for (unsigned int i = 0; i < len; i++) { + if (i < 31) { + snprintf(fingerprint + i * 3, 4, "%02X:", buf[i]); + } else { + snprintf(fingerprint + i * 3, 3, "%02X", buf[i]); + } + } + + fingerprint[95] = '\0'; + + BN_free(n); + BN_free(serial); + X509_NAME_free(name); +} + +WuCert::~WuCert() { + EVP_PKEY_free(key); + X509_free(x509); +} diff --git a/common/network/webudp/WuCrypto.h b/common/network/webudp/WuCrypto.h new file mode 100644 index 0000000..688d377 --- /dev/null +++ b/common/network/webudp/WuCrypto.h @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +const size_t kSHA1Length = 20; + +struct WuSHA1Digest { + uint8_t bytes[kSHA1Length]; +}; + +struct WuCert { + WuCert(); + ~WuCert(); + + EVP_PKEY* key; + X509* x509; + char fingerprint[96]; +}; + +WuSHA1Digest WuSHA1(const uint8_t* src, size_t len, const void* key, + size_t keyLen); diff --git a/common/network/webudp/WuHost.h b/common/network/webudp/WuHost.h new file mode 100644 index 0000000..8b6bab9 --- /dev/null +++ b/common/network/webudp/WuHost.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include "Wu.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct WuHost WuHost; + +int32_t WuHostCreate(const char* hostAddr, uint16_t port, int32_t maxClients, + WuHost** host); +void WuHostDestroy(WuHost* host); +/* + * Timeout: + * -1 = Block until an event + * 0 = Return immediately + * >0 = Block for N milliseconds + * Returns 1 if an event was received, 0 otherwise. + */ +int32_t WuHostServe(WuHost* host, WuEvent* evt, int timeout); +void WuHostRemoveClient(WuHost* wu, WuClient* client); +int32_t WuHostSendText(WuHost* host, WuClient* client, const char* text, + int32_t length); +int32_t WuHostSendBinary(WuHost* host, WuClient* client, const uint8_t* data, + int32_t length); +void WuHostSetErrorCallback(WuHost* host, WuErrorFn callback); +void WuHostSetDebugCallback(WuHost* host, WuErrorFn callback); +WuClient* WuHostFindClient(const WuHost* host, WuAddress address); + +void WuGotHttp(WuHost *host, const char msg[], const uint32_t msglen, + char resp[]); + +#ifdef __cplusplus +} +#endif diff --git a/common/network/webudp/WuHostEpoll.cpp b/common/network/webudp/WuHostEpoll.cpp new file mode 100644 index 0000000..055025b --- /dev/null +++ b/common/network/webudp/WuHostEpoll.cpp @@ -0,0 +1,268 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "WuHost.h" +#include "WuHttp.h" +#include "WuMath.h" +#include "WuNetwork.h" +#include "WuPool.h" +#include "WuRng.h" +#include "WuString.h" + +static pthread_mutex_t wumutex = PTHREAD_MUTEX_INITIALIZER; + +struct WuConnectionBuffer { + size_t size = 0; + int fd = -1; + uint8_t requestBuffer[kMaxHttpRequestLength]; +}; + +struct WuHost { + Wu* wu; + int udpfd; + int epfd; + int pollTimeout; + WuPool* bufferPool; + struct epoll_event* events; + int32_t maxEvents; + uint16_t port; + char errBuf[512]; +}; + +static void HostReclaimBuffer(WuHost* host, WuConnectionBuffer* buffer) { + buffer->fd = -1; + buffer->size = 0; + WuPoolRelease(host->bufferPool, buffer); +} + +static WuConnectionBuffer* HostGetBuffer(WuHost* host) { + WuConnectionBuffer* buffer = (WuConnectionBuffer*)WuPoolAcquire(host->bufferPool); + return buffer; +} + +static void HandleErrno(WuHost* host, const char* description) { + snprintf(host->errBuf, sizeof(host->errBuf), "%s: %s", description, + strerror(errno)); + WuReportError(host->wu, host->errBuf); +} + +static void WriteUDPData(const uint8_t* data, size_t length, + const WuClient* client, void* userData) { + WuHost* host = (WuHost*)userData; + + WuAddress address = WuClientGetAddress(client); + struct sockaddr_in netaddr; + netaddr.sin_family = AF_INET; + netaddr.sin_port = htons(address.port); + netaddr.sin_addr.s_addr = htonl(address.host); + + sendto(host->udpfd, data, length, 0, (struct sockaddr*)&netaddr, + sizeof(netaddr)); +} + +int32_t WuHostServe(WuHost* host, WuEvent* evt, int timeout) { + if (pthread_mutex_lock(&wumutex)) + abort(); + int32_t hres = WuUpdate(host->wu, evt); + pthread_mutex_unlock(&wumutex); + + if (hres) { + return hres; + } + + int n = + epoll_wait(host->epfd, host->events, host->maxEvents, timeout); + + for (int i = 0; i < n; i++) { + struct epoll_event* e = &host->events[i]; + WuConnectionBuffer* c = (WuConnectionBuffer*)e->data.ptr; + + if ((e->events & EPOLLERR) || (e->events & EPOLLHUP) || + (!(e->events & EPOLLIN))) { + close(c->fd); + HostReclaimBuffer(host, c); + continue; + } + + if (host->udpfd == c->fd) { + struct sockaddr_in remote; + socklen_t remoteLen = sizeof(remote); + uint8_t buf[4096]; + + ssize_t r = 0; + while ((r = recvfrom(host->udpfd, buf, sizeof(buf), 0, + (struct sockaddr*)&remote, &remoteLen)) > 0) { + WuAddress address; + address.host = ntohl(remote.sin_addr.s_addr); + address.port = ntohs(remote.sin_port); + + if (pthread_mutex_lock(&wumutex)) + abort(); + WuHandleUDP(host->wu, &address, buf, r); + pthread_mutex_unlock(&wumutex); + } + + } else { + WuReportError(host->wu, "Unknown epoll source"); + } + } + + return 0; +} + +int32_t WuHostCreate(const char* hostAddr, uint16_t port, int32_t maxClients, WuHost** host) { + *host = NULL; + + WuHost* ctx = (WuHost*)calloc(1, sizeof(WuHost)); + + + if (!ctx) { + return WU_OUT_OF_MEMORY; + } + + int32_t status = WuCreate(hostAddr, port, maxClients, &ctx->wu); + + if (status != WU_OK) { + free(ctx); + return status; + } + + ctx->udpfd = CreateSocket(port); + + if (ctx->udpfd == -1) { + WuHostDestroy(ctx); + return WU_ERROR; + } + + status = MakeNonBlocking(ctx->udpfd); + if (status == -1) { + WuHostDestroy(ctx); + return WU_ERROR; + } + + ctx->epfd = epoll_create(1024); + if (ctx->epfd == -1) { + WuHostDestroy(ctx); + return WU_ERROR; + } + + const int32_t maxEvents = 128; + ctx->bufferPool = WuPoolCreate(sizeof(WuConnectionBuffer), maxEvents + 2); + + if (!ctx->bufferPool) { + WuHostDestroy(ctx); + return WU_OUT_OF_MEMORY; + } + + WuConnectionBuffer* udpBuf = HostGetBuffer(ctx); + udpBuf->fd = ctx->udpfd; + + struct epoll_event event; + event.events = EPOLLIN | EPOLLET; + event.data.ptr = udpBuf; + status = epoll_ctl(ctx->epfd, EPOLL_CTL_ADD, ctx->udpfd, &event); + if (status == -1) { + WuHostDestroy(ctx); + return WU_ERROR; + } + + ctx->maxEvents = maxEvents; + ctx->events = (struct epoll_event*)calloc(ctx->maxEvents, sizeof(event)); + + if (!ctx->events) { + WuHostDestroy(ctx); + return WU_OUT_OF_MEMORY; + } + + WuSetUserData(ctx->wu, ctx); + WuSetUDPWriteFunction(ctx->wu, WriteUDPData); + + *host = ctx; + + return WU_OK; +} + +void WuHostRemoveClient(WuHost* host, WuClient* client) { + WuRemoveClient(host->wu, client); +} + +int32_t WuHostSendText(WuHost* host, WuClient* client, const char* text, + int32_t length) { + return WuSendText(host->wu, client, text, length); +} + +int32_t WuHostSendBinary(WuHost* host, WuClient* client, const uint8_t* data, + int32_t length) { + if (pthread_mutex_lock(&wumutex)) + abort(); + int32_t ret = WuSendBinary(host->wu, client, data, length); + pthread_mutex_unlock(&wumutex); + + return ret; +} + +void WuHostSetErrorCallback(WuHost* host, WuErrorFn callback) { + WuSetErrorCallback(host->wu, callback); +} + +void WuHostSetDebugCallback(WuHost* host, WuErrorFn callback) { + WuSetDebugCallback(host->wu, callback); +} + +void WuHostDestroy(WuHost* host) { + if (!host) { + return; + } + + WuDestroy(host->wu); + + if (host->udpfd != -1) { + close(host->udpfd); + } + + if (host->epfd != -1) { + close(host->epfd); + } + + if (host->bufferPool) { + free(host->bufferPool); + } + + if (host->events) { + free(host->events); + } +} + +WuClient* WuHostFindClient(const WuHost* host, WuAddress address) { + return WuFindClient(host->wu, address); +} + +void WuGotHttp(WuHost *host, const char msg[], const uint32_t msglen, + char resp[]) { + + const SDPResult sdp = WuExchangeSDP( + host->wu, msg, msglen); + + if (sdp.status == WuSDPStatus_Success) { + snprintf(resp, 4096, + "%.*s", + sdp.sdpLength, sdp.sdp); + } else if (sdp.status == WuSDPStatus_MaxClients) { + WuReportError(host->wu, "Too many connections"); + strcpy(resp, HTTP_UNAVAILABLE); + } else if (sdp.status == WuSDPStatus_InvalidSDP) { + WuReportError(host->wu, "Invalid SDP"); + strcpy(resp, HTTP_BAD_REQUEST); + } else { + WuReportError(host->wu, "Other error"); + strcpy(resp, HTTP_SERVER_ERROR); + } +} diff --git a/common/network/webudp/WuHostNull.cpp b/common/network/webudp/WuHostNull.cpp new file mode 100644 index 0000000..7aa7e83 --- /dev/null +++ b/common/network/webudp/WuHostNull.cpp @@ -0,0 +1,11 @@ +#include "WuHost.h" + +int32_t WuHostCreate(const char*, const char*, int32_t, WuHost** host) { + *host = NULL; + return WU_OK; +} +int32_t WuHostServe(WuHost*, WuEvent*, int) { return 0; } +void WuHostRemoveClient(WuHost*, WuClient*) {} +int32_t WuHostSendText(WuHost*, WuClient*, const char*, int32_t) { return 0; } +int32_t WuHostSendBinary(WuHost*, WuClient*, const uint8_t*, int32_t) { return 0; } +void WuHostSetErrorCallback(WuHost*, WuErrorFn) {} diff --git a/common/network/webudp/WuHttp.h b/common/network/webudp/WuHttp.h new file mode 100644 index 0000000..e456513 --- /dev/null +++ b/common/network/webudp/WuHttp.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +#define HTTP_BAD_REQUEST "HTTP/1.1 400 Bad request\r\n\r\n" +#define HTTP_UNAVAILABLE "HTTP/1.1 503 Service Unavailable\r\n\r\n" +#define HTTP_SERVER_ERROR "HTTP/1.1 500 Internal Server Error\r\n\r\n" + +const size_t kMaxHttpRequestLength = 4096; diff --git a/common/network/webudp/WuMath.h b/common/network/webudp/WuMath.h new file mode 100644 index 0000000..d699d5f --- /dev/null +++ b/common/network/webudp/WuMath.h @@ -0,0 +1,15 @@ +#pragma once + +template +const T& Min(const T& a, const T& b) { + if (a < b) return a; + + return b; +} + +template +const T& Max(const T& a, const T& b) { + if (a > b) return a; + + return b; +} diff --git a/common/network/webudp/WuNetwork.cpp b/common/network/webudp/WuNetwork.cpp new file mode 100644 index 0000000..9e652f7 --- /dev/null +++ b/common/network/webudp/WuNetwork.cpp @@ -0,0 +1,58 @@ +#include "WuNetwork.h" +#include +#include +#include +#include +#include +#include +#include + +void HexDump(const uint8_t* src, size_t len) { + for (size_t i = 0; i < len; i++) { + if (i % 8 == 0) printf("%04x ", uint32_t(i)); + + printf("%02x ", src[i]); + + if ((i + 1) % 8 == 0) printf("\n"); + } + printf("\n"); +} + +int MakeNonBlocking(int sfd) { + int flags = fcntl(sfd, F_GETFL, 0); + if (flags == -1) { + return -1; + } + + flags |= O_NONBLOCK; + + int s = fcntl(sfd, F_SETFL, flags); + if (s == -1) { + return -1; + } + + return 0; +} + +int CreateSocket(uint16_t port) { + + int sfd = socket(AF_INET, SOCK_DGRAM, 0); + if (sfd == -1) { + return -1; + } + + int enable = 1; + setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(int)); + + struct sockaddr_in addr; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + + if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == 0) + return sfd; + + close(sfd); + + return -1; +} diff --git a/common/network/webudp/WuNetwork.h b/common/network/webudp/WuNetwork.h new file mode 100644 index 0000000..4c5cb1e --- /dev/null +++ b/common/network/webudp/WuNetwork.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include +#include +#include + +void HexDump(const uint8_t* src, size_t len); +int MakeNonBlocking(int sfd); +int CreateSocket(uint16_t port); diff --git a/common/network/webudp/WuPool.cpp b/common/network/webudp/WuPool.cpp new file mode 100644 index 0000000..205c5e8 --- /dev/null +++ b/common/network/webudp/WuPool.cpp @@ -0,0 +1,60 @@ +#include "WuPool.h" +#include +#include + +struct BlockHeader { + int32_t index; +}; + +struct WuPool { + int32_t slotSize; + int32_t numBytes; + int32_t numBlocks; + uint8_t* memory; + int32_t freeIndicesCount; + int32_t* freeIndices; +}; + +WuPool* WuPoolCreate(int32_t blockSize, int32_t numBlocks) { + WuPool* pool = (WuPool*)calloc(1, sizeof(WuPool)); + + pool->slotSize = blockSize + sizeof(BlockHeader); + pool->numBytes = pool->slotSize * numBlocks; + pool->numBlocks = numBlocks; + pool->memory = (uint8_t*)calloc(pool->numBytes, 1); + pool->freeIndicesCount = numBlocks; + pool->freeIndices = (int32_t*)calloc(numBlocks, sizeof(int32_t)); + + for (int32_t i = 0; i < numBlocks; i++) { + pool->freeIndices[i] = numBlocks - i - 1; + } + + return pool; +} + +void WuPoolDestroy(WuPool* pool) { + free(pool->memory); + free(pool->freeIndices); + free(pool); +} + +void* WuPoolAcquire(WuPool* pool) { + if (pool->freeIndicesCount == 0) return NULL; + + const int32_t index = pool->freeIndices[pool->freeIndicesCount - 1]; + pool->freeIndicesCount--; + const int32_t offset = index * pool->slotSize; + + uint8_t* block = pool->memory + offset; + BlockHeader* header = (BlockHeader*)block; + header->index = index; + + uint8_t* userMem = block + sizeof(BlockHeader); + return userMem; +} + +void WuPoolRelease(WuPool* pool, void* ptr) { + uint8_t* mem = (uint8_t*)ptr - sizeof(BlockHeader); + BlockHeader* header = (BlockHeader*)mem; + pool->freeIndices[pool->freeIndicesCount++] = header->index; +} diff --git a/common/network/webudp/WuPool.h b/common/network/webudp/WuPool.h new file mode 100644 index 0000000..54a91b3 --- /dev/null +++ b/common/network/webudp/WuPool.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +struct WuPool; + +WuPool* WuPoolCreate(int32_t blockSize, int32_t numBlocks); +void WuPoolDestroy(WuPool* pool); +void* WuPoolAcquire(WuPool* pool); +void WuPoolRelease(WuPool* pool, void* ptr); diff --git a/common/network/webudp/WuQueue.cpp b/common/network/webudp/WuQueue.cpp new file mode 100644 index 0000000..de2f1eb --- /dev/null +++ b/common/network/webudp/WuQueue.cpp @@ -0,0 +1,58 @@ +#include "WuQueue.h" +#include +#include + +static int32_t WuQueueFull(const WuQueue* q) { + if (q->length == q->capacity) { + return 1; + } + + return 0; +} + +WuQueue* WuQueueCreate(int32_t itemSize, int32_t capacity) { + WuQueue* q = (WuQueue*)calloc(1, sizeof(WuQueue)); + WuQueueInit(q, itemSize, capacity); + return q; +} + +void WuQueueInit(WuQueue* q, int32_t itemSize, int32_t capacity) { + memset(q, 0, sizeof(WuQueue)); + q->itemSize = itemSize; + q->capacity = capacity; + q->items = (uint8_t*)calloc(q->capacity, itemSize); +} + +void WuQueuePush(WuQueue* q, const void* item) { + if (WuQueueFull(q)) { + int32_t newCap = q->capacity * 1.5; + uint8_t* newItems = (uint8_t*)calloc(newCap, q->itemSize); + + int32_t nUpper = q->length - q->start; + int32_t nLower = q->length - nUpper; + memcpy(newItems, q->items + q->start * q->itemSize, q->itemSize * nUpper); + memcpy(newItems + q->itemSize * nUpper, q->items, q->itemSize * nLower); + + free(q->items); + + q->start = 0; + q->capacity = newCap; + q->items = newItems; + } + + const int32_t insertIdx = + ((q->start + q->length) % q->capacity) * q->itemSize; + memcpy(q->items + insertIdx, item, q->itemSize); + q->length++; +} + +int32_t WuQueuePop(WuQueue* q, void* item) { + if (q->length > 0) { + memcpy(item, q->items + q->start * q->itemSize, q->itemSize); + q->start = (q->start + 1) % q->capacity; + q->length--; + return 1; + } + + return 0; +} diff --git a/common/network/webudp/WuQueue.h b/common/network/webudp/WuQueue.h new file mode 100644 index 0000000..4c8f171 --- /dev/null +++ b/common/network/webudp/WuQueue.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +struct WuQueue { + int32_t itemSize; + int32_t start; + int32_t length; + int32_t capacity; + uint8_t* items; +}; + +WuQueue* WuQueueCreate(int32_t itemSize, int32_t capacity); +void WuQueueInit(WuQueue* q, int32_t itemSize, int32_t capacity); +void WuQueuePush(WuQueue* q, const void* item); +int32_t WuQueuePop(WuQueue* q, void* item); diff --git a/common/network/webudp/WuRng.cpp b/common/network/webudp/WuRng.cpp new file mode 100644 index 0000000..fac97c6 --- /dev/null +++ b/common/network/webudp/WuRng.cpp @@ -0,0 +1,51 @@ +#include +#include "WuRng.h" + +static const char kCharacterTable[] = + "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + +static inline uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +static uint64_t WuGetRngSeed() { + uint64_t x = rand(); + uint64_t z = (x += UINT64_C(0x9E3779B97F4A7C15)); + z = (z ^ (z >> 30)) * UINT64_C(0xBF58476D1CE4E5B9); + z = (z ^ (z >> 27)) * UINT64_C(0x94D049BB133111EB); + return z ^ (z >> 31); +} + +static void WuRngInit(WuRngState* state, uint64_t seed) { + state->s[0] = seed; + state->s[1] = seed; +} + +static uint64_t WuRngNext(WuRngState* state) { + const uint64_t s0 = state->s[0]; + uint64_t s1 = state->s[1]; + const uint64_t result = s0 + s1; + + s1 ^= s0; + state->s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); + state->s[1] = rotl(s1, 36); + + return result; +} + +void WuRandomString(char* out, size_t length) { + WuRngState state; + WuRngInit(&state, WuGetRngSeed()); + + for (size_t i = 0; i < length; i++) { + out[i] = kCharacterTable[WuRngNext(&state) % (sizeof(kCharacterTable) - 1)]; + } +} + +uint64_t WuRandomU64() { + WuRngState state; + WuRngInit(&state, WuGetRngSeed()); + return WuRngNext(&state); +} + +uint32_t WuRandomU32() { return (uint32_t)WuRandomU64(); } diff --git a/common/network/webudp/WuRng.h b/common/network/webudp/WuRng.h new file mode 100644 index 0000000..8e17625 --- /dev/null +++ b/common/network/webudp/WuRng.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +// http://xoroshiro.di.unimi.it/xoroshiro128plus.c +struct WuRngState { + uint64_t s[2]; +}; + +uint64_t WuRandomU64(); +uint32_t WuRandomU32(); + +void WuRandomString(char* out, size_t length); diff --git a/common/network/webudp/WuSctp.cpp b/common/network/webudp/WuSctp.cpp new file mode 100644 index 0000000..d495356 --- /dev/null +++ b/common/network/webudp/WuSctp.cpp @@ -0,0 +1,169 @@ +#include "WuSctp.h" +#include +#include +#include +#include "CRC32.h" +#include "WuBufferOp.h" +#include "WuMath.h" +#include "WuNetwork.h" + +int32_t ParseSctpPacket(const uint8_t* buf, size_t len, SctpPacket* packet, + SctpChunk* chunks, size_t maxChunks, size_t* nChunk) { + if (len < 16) { + return 0; + } + + int32_t offset = ReadScalarSwapped(buf, &packet->sourcePort); + offset += ReadScalarSwapped(buf + offset, &packet->destionationPort); + offset += ReadScalarSwapped(buf + offset, &packet->verificationTag); + offset += ReadScalarSwapped(buf + offset, &packet->checkSum); + + int32_t left = len - offset; + + size_t chunkNum = 0; + while (left >= 4 && chunkNum < maxChunks) { + SctpChunk* chunk = &chunks[chunkNum++]; + + offset += ReadScalarSwapped(buf + offset, &chunk->type); + offset += ReadScalarSwapped(buf + offset, &chunk->flags); + offset += ReadScalarSwapped(buf + offset, &chunk->length); + + *nChunk += 1; + + if (chunk->type == Sctp_Data) { + auto* p = &chunk->as.data; + size_t chunkOffset = ReadScalarSwapped(buf + offset, &p->tsn); + chunkOffset += + ReadScalarSwapped(buf + offset + chunkOffset, &p->streamId); + chunkOffset += + ReadScalarSwapped(buf + offset + chunkOffset, &p->streamSeq); + chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset, &p->protoId); + p->userDataLength = Max(int32_t(chunk->length) - 16, 0); + p->userData = buf + offset + chunkOffset; + } else if (chunk->type == Sctp_Sack) { + auto* sack = &chunk->as.sack; + size_t chunkOffset = + ReadScalarSwapped(buf + offset, &sack->cumulativeTsnAck); + chunkOffset += + ReadScalarSwapped(buf + offset + chunkOffset, &sack->advRecvWindow); + chunkOffset += + ReadScalarSwapped(buf + offset + chunkOffset, &sack->numGapAckBlocks); + ReadScalarSwapped(buf + offset + chunkOffset, &sack->numDupTsn); + } else if (chunk->type == Sctp_Heartbeat) { + auto* p = &chunk->as.heartbeat; + size_t chunkOffset = 2; // skip type + uint16_t heartbeatLen; + chunkOffset += + ReadScalarSwapped(buf + offset + chunkOffset, &heartbeatLen); + p->heartbeatInfoLen = int32_t(heartbeatLen) - 4; + p->heartbeatInfo = buf + offset + chunkOffset; + } else if (chunk->type == Sctp_Init) { + size_t chunkOffset = + ReadScalarSwapped(buf + offset, &chunk->as.init.initiateTag); + chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset, + &chunk->as.init.windowCredit); + chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset, + &chunk->as.init.numOutboundStreams); + chunkOffset += ReadScalarSwapped(buf + offset + chunkOffset, + &chunk->as.init.numInboundStreams); + ReadScalarSwapped(buf + offset + chunkOffset, &chunk->as.init.initialTsn); + } + + int32_t valueLength = chunk->length - 4; + int32_t pad = PadSize(valueLength, 4); + offset += valueLength + pad; + left = len - offset; + } + + return 1; +} + +size_t SerializeSctpPacket(const SctpPacket* packet, const SctpChunk* chunks, + size_t numChunks, uint8_t* dst, size_t dstLen) { + size_t offset = WriteScalar(dst, htons(packet->sourcePort)); + offset += WriteScalar(dst + offset, htons(packet->destionationPort)); + offset += WriteScalar(dst + offset, htonl(packet->verificationTag)); + + size_t crcOffset = offset; + offset += WriteScalar(dst + offset, uint32_t(0)); + + for (size_t i = 0; i < numChunks; i++) { + const SctpChunk* chunk = &chunks[i]; + + offset += WriteScalar(dst + offset, chunk->type); + offset += WriteScalar(dst + offset, chunk->flags); + offset += WriteScalar(dst + offset, htons(chunk->length)); + + switch (chunk->type) { + case Sctp_Data: { + auto* dc = &chunk->as.data; + offset += WriteScalar(dst + offset, htonl(dc->tsn)); + offset += WriteScalar(dst + offset, htons(dc->streamId)); + offset += WriteScalar(dst + offset, htons(dc->streamSeq)); + offset += WriteScalar(dst + offset, htonl(dc->protoId)); + memcpy(dst + offset, dc->userData, dc->userDataLength); + int32_t pad = PadSize(dc->userDataLength, 4); + offset += dc->userDataLength + pad; + break; + } + case Sctp_InitAck: { + offset += WriteScalar(dst + offset, htonl(chunk->as.init.initiateTag)); + offset += WriteScalar(dst + offset, htonl(chunk->as.init.windowCredit)); + offset += + WriteScalar(dst + offset, htons(chunk->as.init.numOutboundStreams)); + offset += + WriteScalar(dst + offset, htons(chunk->as.init.numInboundStreams)); + offset += WriteScalar(dst + offset, htonl(chunk->as.init.initialTsn)); + + offset += WriteScalar(dst + offset, htons(Sctp_StateCookie)); + offset += WriteScalar(dst + offset, htons(8)); + offset += WriteScalar(dst + offset, htonl(0xB00B1E5)); + offset += WriteScalar(dst + offset, htons(Sctp_ForwardTsn)); + offset += WriteScalar(dst + offset, htons(4)); + + break; + } + case Sctp_Sack: { + auto* sack = &chunk->as.sack; + offset += WriteScalar(dst + offset, htonl(sack->cumulativeTsnAck)); + offset += WriteScalar(dst + offset, htonl(sack->advRecvWindow)); + offset += WriteScalar(dst + offset, htons(sack->numGapAckBlocks)); + offset += WriteScalar(dst + offset, htons(sack->numDupTsn)); + break; + } + case Sctp_Heartbeat: + case Sctp_HeartbeatAck: { + auto* hb = &chunk->as.heartbeat; + offset += WriteScalar(dst + offset, htons(1)); + offset += WriteScalar(dst + offset, htons(hb->heartbeatInfoLen + 4)); + memcpy(dst + offset, hb->heartbeatInfo, hb->heartbeatInfoLen); + offset += hb->heartbeatInfoLen + PadSize(hb->heartbeatInfoLen, 4); + break; + } + case Sctp_Shutdown: { + auto* shutdown = &chunk->as.shutdown; + offset += WriteScalar(dst + offset, htonl(shutdown->cumulativeTsnAck)); + break; + } + case SctpChunk_ForwardTsn: { + auto* forwardTsn = &chunk->as.forwardTsn; + offset += + WriteScalar(dst + offset, htonl(forwardTsn->newCumulativeTsn)); + break; + } + default: + break; + } + } + + uint32_t crc = SctpCRC32(dst, offset); + WriteScalar(dst + crcOffset, htonl(crc)); + + return offset; +} + +int32_t SctpDataChunkLength(int32_t userDataLength) { + return 16 + userDataLength; +} + +int32_t SctpChunkLength(int32_t contentLength) { return 4 + contentLength; } diff --git a/common/network/webudp/WuSctp.h b/common/network/webudp/WuSctp.h new file mode 100644 index 0000000..2c04e0d --- /dev/null +++ b/common/network/webudp/WuSctp.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include + +const uint32_t kSctpDefaultBufferSpace = 1 << 18; +const uint32_t kSctpMinInitAckLength = 32; + +enum SctpFlag { + SctpFlagEndFragment = 0x01, + SctpFlagBeginFragment = 0x02, + SctpFlagUnreliable = 0x04 +}; + +const uint8_t kSctpFlagCompleteUnreliable = + SctpFlagEndFragment | SctpFlagBeginFragment | SctpFlagUnreliable; + +enum SctpChunkType { + Sctp_Data = 0x00, + Sctp_Init = 0x01, + Sctp_InitAck = 0x02, + Sctp_Sack = 0x03, + Sctp_Heartbeat = 0x04, + Sctp_HeartbeatAck = 0x05, + Sctp_Abort = 0x06, + Sctp_Shutdown = 0x07, + Sctp_CookieEcho = 0x0A, + Sctp_CookieAck = 0x0B, + SctpChunk_ForwardTsn = 0xC0 +}; + +enum SctpParamType { + Sctp_StateCookie = 0x07, + Sctp_ForwardTsn = 0xC000, + Sctp_Random = 0x8002, + Sctp_AuthChunkList = 0x8003, + Sctp_HMACAlgo = 0x8004, + Sctp_SupportedExts = 0x8008 +}; + +struct SctpChunk { + uint8_t type; + uint8_t flags; + uint16_t length; + + union { + struct { + uint32_t tsn; + uint16_t streamId; + uint16_t streamSeq; + uint32_t protoId; + int32_t userDataLength; + const uint8_t* userData; + } data; + + struct { + uint32_t initiateTag; + uint32_t windowCredit; + uint16_t numOutboundStreams; + uint16_t numInboundStreams; + uint32_t initialTsn; + } init; + + struct { + int32_t heartbeatInfoLen; + const uint8_t* heartbeatInfo; + } heartbeat; + + struct { + uint32_t cumulativeTsnAck; + uint32_t advRecvWindow; + uint16_t numGapAckBlocks; + uint16_t numDupTsn; + } sack; + + struct { + uint32_t cumulativeTsnAck; + } shutdown; + + struct { + uint32_t newCumulativeTsn; + } forwardTsn; + } as; +}; + +struct SctpPacket { + uint16_t sourcePort; + uint16_t destionationPort; + uint32_t verificationTag; + uint32_t checkSum; +}; + +int32_t ParseSctpPacket(const uint8_t* buf, size_t len, SctpPacket* packet, + SctpChunk* chunks, size_t maxChunks, size_t* nChunk); + +size_t SerializeSctpPacket(const SctpPacket* packet, const SctpChunk* chunks, + size_t numChunks, uint8_t* dst, size_t dstLen); + +int32_t SctpDataChunkLength(int32_t userDataLength); +int32_t SctpChunkLength(int32_t contentLength); diff --git a/common/network/webudp/WuSdp.cpp b/common/network/webudp/WuSdp.cpp new file mode 100644 index 0000000..97b8e57 --- /dev/null +++ b/common/network/webudp/WuSdp.cpp @@ -0,0 +1,152 @@ +#include "WuSdp.h" +#include +#include +#include "WuArena.h" +#include "WuRng.h" + +enum SdpParseState { kParseIgnore, kParseType, kParseEq, kParseField }; + +static bool ValidField(const IceField* field) { return field->length > 0; } + +static bool BeginsWith(const char* s, size_t len, const char* prefix, + size_t plen) { + if (plen > len) return false; + + for (size_t i = 0; i < plen; i++) { + char a = s[i]; + char b = prefix[i]; + + if (a != b) return false; + } + + return true; +} + +static bool GetIceValue(const char* field, size_t len, const char* name, + IceField* o) { + if (BeginsWith(field, len, name, strlen(name))) { + for (size_t i = 0; i < len; i++) { + char c = field[i]; + if (c == ':') { + size_t valueBegin = i + 1; + if (valueBegin < len) { + size_t valueLength = len - valueBegin; + o->value = field + valueBegin; + o->length = int32_t(valueLength); + return true; + } + break; + } + } + } + + return false; +} + +static void ParseSdpField(const char* field, size_t len, ICESdpFields* fields) { + GetIceValue(field, len, "ice-ufrag", &fields->ufrag); + GetIceValue(field, len, "ice-pwd", &fields->password); + GetIceValue(field, len, "mid", &fields->mid); +} + +bool ParseSdp(const char* sdp, size_t len, ICESdpFields* fields) { + memset(fields, 0, sizeof(ICESdpFields)); + + SdpParseState state = kParseType; + size_t begin = 0; + size_t length = 0; + + for (size_t i = 0; i < len; i++) { + char c = sdp[i]; + switch (state) { + case kParseType: { + if (c == 'a') { + state = kParseEq; + } else { + state = kParseIgnore; + } + break; + } + case kParseEq: { + if (c == '=') { + state = kParseField; + begin = i + 1; + length = 0; + break; + } else { + return false; + } + } + case kParseField: { + switch (c) { + case '\n': { + ParseSdpField(sdp + begin, length, fields); + length = 0; + state = kParseType; + break; + } + case '\r': { + state = kParseIgnore; + ParseSdpField(sdp + begin, length, fields); + length = 0; + break; + }; + default: { length++; } + } + } + default: { + if (c == '\n') state = kParseType; + } + } + } + + return ValidField(&fields->ufrag) && ValidField(&fields->password) && + ValidField(&fields->mid); +} + +const char* GenerateSDP(WuArena* arena, const char* certFingerprint, + const char* serverIp, uint16_t serverPort, + const char* ufrag, int32_t ufragLen, const char* pass, + int32_t passLen, const ICESdpFields* remote, + int* outLength) { + const uint32_t port = uint32_t(serverPort); + char buf[4096]; + + int32_t length = snprintf( + buf, sizeof(buf), + "{\"answer\":{\"sdp\":\"v=0\\r\\n" + "o=- %u 1 IN IP4 %u\\r\\n" + "s=-\\r\\n" + "t=0 0\\r\\n" + "m=application %u UDP/DTLS/SCTP webrtc-datachannel\\r\\n" + "c=IN IP4 %s\\r\\n" + "a=ice-lite\\r\\n" + "a=ice-ufrag:%.*s\\r\\n" + "a=ice-pwd:%.*s\\r\\n" + "a=fingerprint:sha-256 %s\\r\\n" + "a=ice-options:trickle\\r\\n" + "a=setup:passive\\r\\n" + "a=mid:%.*s\\r\\n" + "a=sctp-port:%u\\r\\n\"," + "\"type\":\"answer\"},\"candidate\":{\"sdpMLineIndex\":0," + "\"sdpMid\":\"%.*s\",\"candidate\":\"candidate:1 1 UDP %u %s %u typ " + "host\"}}", + WuRandomU32(), port, port, serverIp, ufragLen, ufrag, passLen, pass, + certFingerprint, remote->mid.length, remote->mid.value, port, + remote->mid.length, remote->mid.value, WuRandomU32(), serverIp, port); + + if (length <= 0 || length >= int32_t(sizeof(buf))) { + return NULL; + } + + char* sdp = (char*)WuArenaAcquire(arena, length); + + if (!sdp) { + return NULL; + } + + memcpy(sdp, buf, length); + *outLength = length; + + return sdp; +} diff --git a/common/network/webudp/WuSdp.h b/common/network/webudp/WuSdp.h new file mode 100644 index 0000000..fafb142 --- /dev/null +++ b/common/network/webudp/WuSdp.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +struct WuArena; + +struct IceField { + const char* value; + int32_t length; +}; + +struct ICESdpFields { + IceField ufrag; + IceField password; + IceField mid; +}; + +bool ParseSdp(const char* sdp, size_t len, ICESdpFields* fields); + +const char* GenerateSDP(WuArena* arena, const char* certFingerprint, + const char* serverIp, uint16_t serverPort, + const char* ufrag, int32_t ufragLen, const char* pass, + int32_t passLen, const ICESdpFields* remote, + int* outLength); diff --git a/common/network/webudp/WuString.cpp b/common/network/webudp/WuString.cpp new file mode 100644 index 0000000..287f508 --- /dev/null +++ b/common/network/webudp/WuString.cpp @@ -0,0 +1,19 @@ +#include "WuString.h" +#include +#include +#include + +int32_t FindTokenIndex(const char* s, size_t len, char token) { + for (size_t i = 0; i < len; i++) { + if (s[i] == token) return i; + } + + return -1; +} + +bool MemEqual(const void* first, size_t firstLen, const void* second, + size_t secondLen) { + if (firstLen != secondLen) return false; + + return memcmp(first, second, firstLen) == 0; +} diff --git a/common/network/webudp/WuString.h b/common/network/webudp/WuString.h new file mode 100644 index 0000000..f8a618c --- /dev/null +++ b/common/network/webudp/WuString.h @@ -0,0 +1,6 @@ +#include +#include + +int32_t FindTokenIndex(const char* s, size_t len, char token); +bool MemEqual(const void* first, size_t firstLen, const void* second, + size_t secondLen); diff --git a/common/network/webudp/WuStun.cpp b/common/network/webudp/WuStun.cpp new file mode 100644 index 0000000..f1dabf3 --- /dev/null +++ b/common/network/webudp/WuStun.cpp @@ -0,0 +1,134 @@ +#include "WuStun.h" +#include +#include +#include "CRC32.h" +#include "WuCrypto.h" + +const int32_t kStunHeaderLength = 20; +const int32_t kStunAlignment = 4; + +bool ParseStun(const uint8_t* src, int32_t len, StunPacket* packet) { + if (len < kStunHeaderLength || src[0] != 0 || src[1] != 1) { + return false; + } + + src += ReadScalarSwapped(src, &packet->type); + + if (packet->type != Stun_BindingRequest) { + return false; + } + + src += ReadScalarSwapped(src, &packet->length); + + if (packet->length < 4 || packet->length > len - kStunHeaderLength) { + // Need at least 1 attribute + return false; + } + + src += ReadScalarSwapped(src, &packet->cookie); + + for (int32_t i = 0; i < kStunTransactionIdLength; i++) { + packet->transactionId[i] = src[i]; + } + + src += kStunTransactionIdLength; + + int32_t maxOffset = int32_t(packet->length) - 1; + int32_t payloadOffset = 0; + while (payloadOffset < maxOffset) { + int32_t remain = len - kStunHeaderLength - payloadOffset; + if (remain >= 4) { + uint16_t payloadType = 0; + uint16_t payloadLength = 0; + + payloadOffset += ReadScalarSwapped(src + payloadOffset, &payloadType); + payloadOffset += ReadScalarSwapped(src + payloadOffset, &payloadLength); + remain -= 4; + + int32_t paddedLength = + payloadLength + PadSize(payloadLength, kStunAlignment); + + if (payloadType == StunAttrib_User) { + // fragment = min 4 chars + // username = fragment:fragment (at least 9 characters) + if (paddedLength <= remain && payloadLength >= 9) { + const char* uname = (const char*)src + payloadOffset; + int32_t colonIndex = FindTokenIndex(uname, payloadLength, ':'); + if (colonIndex >= 4) { + int32_t serverUserLength = colonIndex; + int32_t remoteUserLength = payloadLength - colonIndex - 1; + if (serverUserLength > kMaxStunIdentifierLength || + remoteUserLength > kMaxStunIdentifierLength) { + return false; + } else { + packet->serverUser.length = serverUserLength; + packet->remoteUser.length = remoteUserLength; + memcpy(packet->serverUser.identifier, uname, serverUserLength); + memcpy(packet->remoteUser.identifier, uname + colonIndex + 1, + remoteUserLength); + return true; + } + + } else { + return false; + } + } else { + // Actual length > reported length + return false; + } + } + + payloadOffset += paddedLength; + } else { + return false; + } + } + + return true; +} + +int32_t SerializeStunPacket(const StunPacket* packet, const uint8_t* password, + int32_t passwordLen, uint8_t* dest, int32_t len) { + memset(dest, 0, len); + int32_t offset = WriteScalar(dest, htons(Stun_SuccessResponse)); + // X-MAPPED-ADDRESS (ip4) + MESSAGE-INTEGRITY SHA1 + int32_t contentLength = 12 + 24; + int32_t contentLengthIntegrity = contentLength + 8; + const int32_t contentLengthOffset = offset; + offset += WriteScalar(dest + offset, htons(contentLength)); + offset += WriteScalar(dest + offset, htonl(kStunCookie)); + + for (int32_t i = 0; i < 12; i++) { + dest[i + offset] = packet->transactionId[i]; + } + + offset += 12; + + // xor mapped address attribute ipv4 + offset += WriteScalar(dest + offset, htons(StunAttrib_XorMappedAddress)); + offset += WriteScalar(dest + offset, htons(8)); + offset += WriteScalar(dest + offset, uint8_t(0)); // reserved + offset += WriteScalar(dest + offset, packet->xorMappedAddress.family); + offset += WriteScalar(dest + offset, packet->xorMappedAddress.port); + offset += WriteScalar(dest + offset, packet->xorMappedAddress.address.ipv4); + + WuSHA1Digest digest = WuSHA1(dest, offset, password, passwordLen); + + offset += WriteScalar(dest + offset, htons(StunAttrib_MessageIntegrity)); + offset += WriteScalar(dest + offset, htons(20)); + + for (int32_t i = 0; i < 20; i++) { + dest[i + offset] = digest.bytes[i]; + } + + offset += 20; + + WriteScalar(dest + contentLengthOffset, htons(contentLengthIntegrity)); + uint32_t crc = StunCRC32(dest, offset) ^ 0x5354554e; + + offset += WriteScalar(dest + offset, htons(StunAttrib_Fingerprint)); + offset += WriteScalar(dest + offset, htons(4)); + offset += WriteScalar(dest + offset, htonl(crc)); + + return offset; +} diff --git a/common/network/webudp/WuStun.h b/common/network/webudp/WuStun.h new file mode 100644 index 0000000..0536b3d --- /dev/null +++ b/common/network/webudp/WuStun.h @@ -0,0 +1,57 @@ +#pragma once + +#include "WuBufferOp.h" +#include "WuString.h" + +const int32_t kMaxStunIdentifierLength = 128; +const int32_t kStunTransactionIdLength = 12; +const uint32_t kStunCookie = 0x2112a442; +const uint16_t kStunXorMagic = 0x2112; + +struct StunUserIdentifier { + uint8_t identifier[kMaxStunIdentifierLength]; + int32_t length; +}; + +enum StunAddressFamily { Stun_IPV4 = 0x01, Stun_IPV6 = 0x02 }; + +enum StunType { Stun_BindingRequest = 0x0001, Stun_SuccessResponse = 0x0101 }; + +enum StunAttributeType { + StunAttrib_User = 0x06, + StunAttrib_MessageIntegrity = 0x08, + StunAttrib_XorMappedAddress = 0x20, + StunAttrib_Fingerprint = 0x8028 +}; + +struct StunAddress { + uint8_t family; + uint16_t port; + + union { + uint32_t ipv4; + uint8_t ipv6[16]; + } address; +}; + +inline bool StunUserIdentifierEqual(const StunUserIdentifier* a, + const StunUserIdentifier* b) { + return MemEqual(a->identifier, a->length, b->identifier, b->length); +} + +struct StunPacket { + uint16_t type; + uint16_t length; + uint32_t cookie; + uint8_t transactionId[kStunTransactionIdLength]; + + StunUserIdentifier remoteUser; + StunUserIdentifier serverUser; + + StunAddress xorMappedAddress; +}; + +bool ParseStun(const uint8_t* src, int32_t len, StunPacket* packet); + +int32_t SerializeStunPacket(const StunPacket* packet, const uint8_t* password, + int32_t passwordLen, uint8_t* dest, int32_t len); diff --git a/common/network/webudp/package.json b/common/network/webudp/package.json new file mode 100644 index 0000000..377680e --- /dev/null +++ b/common/network/webudp/package.json @@ -0,0 +1,19 @@ +{ + "name": "webudp", + "version": "1.0.0", + "description": "", + "main": "webudp.js", + "scripts": { + "configure": "node-gyp configure", + "build": "node-gyp build" + }, + "dependencies": { + "express": "4.16.3", + "cors": "2.8.4", + "body-parser": "1.18.3" + }, + "devDependencies": { + "node-gyp": "6.1.0", + "nan": "2.14.0" + } +} diff --git a/common/rdr/ZlibOutStream.cxx b/common/rdr/ZlibOutStream.cxx index d58e43e..1661f9f 100644 --- a/common/rdr/ZlibOutStream.cxx +++ b/common/rdr/ZlibOutStream.cxx @@ -194,3 +194,9 @@ void ZlibOutStream::checkCompressionLevel() compressionLevel = newLevel; } } + +void ZlibOutStream::resetDeflate() { + int ret = deflateReset(zs); + if (ret != Z_OK) + throw Exception("ZlibOutStream: deflateReset failed"); +} diff --git a/common/rdr/ZlibOutStream.h b/common/rdr/ZlibOutStream.h index 2b08f8d..4278e49 100644 --- a/common/rdr/ZlibOutStream.h +++ b/common/rdr/ZlibOutStream.h @@ -43,6 +43,8 @@ namespace rdr { void flush(); size_t length(); + void resetDeflate(); + private: virtual void overrun(size_t needed); diff --git a/common/rfb/ConnParams.cxx b/common/rfb/ConnParams.cxx index 3871037..1893063 100644 --- a/common/rfb/ConnParams.cxx +++ b/common/rfb/ConnParams.cxx @@ -45,6 +45,7 @@ ConnParams::ConnParams() supportsWEBP(false), supportsSetDesktopSize(false), supportsFence(false), supportsContinuousUpdates(false), supportsExtendedClipboard(false), + supportsUdp(false), compressLevel(2), qualityLevel(-1), fineQualityLevel(-1), subsampling(subsampleUndefined), name_(0), cursorPos_(0, 0), verStrPos(0), ledState_(ledUnknown), shandler(NULL) diff --git a/common/rfb/ConnParams.h b/common/rfb/ConnParams.h index af39641..304a614 100644 --- a/common/rfb/ConnParams.h +++ b/common/rfb/ConnParams.h @@ -117,6 +117,8 @@ namespace rfb { bool supportsContinuousUpdates; bool supportsExtendedClipboard; + bool supportsUdp; + int compressLevel; int qualityLevel; int fineQualityLevel; diff --git a/common/rfb/EncodeManager.cxx b/common/rfb/EncodeManager.cxx index 9a079bc..041c788 100644 --- a/common/rfb/EncodeManager.cxx +++ b/common/rfb/EncodeManager.cxx @@ -622,7 +622,7 @@ Encoder *EncodeManager::startRect(const Rect& rect, int type, const bool trackQu if (isWebp) klass = encoderTightWEBP; - beforeLength = conn->getOutStream()->length(); + beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length(); stats[klass][activeType].rects++; stats[klass][activeType].pixels += rect.area(); @@ -655,7 +655,7 @@ void EncodeManager::endRect(const uint8_t isWebp) conn->writer()->endRect(); - length = conn->getOutStream()->length() - beforeLength; + length = conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength; klass = activeEncoders[activeType]; if (isWebp) @@ -669,7 +669,7 @@ void EncodeManager::writeCopyPassRects(const std::vector& copypass Region lossyCopy; - beforeLength = conn->getOutStream()->length(); + beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length(); for (rect = copypassed.begin(); rect != copypassed.end(); ++rect) { int equiv; @@ -689,7 +689,7 @@ void EncodeManager::writeCopyPassRects(const std::vector& copypass lossyRegion.assign_union(lossyCopy); } - copyStats.bytes += conn->getOutStream()->length() - beforeLength; + copyStats.bytes += conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength; } void EncodeManager::writeCopyRects(const Region& copied, const Point& delta) @@ -699,7 +699,7 @@ void EncodeManager::writeCopyRects(const Region& copied, const Point& delta) Region lossyCopy; - beforeLength = conn->getOutStream()->length(); + beforeLength = conn->getOutStream(conn->cp.supportsUdp)->length(); copied.get_rects(&rects, delta.x <= 0, delta.y <= 0); for (rect = rects.begin(); rect != rects.end(); ++rect) { @@ -714,7 +714,7 @@ void EncodeManager::writeCopyRects(const Region& copied, const Point& delta) rect->tl.y - delta.y); } - copyStats.bytes += conn->getOutStream()->length() - beforeLength; + copyStats.bytes += conn->getOutStream(conn->cp.supportsUdp)->length() - beforeLength; lossyCopy = lossyRegion; lossyCopy.translate(delta); diff --git a/common/rfb/SConnection.cxx b/common/rfb/SConnection.cxx index 0203917..f0e6be5 100644 --- a/common/rfb/SConnection.cxx +++ b/common/rfb/SConnection.cxx @@ -62,6 +62,8 @@ SConnection::SConnection() defaultMinorVersion = 3; cp.setVersion(defaultMajorVersion, defaultMinorVersion); + + udps = new network::UdpStream; } SConnection::~SConnection() @@ -72,6 +74,7 @@ SConnection::~SConnection() delete writer_; writer_ = 0; strFree(clientClipboard); + delete udps; } void SConnection::setStreams(rdr::InStream* is_, rdr::OutStream* os_) @@ -348,7 +351,7 @@ void SConnection::approveConnection(bool accept, const char* reason) if (accept) { state_ = RFBSTATE_INITIALISATION; reader_ = new SMsgReader(this, is); - writer_ = new SMsgWriter(&cp, os); + writer_ = new SMsgWriter(&cp, os, udps); authSuccess(); } else { state_ = RFBSTATE_INVALID; diff --git a/common/rfb/SConnection.h b/common/rfb/SConnection.h index 0e831c8..bfecb0d 100644 --- a/common/rfb/SConnection.h +++ b/common/rfb/SConnection.h @@ -24,6 +24,7 @@ #ifndef __RFB_SCONNECTION_H__ #define __RFB_SCONNECTION_H__ +#include #include #include #include @@ -173,7 +174,7 @@ namespace rfb { SMsgWriter* writer() { return writer_; } rdr::InStream* getInStream() { return is; } - rdr::OutStream* getOutStream() { return os; } + rdr::OutStream* getOutStream(const bool udp = false) { return udp ? udps : os; } enum stateEnum { RFBSTATE_UNINITIALISED, @@ -219,6 +220,7 @@ namespace rfb { int defaultMajorVersion, defaultMinorVersion; rdr::InStream* is; rdr::OutStream* os; + network::UdpStream *udps; SMsgReader* reader_; SMsgWriter* writer_; SecurityServer security; diff --git a/common/rfb/SMsgHandler.cxx b/common/rfb/SMsgHandler.cxx index c9275aa..ac9e588 100644 --- a/common/rfb/SMsgHandler.cxx +++ b/common/rfb/SMsgHandler.cxx @@ -104,4 +104,3 @@ void SMsgHandler::setDesktopSize(int fb_width, int fb_height, cp.height = fb_height; cp.screenLayout = layout; } - diff --git a/common/rfb/SMsgHandler.h b/common/rfb/SMsgHandler.h index 053b215..4e656cc 100644 --- a/common/rfb/SMsgHandler.h +++ b/common/rfb/SMsgHandler.h @@ -95,6 +95,9 @@ namespace rfb { // handler will send a pseudo-rect back, signalling server support. virtual void supportsQEMUKeyEvent(); + virtual void udpUpgrade(const char *resp) = 0; + virtual void udpDowngrade() = 0; + ConnParams cp; }; } diff --git a/common/rfb/SMsgReader.cxx b/common/rfb/SMsgReader.cxx index 9bfdaa2..f60ca1a 100644 --- a/common/rfb/SMsgReader.cxx +++ b/common/rfb/SMsgReader.cxx @@ -17,6 +17,7 @@ * USA. */ #include +#include #include #include @@ -96,6 +97,9 @@ void SMsgReader::readMsg() case msgTypeQEMUClientMessage: readQEMUMessage(); break; + case msgTypeUpgradeToUdp: + readUpgradeToUdp(); + break; default: fprintf(stderr, "unknown message type %d\n", msgType); throw Exception("unknown message type"); @@ -329,3 +333,27 @@ void SMsgReader::readQEMUKeyEvent() } handler->keyEvent(keysym, keycode, down); } + +void SMsgReader::readUpgradeToUdp() +{ + char buf[4096], resp[4096]; + rdr::U16 len = is->readU16(); + + if (len >= sizeof(buf)) { + vlog.error("Ignoring udp upgrade with too large payload"); + is->skip(len); + return; + } + + if (!len) { + handler->udpDowngrade(); + return; + } + + is->readBytes(buf, len); + buf[len] = '\0'; + + wuGotHttp(buf, len, resp); + + handler->udpUpgrade(resp); +} diff --git a/common/rfb/SMsgReader.h b/common/rfb/SMsgReader.h index 8a1eb34..7e9f5b0 100644 --- a/common/rfb/SMsgReader.h +++ b/common/rfb/SMsgReader.h @@ -63,6 +63,8 @@ namespace rfb { void readQEMUMessage(); void readQEMUKeyEvent(); + void readUpgradeToUdp(); + SMsgHandler* handler; rdr::InStream* is; }; diff --git a/common/rfb/SMsgWriter.cxx b/common/rfb/SMsgWriter.cxx index 5c5562c..ea77081 100644 --- a/common/rfb/SMsgWriter.cxx +++ b/common/rfb/SMsgWriter.cxx @@ -37,8 +37,8 @@ using namespace rfb; static LogWriter vlog("SMsgWriter"); -SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_) - : cp(cp_), os(os_), +SMsgWriter::SMsgWriter(ConnParams* cp_, rdr::OutStream* os_, rdr::OutStream* udps_) + : cp(cp_), os(os_), udps(udps_), nRectsInUpdate(0), nRectsInHeader(0), needSetDesktopSize(false), needExtendedDesktopSize(false), needSetDesktopName(false), needSetCursor(false), @@ -362,6 +362,16 @@ void SMsgWriter::writeFramebufferUpdateEnd() os->writeU16(0); os->writeU16(0); os->writeU32(pseudoEncodingLastRect); + + // Send an UDP flip marker, if needed + if (cp->supportsUdp) { + udps->writeS16(0); + udps->writeS16(0); + udps->writeU16(0); + udps->writeU16(0); + udps->writeU32(pseudoEncodingLastRect); + udps->flush(); + } } endMsg(); @@ -370,8 +380,13 @@ void SMsgWriter::writeFramebufferUpdateEnd() void SMsgWriter::writeCopyRect(const Rect& r, int srcX, int srcY) { startRect(r,encodingCopyRect); - os->writeU16(srcX); - os->writeU16(srcY); + if (cp->supportsUdp) { + udps->writeU16(srcX); + udps->writeU16(srcY); + } else { + os->writeU16(srcX); + os->writeU16(srcY); + } endRect(); } @@ -380,16 +395,27 @@ void SMsgWriter::startRect(const Rect& r, int encoding) if (++nRectsInUpdate > nRectsInHeader && nRectsInHeader) throw Exception("SMsgWriter::startRect: nRects out of sync"); - os->writeS16(r.tl.x); - os->writeS16(r.tl.y); - os->writeU16(r.width()); - os->writeU16(r.height()); - os->writeU32(encoding); + if (cp->supportsUdp) { + udps->writeS16(r.tl.x); + udps->writeS16(r.tl.y); + udps->writeU16(r.width()); + udps->writeU16(r.height()); + udps->writeU32(encoding); + } else { + os->writeS16(r.tl.x); + os->writeS16(r.tl.y); + os->writeU16(r.width()); + os->writeU16(r.height()); + os->writeU32(encoding); + } } void SMsgWriter::endRect() { - os->flush(); + if (cp->supportsUdp) + udps->flush(); + else + os->flush(); } void SMsgWriter::startMsg(int type) @@ -712,3 +738,14 @@ void SMsgWriter::writeQEMUKeyEventRect() os->writeU16(0); os->writeU32(pseudoEncodingQEMUKeyEvent); } + +void SMsgWriter::writeUdpUpgrade(const char *resp) +{ + startMsg(msgTypeUpgradeToUdp); + + rdr::U16 len = strlen(resp); + os->writeU16(len); + os->writeBytes(resp, len); + + endMsg(); +} diff --git a/common/rfb/SMsgWriter.h b/common/rfb/SMsgWriter.h index c72012e..71b6fcd 100644 --- a/common/rfb/SMsgWriter.h +++ b/common/rfb/SMsgWriter.h @@ -38,7 +38,7 @@ namespace rfb { class SMsgWriter { public: - SMsgWriter(ConnParams* cp, rdr::OutStream* os); + SMsgWriter(ConnParams* cp, rdr::OutStream* os, rdr::OutStream *udps); virtual ~SMsgWriter(); // writeServerInit() must only be called at the appropriate time in the @@ -127,6 +127,8 @@ namespace rfb { void startRect(const Rect& r, int enc); void endRect(); + void writeUdpUpgrade(const char *resp); + protected: void startMsg(int type); void endMsg(); @@ -157,6 +159,7 @@ namespace rfb { ConnParams* cp; rdr::OutStream* os; + rdr::OutStream* udps; int nRectsInUpdate; int nRectsInHeader; diff --git a/common/rfb/ServerCore.cxx b/common/rfb/ServerCore.cxx index 23832ec..5f01dae 100644 --- a/common/rfb/ServerCore.cxx +++ b/common/rfb/ServerCore.cxx @@ -217,6 +217,16 @@ rfb::StringParameter rfb::Server::kasmPasswordFile "Password file for BasicAuth, created with the kasmvncpasswd utility.", "~/.kasmpasswd"); +rfb::StringParameter rfb::Server::publicIP +("publicIP", + "The server's public IP, for UDP negotiation. If not set, will be queried via the internet.", + ""); + +rfb::IntParameter rfb::Server::udpFullFrameFrequency +("udpFullFrameFrequency", + "Send a full frame every N frames for clients using UDP. 0 to disable", + 0, 0, 1000); + static void bandwidthPreset() { rfb::Server::dynamicQualityMin.setParam(2); rfb::Server::dynamicQualityMax.setParam(9); diff --git a/common/rfb/ServerCore.h b/common/rfb/ServerCore.h index 9717cc1..9ddfba6 100644 --- a/common/rfb/ServerCore.h +++ b/common/rfb/ServerCore.h @@ -60,7 +60,9 @@ namespace rfb { static IntParameter videoOutTime; static IntParameter videoArea; static IntParameter videoScaling; + static IntParameter udpFullFrameFrequency; static StringParameter kasmPasswordFile; + static StringParameter publicIP; static BoolParameter printVideoArea; static BoolParameter protocol3_3; static BoolParameter alwaysShared; diff --git a/common/rfb/TightEncoder.cxx b/common/rfb/TightEncoder.cxx index 0d428f1..beed4e4 100644 --- a/common/rfb/TightEncoder.cxx +++ b/common/rfb/TightEncoder.cxx @@ -104,7 +104,7 @@ void TightEncoder::writeSolidRect(int width, int height, { rdr::OutStream* os; - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); os->writeU8(tightFill << 4); writePixels(colour, pf, 1, os); @@ -165,9 +165,11 @@ void TightEncoder::writeFullColourRect(const PixelBuffer* pb, const Palette& pal const rdr::U8* buffer; int stride, h; - os = conn->getOutStream(); - - os->writeU8(streamId << 4); + os = conn->getOutStream(conn->cp.supportsUdp); + if (conn->cp.supportsUdp) + os->writeU8((streamId << 4) | (1 << streamId)); + else + os->writeU8(streamId << 4); // Set up compression if ((pb->getPF().bpp != 32) || !pb->getPF().is888()) @@ -238,13 +240,15 @@ rdr::OutStream* TightEncoder::getZlibOutStream(int streamId, int level, size_t l // Minimum amount of data to be compressed. This value should not be // changed, doing so will break compatibility with existing clients. if (length < 12) - return conn->getOutStream(); + return conn->getOutStream(conn->cp.supportsUdp); assert(streamId >= 0); assert(streamId < 4); zlibStreams[streamId].setUnderlying(&memStream); zlibStreams[streamId].setCompressionLevel(level); + if (conn->cp.supportsUdp) + zlibStreams[streamId].resetDeflate(); return &zlibStreams[streamId]; } @@ -261,7 +265,7 @@ void TightEncoder::flushZlibOutStream(rdr::OutStream* os_) zos->flush(); zos->setUnderlying(NULL); - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); writeCompact(os, memStream.length()); os->writeBytes(memStream.data(), memStream.length()); diff --git a/common/rfb/TightEncoderBPP.cxx b/common/rfb/TightEncoderBPP.cxx index 8874662..5721c27 100644 --- a/common/rfb/TightEncoderBPP.cxx +++ b/common/rfb/TightEncoderBPP.cxx @@ -38,9 +38,12 @@ void TightEncoder::writeMonoRect(int width, int height, assert(palette.size() == 2); - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); - os->writeU8((streamId | tightExplicitFilter) << 4); + if (conn->cp.supportsUdp) + os->writeU8(((streamId | tightExplicitFilter) << 4) | (1 << streamId)); + else + os->writeU8((streamId | tightExplicitFilter) << 4); os->writeU8(tightFilterPalette); // Write the palette @@ -125,9 +128,12 @@ void TightEncoder::writeIndexedRect(int width, int height, assert(palette.size() > 0); assert(palette.size() <= 256); - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); - os->writeU8((streamId | tightExplicitFilter) << 4); + if (conn->cp.supportsUdp) + os->writeU8(((streamId | tightExplicitFilter) << 4) | (1 << streamId)); + else + os->writeU8((streamId | tightExplicitFilter) << 4); os->writeU8(tightFilterPalette); // Write the palette diff --git a/common/rfb/TightJPEGEncoder.cxx b/common/rfb/TightJPEGEncoder.cxx index c89d840..8041b76 100644 --- a/common/rfb/TightJPEGEncoder.cxx +++ b/common/rfb/TightJPEGEncoder.cxx @@ -147,7 +147,7 @@ void TightJPEGEncoder::writeOnly(const std::vector &out) const { rdr::OutStream* os; - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); os->writeU8(tightJpeg << 4); @@ -184,7 +184,7 @@ void TightJPEGEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) jc.compress(buffer, stride, pb->getRect(), pb->getPF(), quality, subsampling); - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); os->writeU8(tightJpeg << 4); diff --git a/common/rfb/TightWEBPEncoder.cxx b/common/rfb/TightWEBPEncoder.cxx index 74c109c..89d9b3a 100644 --- a/common/rfb/TightWEBPEncoder.cxx +++ b/common/rfb/TightWEBPEncoder.cxx @@ -188,7 +188,7 @@ void TightWEBPEncoder::writeOnly(const std::vector &out) const { rdr::OutStream* os; - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); os->writeU8(tightWebp << 4); @@ -248,7 +248,7 @@ void TightWEBPEncoder::writeRect(const PixelBuffer* pb, const Palette& palette) vlog.error("WEBP error %u", pic.error_code); } - os = conn->getOutStream(); + os = conn->getOutStream(conn->cp.supportsUdp); os->writeU8(tightWebp << 4); diff --git a/common/rfb/VNCSConnectionST.cxx b/common/rfb/VNCSConnectionST.cxx index 1738330..e3f56f2 100644 --- a/common/rfb/VNCSConnectionST.cxx +++ b/common/rfb/VNCSConnectionST.cxx @@ -53,7 +53,7 @@ extern rfb::BoolParameter disablebasicauth; VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, bool reverse) - : sock(s), reverseConnection(reverse), + : upgradingToUdp(false), sock(s), reverseConnection(reverse), inProcessMessages(false), pendingSyncFence(false), syncFence(false), fenceFlags(0), fenceDataLen(0), fenceData(NULL), congestionTimer(this), @@ -63,7 +63,8 @@ VNCSConnectionST::VNCSConnectionST(VNCServerST* server_, network::Socket *s, continuousUpdates(false), encodeManager(this, &server_->encCache), needsPermCheck(false), pointerEventTime(0), clientHasCursor(false), - accessRights(AccessDefault), startTime(time(0)), frameTracking(false) + accessRights(AccessDefault), startTime(time(0)), frameTracking(false), + udpFramesSinceFull(0) { setStreams(&sock->inStream(), &sock->outStream()); peerEndpoint.buf = sock->getPeerEndpoint(); @@ -1231,7 +1232,7 @@ bool VNCSConnectionST::isCongested() if (sock->outStream().bufferUsage() > 0) return true; - if (!cp.supportsFence) + if (!cp.supportsFence || cp.supportsUdp) return false; congestion.updatePosition(sock->outStream().length()); @@ -1462,6 +1463,14 @@ void VNCSConnectionST::writeDataUpdate() if (!pending.is_empty()) ui.copypassed.clear(); + // Do we need to send a full frame? + if (Server::udpFullFrameFrequency && cp.supportsUdp) { + if (udpFramesSinceFull >= (unsigned) Server::udpFullFrameFrequency) { + udpFramesSinceFull = 0; + ui.changed.assign_union(Region(Rect(0, 0, cp.width, cp.height))); + } + } + // Return if there is nothing to send the client. const unsigned losslessThreshold = 80 + 2 * 1000 / Server::frameRate; @@ -1518,6 +1527,9 @@ void VNCSConnectionST::writeDataUpdate() updates.subtract(req); requested.clear(); + + if (Server::udpFullFrameFrequency && cp.supportsUdp) + udpFramesSinceFull++; } void VNCSConnectionST::writeBinaryClipboard() @@ -1745,3 +1757,22 @@ bool VNCSConnectionST::checkOwnerConn() const return false; } + +void VNCSConnectionST::udpUpgrade(const char *resp) +{ + if (resp[0] == 'H') { + vlog.info("Client %s requested upgrade to udp, but WebUdp refused", sock->getPeerAddress()); + } else { + vlog.info("Client %s requesting upgrade to udp", sock->getPeerAddress()); + upgradingToUdp = true; + } + writer()->writeUdpUpgrade(resp); +} + +void VNCSConnectionST::udpDowngrade() +{ + cp.supportsUdp = false; + cp.useCopyRect = true; + + vlog.info("Client %s downgrading from udp", sock->getPeerAddress()); +} diff --git a/common/rfb/VNCSConnectionST.h b/common/rfb/VNCSConnectionST.h index d60155b..c60e74f 100644 --- a/common/rfb/VNCSConnectionST.h +++ b/common/rfb/VNCSConnectionST.h @@ -196,6 +196,8 @@ namespace rfb { return encodeManager.getScalingTime(); } + bool upgradingToUdp; + private: // SConnection callbacks @@ -217,6 +219,8 @@ namespace rfb { int x, int y, int w, int h); virtual void handleClipboardAnnounce(bool available); virtual void handleClipboardAnnounceBinary(const unsigned num, const char mimes[][32]); + virtual void udpUpgrade(const char *resp); + virtual void udpDowngrade(); virtual void supportsLocalCursor(); virtual void supportsFence(); virtual void supportsContinuousUpdates(); @@ -318,6 +322,7 @@ namespace rfb { std::vector copypassed; bool frameTracking; + uint32_t udpFramesSinceFull; }; } #endif diff --git a/common/rfb/VNCServerST.cxx b/common/rfb/VNCServerST.cxx index f440894..a6f7090 100644 --- a/common/rfb/VNCServerST.cxx +++ b/common/rfb/VNCServerST.cxx @@ -52,6 +52,7 @@ #include #include +#include #include #include @@ -66,6 +67,7 @@ #include +#include #include #include #include @@ -796,8 +798,43 @@ int VNCServerST::msToNextUpdate() return frameTimer.getRemainingMs(); } +static void upgradeClientToUdp(const network::GetAPIMessager::action_data &act, + std::list &clients) +{ + std::list::iterator ci, ci_next; + + for (ci = clients.begin(); ci != clients.end(); ci = ci_next) { + ci_next = ci; ci_next++; + + if (!(*ci)->upgradingToUdp) + continue; + + char buf[32]; + inet_ntop(AF_INET, &act.udp.ip, buf, 32); + + const char * const who = (*ci)->getPeerEndpoint(); + const char *start = strchr(who, '@'); + if (!start) + continue; + start++; + + // Slightly inaccurate, if several clients on the same IP try to upgrade at the same time + if (strncmp(start, buf, strlen(buf))) + continue; + + (*ci)->upgradingToUdp = false; + (*ci)->cp.useCopyRect = false; + ((network::UdpStream *)(*ci)->getOutStream(true))->setClient((WuClient *) act.udp.client); + (*ci)->cp.supportsUdp = true; + + slog.info("%s upgraded to UDP", who); + return; + } +} + static void checkAPIMessages(network::GetAPIMessager *apimessager, - rdr::U8 &trackingFrameStats, char trackingClient[]) + rdr::U8 &trackingFrameStats, char trackingClient[], + std::list &clients) { if (pthread_mutex_lock(&apimessager->userMutex)) return; @@ -826,6 +863,9 @@ static void checkAPIMessages(network::GetAPIMessager *apimessager, trackingFrameStats = act.action; memcpy(trackingClient, act.data.password, 128); break; + case network::GetAPIMessager::UDP_UPGRADE: + upgradeClientToUdp(act, clients); + break; } } @@ -991,7 +1031,7 @@ void VNCServerST::writeUpdate() shottime = msSince(&shotstart); trackingFrameStats = 0; - checkAPIMessages(apimessager, trackingFrameStats, trackingClient); + checkAPIMessages(apimessager, trackingFrameStats, trackingClient, clients); } const rdr::U8 origtrackingFrameStats = trackingFrameStats; diff --git a/common/rfb/VNCServerST.h b/common/rfb/VNCServerST.h index 21ce5a9..71c1f32 100644 --- a/common/rfb/VNCServerST.h +++ b/common/rfb/VNCServerST.h @@ -43,7 +43,6 @@ namespace rfb { class ListConnInfo; class PixelBuffer; class KeyRemapper; - class network::GetAPIMessager; class VNCServerST : public VNCServer, public Timer::Callback, diff --git a/common/rfb/encodings.h b/common/rfb/encodings.h index d5ce64b..6bb319f 100644 --- a/common/rfb/encodings.h +++ b/common/rfb/encodings.h @@ -27,6 +27,7 @@ namespace rfb { const int encodingCoRRE = 4; const int encodingHextile = 5; const int encodingTight = 7; + const int encodingUdp = 8; const int encodingZRLE = 16; const int encodingMax = 255; diff --git a/common/rfb/msgTypes.h b/common/rfb/msgTypes.h index ea8e007..11805e3 100644 --- a/common/rfb/msgTypes.h +++ b/common/rfb/msgTypes.h @@ -32,6 +32,7 @@ namespace rfb { const int msgTypeStats = 178; const int msgTypeRequestFrameStats = 179; const int msgTypeBinaryClipboard = 180; + const int msgTypeUpgradeToUdp = 181; const int msgTypeServerFence = 248; @@ -50,7 +51,9 @@ namespace rfb { // kasm const int msgTypeRequestStats = 178; const int msgTypeFrameStats = 179; + // same as the other direction //const int msgTypeBinaryClipboard = 180; + //const int msgTypeUpgradeToUdp = 181; const int msgTypeClientFence = 248; diff --git a/unix/xserver/hw/vnc/Xvnc.man b/unix/xserver/hw/vnc/Xvnc.man index 7cafa02..d0ea160 100644 --- a/unix/xserver/hw/vnc/Xvnc.man +++ b/unix/xserver/hw/vnc/Xvnc.man @@ -122,6 +122,15 @@ Password file for BasicAuth, created with the \fBkasmvncpasswd\fP utility. Default \fI~/.kasmpasswd\fP. . .TP +.B \-PublicIP \fImy-ip\fP +The server's public IP, for UDP negotiation. If not set, will be queried via the internet. +Default unset. +. +.TP +.B \-udpFullFrameFrequency \fIframes\fP +Send a full frame every N frames for clients using UDP. 0 to disable. Default \fI0\fP. +. +.TP .B \-AcceptCutText Accept clipboard updates from clients. Default is on. . diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc index 503b2b3..930c4c2 100644 --- a/unix/xserver/hw/vnc/vncExtInit.cc +++ b/unix/xserver/hw/vnc/vncExtInit.cc @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -199,8 +200,10 @@ void vncExtensionInit(void) vncAddExtension(); - if (!initialised) + if (!initialised) { parseClipTypes(); + getPublicIP(); + } vncSelectionInit(); vlog.info("VNC extension running!");