Merge branch 'udp' into 'master'
Udp See merge request kasm-technologies/internal/KasmVNC!52release/1.0.0
commit
6fd2ad874f
@ -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);
|
||||||
|
}
|
@ -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__
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
@ -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.
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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.
|
@ -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.
|
@ -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;
|
||||||
|
}
|
@ -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
|
@ -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); }
|
@ -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);
|
@ -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;
|
||||||
|
}
|
@ -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());
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -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);
|
@ -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
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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) {}
|
@ -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;
|
@ -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;
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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(); }
|
@ -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);
|
@ -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; }
|
@ -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);
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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;
|
||||||
|
}
|
@ -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);
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue