mirror of
https://github.com/kasmtech/KasmVNC.git
synced 2024-11-24 17:14:01 +01:00
Udp
This commit is contained in:
parent
ba902f8194
commit
3b40a92548
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
155
common/network/Udp.cxx
Normal 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
53
common/network/Udp.h
Normal 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
185
common/network/iceip.cxx
Normal 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
23
common/network/iceip.h
Normal 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
|
@ -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);
|
||||
|
38
common/network/webudp/CHANGELOG.md
Normal file
38
common/network/webudp/CHANGELOG.md
Normal 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.
|
124
common/network/webudp/CRC32.cpp
Normal file
124
common/network/webudp/CRC32.cpp
Normal 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;
|
||||
}
|
7
common/network/webudp/CRC32.h
Normal file
7
common/network/webudp/CRC32.h
Normal 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);
|
21
common/network/webudp/LICENSE
Normal file
21
common/network/webudp/LICENSE
Normal 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.
|
20
common/network/webudp/README.md
Normal file
20
common/network/webudp/README.md
Normal 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.
|
771
common/network/webudp/Wu.cpp
Normal file
771
common/network/webudp/Wu.cpp
Normal 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;
|
||||
}
|
76
common/network/webudp/Wu.h
Normal file
76
common/network/webudp/Wu.h
Normal 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
|
26
common/network/webudp/WuArena.cpp
Normal file
26
common/network/webudp/WuArena.cpp
Normal 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); }
|
14
common/network/webudp/WuArena.h
Normal file
14
common/network/webudp/WuArena.h
Normal 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);
|
48
common/network/webudp/WuBufferOp.h
Normal file
48
common/network/webudp/WuBufferOp.h
Normal 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;
|
||||
}
|
34
common/network/webudp/WuClock.h
Normal file
34
common/network/webudp/WuClock.h
Normal 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());
|
||||
}
|
65
common/network/webudp/WuCrypto.cpp
Normal file
65
common/network/webudp/WuCrypto.cpp
Normal 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);
|
||||
}
|
23
common/network/webudp/WuCrypto.h
Normal file
23
common/network/webudp/WuCrypto.h
Normal 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);
|
36
common/network/webudp/WuHost.h
Normal file
36
common/network/webudp/WuHost.h
Normal 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
|
268
common/network/webudp/WuHostEpoll.cpp
Normal file
268
common/network/webudp/WuHostEpoll.cpp
Normal 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);
|
||||
}
|
||||
}
|
11
common/network/webudp/WuHostNull.cpp
Normal file
11
common/network/webudp/WuHostNull.cpp
Normal 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) {}
|
9
common/network/webudp/WuHttp.h
Normal file
9
common/network/webudp/WuHttp.h
Normal 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;
|
15
common/network/webudp/WuMath.h
Normal file
15
common/network/webudp/WuMath.h
Normal 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;
|
||||
}
|
58
common/network/webudp/WuNetwork.cpp
Normal file
58
common/network/webudp/WuNetwork.cpp
Normal 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;
|
||||
}
|
9
common/network/webudp/WuNetwork.h
Normal file
9
common/network/webudp/WuNetwork.h
Normal 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);
|
60
common/network/webudp/WuPool.cpp
Normal file
60
common/network/webudp/WuPool.cpp
Normal 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;
|
||||
}
|
10
common/network/webudp/WuPool.h
Normal file
10
common/network/webudp/WuPool.h
Normal 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);
|
58
common/network/webudp/WuQueue.cpp
Normal file
58
common/network/webudp/WuQueue.cpp
Normal 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;
|
||||
}
|
16
common/network/webudp/WuQueue.h
Normal file
16
common/network/webudp/WuQueue.h
Normal 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);
|
51
common/network/webudp/WuRng.cpp
Normal file
51
common/network/webudp/WuRng.cpp
Normal 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(); }
|
14
common/network/webudp/WuRng.h
Normal file
14
common/network/webudp/WuRng.h
Normal 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);
|
169
common/network/webudp/WuSctp.cpp
Normal file
169
common/network/webudp/WuSctp.cpp
Normal 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; }
|
100
common/network/webudp/WuSctp.h
Normal file
100
common/network/webudp/WuSctp.h
Normal 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);
|
152
common/network/webudp/WuSdp.cpp
Normal file
152
common/network/webudp/WuSdp.cpp
Normal 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;
|
||||
}
|
24
common/network/webudp/WuSdp.h
Normal file
24
common/network/webudp/WuSdp.h
Normal 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);
|
19
common/network/webudp/WuString.cpp
Normal file
19
common/network/webudp/WuString.cpp
Normal 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;
|
||||
}
|
6
common/network/webudp/WuString.h
Normal file
6
common/network/webudp/WuString.h
Normal 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);
|
134
common/network/webudp/WuStun.cpp
Normal file
134
common/network/webudp/WuStun.cpp
Normal 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;
|
||||
}
|
57
common/network/webudp/WuStun.h
Normal file
57
common/network/webudp/WuStun.h
Normal 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);
|
19
common/network/webudp/package.json
Normal file
19
common/network/webudp/package.json
Normal 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"
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
@ -43,6 +43,8 @@ namespace rdr {
|
||||
void flush();
|
||||
size_t length();
|
||||
|
||||
void resetDeflate();
|
||||
|
||||
private:
|
||||
|
||||
virtual void overrun(size_t needed);
|
||||
|
@ -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)
|
||||
|
@ -117,6 +117,8 @@ namespace rfb {
|
||||
bool supportsContinuousUpdates;
|
||||
bool supportsExtendedClipboard;
|
||||
|
||||
bool supportsUdp;
|
||||
|
||||
int compressLevel;
|
||||
int qualityLevel;
|
||||
int fineQualityLevel;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -104,4 +104,3 @@ void SMsgHandler::setDesktopSize(int fb_width, int fb_height,
|
||||
cp.height = fb_height;
|
||||
cp.screenLayout = layout;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
};
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ namespace rfb {
|
||||
void readQEMUMessage();
|
||||
void readQEMUKeyEvent();
|
||||
|
||||
void readUpgradeToUdp();
|
||||
|
||||
SMsgHandler* handler;
|
||||
rdr::InStream* is;
|
||||
};
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
|
@ -43,7 +43,6 @@ namespace rfb {
|
||||
class ListConnInfo;
|
||||
class PixelBuffer;
|
||||
class KeyRemapper;
|
||||
class network::GetAPIMessager;
|
||||
|
||||
class VNCServerST : public VNCServer,
|
||||
public Timer::Callback,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
.
|
||||
|
@ -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!");
|
||||
|
Loading…
Reference in New Issue
Block a user