This commit is contained in:
Lauri Kasanen 2022-07-26 10:38:14 +00:00 committed by Matthew McClaskey
parent ba902f8194
commit 3b40a92548
72 changed files with 3314 additions and 52 deletions

View File

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

View File

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

View File

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

View File

@ -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);
}

View File

@ -42,10 +42,14 @@
#include <wordexp.h>
#include <sys/types.h>
#include <unistd.h>
#include <openssl/crypto.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include "websocket.h"
#include <network/GetAPI.h>
#include <network/TcpSocket.h>
#include <network/Udp.h>
#include <rfb/LogWriter.h>
#include <rfb/Configuration.h>
#include <rfb/ServerCore.h>
@ -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) {

155
common/network/Udp.cxx Normal file
View File

@ -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 <config.h>
#endif
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <stddef.h>
#include <time.h>
#include <network/GetAPI.h>
#include <network/Udp.h>
#include <network/webudp/WuHost.h>
#include <network/webudp/Wu.h>
#include <network/websocket.h>
#include <rfb/LogWriter.h>
#include <rfb/ServerCore.h>
#include <rfb/xxhash.h>
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);
}

53
common/network/Udp.h Normal file
View File

@ -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 <stdint.h>
#include <rdr/OutStream.h>
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__

185
common/network/iceip.cxx Normal file
View File

@ -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 <config.h>
#endif
#include <arpa/inet.h>
#include <errno.h>
#include <limits.h>
#include <netdb.h>
#include <netinet/in.h>
#include <poll.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sys/socket.h>
#include <time.h>
#include <unistd.h>
#include <network/iceip.h>
#include <rfb/LogWriter.h>
#include <rfb/ServerCore.h>
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);
}
}

23
common/network/iceip.h Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
uint32_t StunCRC32(const void* data, int32_t len);
uint32_t SctpCRC32(const void* data, int32_t len);

View File

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

View File

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

View File

@ -0,0 +1,771 @@
#include "Wu.h"
#include <assert.h>
#include <openssl/ec.h>
#include <openssl/err.h>
#include <openssl/ssl.h>
#include <string.h>
#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;
}

View File

@ -0,0 +1,76 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
#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

View File

@ -0,0 +1,26 @@
#include "WuArena.h"
#include <assert.h>
#include <stdlib.h>
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); }

View File

@ -0,0 +1,14 @@
#pragma once
#include <stdint.h>
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);

View File

@ -0,0 +1,48 @@
#pragma once
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
template <typename T>
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 <typename T>
size_t WriteScalar(uint8_t* dest, T v) {
*((T*)dest) = v;
return sizeof(T);
}
template <typename T>
int32_t ReadScalar(const uint8_t* src, T* v) {
*v = *(const T*)src;
return sizeof(T);
}
template <typename T>
size_t WriteScalarSwapped(uint8_t* dest, T v) {
*((T*)dest) = ByteSwap(v);
return sizeof(T);
}
template <typename T>
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;
}

View File

@ -0,0 +1,34 @@
#pragma once
#ifdef _WIN32
#include <windows.h>
#else
#include <sys/time.h>
#endif
#include <stdint.h>
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());
}

View File

@ -0,0 +1,65 @@
#include "WuCrypto.h"
#include <assert.h>
#include <openssl/hmac.h>
#include <openssl/rand.h>
#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);
}

View File

@ -0,0 +1,23 @@
#pragma once
#include <openssl/x509.h>
#include <stddef.h>
#include <stdint.h>
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);

View File

@ -0,0 +1,36 @@
#pragma once
#include <stdint.h>
#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

View File

@ -0,0 +1,268 @@
#include <errno.h>
#include <netdb.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#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);
}
}

View File

@ -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) {}

View File

@ -0,0 +1,9 @@
#pragma once
#include <stddef.h>
#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;

View File

@ -0,0 +1,15 @@
#pragma once
template <typename T>
const T& Min(const T& a, const T& b) {
if (a < b) return a;
return b;
}
template <typename T>
const T& Max(const T& a, const T& b) {
if (a > b) return a;
return b;
}

View File

@ -0,0 +1,58 @@
#include "WuNetwork.h"
#include <arpa/inet.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
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;
}

View File

@ -0,0 +1,9 @@
#pragma once
#include <assert.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
void HexDump(const uint8_t* src, size_t len);
int MakeNonBlocking(int sfd);
int CreateSocket(uint16_t port);

View File

@ -0,0 +1,60 @@
#include "WuPool.h"
#include <assert.h>
#include <stdlib.h>
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;
}

View File

@ -0,0 +1,10 @@
#pragma once
#include <stdint.h>
struct WuPool;
WuPool* WuPoolCreate(int32_t blockSize, int32_t numBlocks);
void WuPoolDestroy(WuPool* pool);
void* WuPoolAcquire(WuPool* pool);
void WuPoolRelease(WuPool* pool, void* ptr);

View File

@ -0,0 +1,58 @@
#include "WuQueue.h"
#include <stdlib.h>
#include <string.h>
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;
}

View File

@ -0,0 +1,16 @@
#pragma once
#include <stdint.h>
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);

View File

@ -0,0 +1,51 @@
#include <stdlib.h>
#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(); }

View File

@ -0,0 +1,14 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
// 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);

View File

@ -0,0 +1,169 @@
#include "WuSctp.h"
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#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; }

View File

@ -0,0 +1,100 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
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);

View File

@ -0,0 +1,152 @@
#include "WuSdp.h"
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -0,0 +1,24 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
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);

View File

@ -0,0 +1,19 @@
#include "WuString.h"
#include <ctype.h>
#include <stdint.h>
#include <string.h>
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;
}

View File

@ -0,0 +1,6 @@
#include <stddef.h>
#include <stdint.h>
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);

View File

@ -0,0 +1,134 @@
#include "WuStun.h"
#include <arpa/inet.h>
#include <string.h>
#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;
}

View File

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

View File

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

View File

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

View File

@ -43,6 +43,8 @@ namespace rdr {
void flush();
size_t length();
void resetDeflate();
private:
virtual void overrun(size_t needed);

View File

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

View File

@ -117,6 +117,8 @@ namespace rfb {
bool supportsContinuousUpdates;
bool supportsExtendedClipboard;
bool supportsUdp;
int compressLevel;
int qualityLevel;
int fineQualityLevel;

View File

@ -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<CopyPassRect>& 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<CopyPassRect>& 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);

View File

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

View File

@ -24,6 +24,7 @@
#ifndef __RFB_SCONNECTION_H__
#define __RFB_SCONNECTION_H__
#include <network/Udp.h>
#include <rdr/InStream.h>
#include <rdr/OutStream.h>
#include <rfb/SMsgHandler.h>
@ -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;

View File

@ -104,4 +104,3 @@ void SMsgHandler::setDesktopSize(int fb_width, int fb_height,
cp.height = fb_height;
cp.screenLayout = layout;
}

View File

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

View File

@ -17,6 +17,7 @@
* USA.
*/
#include <stdio.h>
#include <network/Udp.h>
#include <rdr/InStream.h>
#include <rdr/ZlibInStream.h>
@ -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);
}

View File

@ -63,6 +63,8 @@ namespace rfb {
void readQEMUMessage();
void readQEMUKeyEvent();
void readUpgradeToUdp();
SMsgHandler* handler;
rdr::InStream* is;
};

View File

@ -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();
}

View File

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

View File

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

View File

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

View File

@ -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());

View File

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

View File

@ -147,7 +147,7 @@ void TightJPEGEncoder::writeOnly(const std::vector<uint8_t> &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);

View File

@ -188,7 +188,7 @@ void TightWEBPEncoder::writeOnly(const std::vector<uint8_t> &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);

View File

@ -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());
}

View File

@ -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<CopyPassRect> copypassed;
bool frameTracking;
uint32_t udpFramesSinceFull;
};
}
#endif

View File

@ -52,6 +52,7 @@
#include <stdlib.h>
#include <network/GetAPI.h>
#include <network/Udp.h>
#include <rfb/cpuid.h>
#include <rfb/ComparingUpdateTracker.h>
@ -66,6 +67,7 @@
#include <rdr/types.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/inotify.h>
#include <unistd.h>
@ -796,8 +798,43 @@ int VNCServerST::msToNextUpdate()
return frameTimer.getRemainingMs();
}
static void upgradeClientToUdp(const network::GetAPIMessager::action_data &act,
std::list<VNCSConnectionST*> &clients)
{
std::list<VNCSConnectionST*>::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<VNCSConnectionST*> &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;

View File

@ -43,7 +43,6 @@ namespace rfb {
class ListConnInfo;
class PixelBuffer;
class KeyRemapper;
class network::GetAPIMessager;
class VNCServerST : public VNCServer,
public Timer::Callback,

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@
#include <rfb/Hostname.h>
#include <rfb/Region.h>
#include <rfb/ledStates.h>
#include <network/iceip.h>
#include <network/TcpSocket.h>
#include <network/UnixSocket.h>
@ -199,8 +200,10 @@ void vncExtensionInit(void)
vncAddExtension();
if (!initialised)
if (!initialised) {
parseClipTypes();
getPublicIP();
}
vncSelectionInit();
vlog.info("VNC extension running!");