diff options
| author | eeeee <eeeee@qwe123.info> | 2015-02-07 14:15:58 -0800 |
|---|---|---|
| committer | def <dennis@felsin9.de> | 2015-04-19 16:05:39 +0200 |
| commit | ed763c52869ed05f326edf73c79a8ac8a6e35980 (patch) | |
| tree | 2f81c70e8c74ed37e7efb17a8c5fd884ed50706e | |
| parent | 424ce4987ced7deda3e355b7b6373680bf506646 (diff) | |
| download | zcatch-ed763c52869ed05f326edf73c79a8ac8a6e35980.tar.gz zcatch-ed763c52869ed05f326edf73c79a8ac8a6e35980.zip | |
websockets support in server
Conflicts: bam.lua
| -rw-r--r-- | bam.lua | 14 | ||||
| -rw-r--r-- | src/base/system.c | 73 | ||||
| -rw-r--r-- | src/base/system.h | 4 | ||||
| -rw-r--r-- | src/engine/shared/websockets.cpp | 275 | ||||
| -rw-r--r-- | src/engine/shared/websockets.h | 16 |
5 files changed, 376 insertions, 6 deletions
diff --git a/bam.lua b/bam.lua index 4e70af36..1f6b63f4 100644 --- a/bam.lua +++ b/bam.lua @@ -14,6 +14,7 @@ config:Add(OptTestCompileC("macosxppc", "int main(){return 0;}", "-arch ppc")) config:Add(OptLibrary("zlib", "zlib.h", false)) config:Add(SDL.OptFind("sdl", true)) config:Add(FreeType.OptFind("freetype", true)) +config:Add(OptString("websockets", false)) config:Finalize("config.lua") -- data compiler @@ -160,6 +161,9 @@ function build(settings) if ldflags then settings.link.flags:Add(ldflags) end + if config.websockets.value then + settings.cc.defines:Add("WEBSOCKETS") + end if config.compiler.driver == "cl" then settings.cc.flags:Add("/wd4244") @@ -225,6 +229,10 @@ function build(settings) pnglite = Compile(settings, Collect("src/engine/external/pnglite/*.c")) jsonparser = Compile(settings, Collect("src/engine/external/json-parser/*.c")) + if config.websockets.value then + libwebsockets = Compile(settings, Collect("src/engine/external/libwebsockets/*.c")) + end + -- build game components engine_settings = settings:Copy() server_settings = engine_settings:Copy() @@ -310,10 +318,10 @@ function build(settings) -- build client, server, version server and master server client_exe = Link(client_settings, "zCatch", game_shared, game_client, engine, client, game_editor, zlib, pnglite, wavpack, - client_link_other, client_osxlaunch, jsonparser) + client_link_other, client_osxlaunch, jsonparser, libwebsockets) server_exe = Link(server_settings, "zCatch-Server", engine, server, - game_shared, game_server, zlib, server_link_other) + game_shared, game_server, zlib, server_link_other, libwebsockets) serverlaunch = {} if platform == "macosx" then @@ -321,7 +329,7 @@ function build(settings) end versionserver_exe = Link(server_settings, "versionsrv", versionserver, - engine, zlib) + engine, zlib, libwebsockets) masterserver_exe = Link(server_settings, "mastersrv", masterserver, engine, zlib) diff --git a/src/base/system.c b/src/base/system.c index 5c6762ef..bc0c261d 100644 --- a/src/base/system.c +++ b/src/base/system.c @@ -9,6 +9,10 @@ #include "system.h" +#if defined(WEBSOCKETS) + #include "engine/shared/websockets.h" +#endif + #if defined(CONF_FAMILY_UNIX) #include <sys/time.h> #include <unistd.h> @@ -64,6 +68,8 @@ static MEMSTATS memory_stats = {0}; static NETSOCKET invalid_socket = {NETTYPE_INVALID, -1, -1}; +#define AF_WEBSOCKET_INET (0xee) + void dbg_logger(DBG_LOGGER logger) { loggers[num_loggers++] = logger; @@ -561,7 +567,7 @@ int64 time_freq() static void netaddr_to_sockaddr_in(const NETADDR *src, struct sockaddr_in *dest) { mem_zero(dest, sizeof(struct sockaddr_in)); - if(src->type != NETTYPE_IPV4) + if(src->type != NETTYPE_IPV4 && src->type != NETTYPE_WEBSOCKET_IPV4) { dbg_msg("system", "couldn't convert NETADDR of type %d to ipv4", src->type); return; @@ -595,6 +601,13 @@ static void sockaddr_to_netaddr(const struct sockaddr *src, NETADDR *dst) dst->port = htons(((struct sockaddr_in*)src)->sin_port); mem_copy(dst->ip, &((struct sockaddr_in*)src)->sin_addr.s_addr, 4); } + else if(src->sa_family == AF_WEBSOCKET_INET) + { + mem_zero(dst, sizeof(NETADDR)); + dst->type = NETTYPE_WEBSOCKET_IPV4; + dst->port = htons(((struct sockaddr_in*)src)->sin_port); + mem_copy(dst->ip, &((struct sockaddr_in*)src)->sin_addr.s_addr, 4); + } else if(src->sa_family == AF_INET6) { mem_zero(dst, sizeof(NETADDR)); @@ -616,7 +629,7 @@ int net_addr_comp(const NETADDR *a, const NETADDR *b) void net_addr_str(const NETADDR *addr, char *string, int max_length, int add_port) { - if(addr->type == NETTYPE_IPV4) + if(addr->type == NETTYPE_IPV4 || addr->type == NETTYPE_WEBSOCKET_IPV4) { if(add_port != 0) str_format(string, max_length, "%d.%d.%d.%d:%d", addr->ip[0], addr->ip[1], addr->ip[2], addr->ip[3], addr->port); @@ -842,6 +855,16 @@ static int priv_net_close_all_sockets(NETSOCKET sock) sock.type &= ~NETTYPE_IPV4; } +#if defined(WEBSOCKETS) + /* close down websocket_ipv4 */ + if(sock.web_ipv4sock >= 0) + { + websocket_destroy(sock.web_ipv4sock); + sock.web_ipv4sock = -1; + sock.type &= ~NETTYPE_WEBSOCKET_IPV4; + } +#endif + /* close down ipv6 */ if(sock.ipv6sock >= 0) { @@ -931,6 +954,25 @@ NETSOCKET net_udp_create(NETADDR bindaddr) } } +#if defined(WEBSOCKETS) + if(bindaddr.type&NETTYPE_WEBSOCKET_IPV4) + { + int socket = -1; + + /* bind, we should check for error */ + tmpbindaddr.type = NETTYPE_WEBSOCKET_IPV4; + + char addr_str[NETADDR_MAXSTRSIZE]; + net_addr_str(&tmpbindaddr, addr_str, sizeof(addr_str), 0); + socket = websocket_create(addr_str, tmpbindaddr.port); + + if (socket >= 0) { + sock.type |= NETTYPE_WEBSOCKET_IPV4; + sock.web_ipv4sock = socket; + } + } +#endif + if(bindaddr.type&NETTYPE_IPV6) { struct sockaddr_in6 addr; @@ -985,6 +1027,16 @@ int net_udp_send(NETSOCKET sock, const NETADDR *addr, const void *data, int size dbg_msg("net", "can't sent ipv4 traffic to this socket"); } +#if defined(WEBSOCKETS) + if(addr->type&NETTYPE_WEBSOCKET_IPV4) + { + if(sock.web_ipv4sock >= 0) + d = websocket_send(sock.web_ipv4sock, (const unsigned char*)data, size, addr->port); + else + dbg_msg("net", "can't sent websocket_ipv4 traffic to this socket"); + } +#endif + if(addr->type&NETTYPE_IPV6) { if(sock.ipv6sock >= 0) @@ -1046,6 +1098,15 @@ int net_udp_recv(NETSOCKET sock, NETADDR *addr, void *data, int maxsize) bytes = recvfrom(sock.ipv6sock, (char*)data, maxsize, 0, (struct sockaddr *)&sockaddrbuf, &fromlen); } +#if defined(WEBSOCKETS) + if(bytes <= 0 && sock.web_ipv4sock >= 0) + { + fromlen = sizeof(struct sockaddr); + bytes = websocket_recv(sock.web_ipv4sock, data, maxsize, (struct sockaddr_in *)&sockaddrbuf, fromlen); + ((struct sockaddr_in *)&sockaddrbuf)->sin_family = AF_WEBSOCKET_INET; + } +#endif + if(bytes > 0) { sockaddr_to_netaddr((struct sockaddr *)&sockaddrbuf, addr); @@ -1520,6 +1581,14 @@ int net_socket_read_wait(NETSOCKET sock, int time) if(sock.ipv6sock > sockid) sockid = sock.ipv6sock; } +#if defined(WEBSOCKETS) + if(sock.web_ipv4sock >= 0) + { + int maxfd = websocket_fd_set(sock.web_ipv4sock, &readfds); + if (maxfd > sockid) + sockid = maxfd; + } +#endif /* don't care about writefds and exceptfds */ select(sockid+1, &readfds, NULL, NULL, &tv); diff --git a/src/base/system.h b/src/base/system.h index 00c58c10..ad606ceb 100644 --- a/src/base/system.h +++ b/src/base/system.h @@ -471,6 +471,7 @@ typedef struct int type; int ipv4sock; int ipv6sock; + int web_ipv4sock; } NETSOCKET; enum @@ -481,7 +482,8 @@ enum NETTYPE_IPV4 = 1, NETTYPE_IPV6 = 2, NETTYPE_LINK_BROADCAST = 4, - NETTYPE_ALL = NETTYPE_IPV4|NETTYPE_IPV6 + NETTYPE_WEBSOCKET_IPV4 = 8, + NETTYPE_ALL = NETTYPE_IPV4|NETTYPE_IPV6|NETTYPE_WEBSOCKET_IPV4 }; typedef struct diff --git a/src/engine/shared/websockets.cpp b/src/engine/shared/websockets.cpp new file mode 100644 index 00000000..795af2c2 --- /dev/null +++ b/src/engine/shared/websockets.cpp @@ -0,0 +1,275 @@ +#if defined(WEBSOCKETS) + +#include <string.h> +#include <stdlib.h> + +#include "engine/external/libwebsockets/libwebsockets.h" +#include "base/system.h" +#include "protocol.h" +#include "ringbuffer.h" +#include <arpa/inet.h> + +extern "C" { + +#include "websockets.h" + +// not sure why would anyone need more than one but well... +#define WS_CONTEXTS 4 +// ddnet client opens two connections for whatever reason +#define WS_CLIENTS MAX_CLIENTS*2 + +typedef TStaticRingBuffer<unsigned char, WS_CLIENTS * 4 * 1024, CRingBufferBase::FLAG_RECYCLE> TRecvBuffer; +typedef TStaticRingBuffer<unsigned char, 4 * 1024, CRingBufferBase::FLAG_RECYCLE> TSendBuffer; + +typedef struct +{ + size_t size; + size_t read; + sockaddr_in addr; + unsigned char data[0]; +} websocket_chunk; + +struct per_session_data +{ + struct libwebsocket *wsi; + int port; + sockaddr_in addr; + TSendBuffer send_buffer; +}; + +struct context_data +{ + libwebsocket_context *context; + per_session_data *port_map[WS_CLIENTS]; + TRecvBuffer recv_buffer; + int last_used_port; +}; + +static int receive_chunk(context_data *ctx_data, struct per_session_data *pss, void *in, size_t len) +{ + websocket_chunk *chunk = (websocket_chunk *)ctx_data->recv_buffer.Allocate(len + sizeof(websocket_chunk)); + if(chunk == 0) + return 1; + chunk->size = len; + chunk->read = 0; + memcpy(&chunk->addr, &pss->addr, sizeof(sockaddr_in)); + memcpy(&chunk->data[0], in, len); + return 0; +} + +static int websocket_callback(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) +{ + struct per_session_data *pss = (struct per_session_data *)user; + context_data *ctx_data = (context_data *)libwebsocket_context_user(context); + + switch(reason) + { + + case LWS_CALLBACK_ESTABLISHED: + { + int port = -1; + for(int i = 0; i < WS_CLIENTS; i++) + { + int j = (ctx_data->last_used_port + i + 1) % WS_CLIENTS; + if(ctx_data->port_map[j] == NULL) + { + port = j; + break; + } + } + if(port == -1) + { + dbg_msg("websockets", "no free ports, dropping"); + pss->port = -1; + return -1; + } + ctx_data->last_used_port = port; + pss->wsi = wsi; + int fd = libwebsocket_get_socket_fd(wsi); + socklen_t addr_size = sizeof(pss->addr); + getpeername(fd, (struct sockaddr *)&pss->addr, &addr_size); + int orig_port = ntohs(pss->addr.sin_port); + pss->addr.sin_port = htons(port); + pss->send_buffer.Init(); + pss->port = port; + ctx_data->port_map[port] = pss; + char addr_str[NETADDR_MAXSTRSIZE]; + inet_ntop(AF_INET, &pss->addr.sin_addr, addr_str, sizeof(addr_str)); + dbg_msg("websockets", "connection established with %s:%d , assigned fake port %d", addr_str, orig_port, port); + } + break; + + case LWS_CALLBACK_CLOSED: + { + dbg_msg("websockets", "connection with fake port %d closed", pss->port); + if (pss->port > -1) { + unsigned char close_packet[] = { 0x10, 0x0e, 0x00, 0x04 }; + receive_chunk(ctx_data, pss, &close_packet, sizeof(close_packet)); + pss->wsi = 0; + ctx_data->port_map[pss->port] = NULL; + } + } + break; + + case LWS_CALLBACK_SERVER_WRITEABLE: + { + websocket_chunk *chunk = (websocket_chunk *)pss->send_buffer.First(); + if(chunk == NULL) + break; + int len = chunk->size - chunk->read; + int n = libwebsocket_write(wsi, &chunk->data[LWS_SEND_BUFFER_PRE_PADDING + chunk->read], chunk->size - chunk->read, LWS_WRITE_BINARY); + if(n < 0) + return 1; + if(n < len) + { + chunk->read += n; + libwebsocket_callback_on_writable(context, wsi); + break; + } + pss->send_buffer.PopFirst(); + libwebsocket_callback_on_writable(context, wsi); + } + break; + + case LWS_CALLBACK_RECEIVE: + if(pss->port == -1) + return -1; + if(!receive_chunk(ctx_data, pss, in, len)) + return 1; + break; + + default: + break; + } + + return 0; +} + +static struct libwebsocket_protocols protocols[] = { { + "binary", /* name */ + websocket_callback, /* callback */ + sizeof(struct per_session_data) /* per_session_data_size */ + }, + { + NULL, NULL, 0 /* End of list */ + } }; + +static context_data contexts[WS_CONTEXTS]; + +int websocket_create(const char *addr, int port) +{ + struct lws_context_creation_info info; + memset(&info, 0, sizeof(info)); + info.port = port; + info.iface = addr; + info.protocols = protocols; + info.gid = -1; + info.uid = -1; + + // find free context + int first_free = -1; + for(int i = 0; i < WS_CONTEXTS; i++) + { + if(contexts[i].context == NULL) + { + first_free = i; + break; + } + } + if(first_free == -1) + return -1; + + context_data *ctx_data = &contexts[first_free]; + info.user = (void *)ctx_data; + + ctx_data->context = libwebsocket_create_context(&info); + if(ctx_data->context == NULL) + { + return -1; + } + memset(ctx_data->port_map, 0, sizeof(ctx_data->port_map)); + ctx_data->recv_buffer.Init(); + ctx_data->last_used_port = 0; + return first_free; +} + +int websocket_destroy(int socket) +{ + libwebsocket_context *context = contexts[socket].context; + if(context == NULL) + return -1; + libwebsocket_context_destroy(context); + contexts[socket].context = NULL; + return 0; +} + +int websocket_recv(int socket, unsigned char *data, size_t maxsize, struct sockaddr_in *sockaddrbuf, size_t fromLen) +{ + libwebsocket_context *context = contexts[socket].context; + if(context == NULL) + return -1; + int n = libwebsocket_service(context, 0); + if(n < 0) + return n; + context_data *ctx_data = (context_data *)libwebsocket_context_user(context); + websocket_chunk *chunk = (websocket_chunk *)ctx_data->recv_buffer.First(); + if(chunk == 0) + return 0; + if(maxsize >= chunk->size - chunk->read) + { + int len = chunk->size - chunk->read; + memcpy(data, &chunk->data[chunk->read], len); + memcpy(sockaddrbuf, &chunk->addr, fromLen); + ctx_data->recv_buffer.PopFirst(); + return len; + } + else + { + memcpy(data, &chunk->data[chunk->read], maxsize); + memcpy(sockaddrbuf, &chunk->addr, fromLen); + chunk->read += maxsize; + return maxsize; + } +} + +int websocket_send(int socket, const unsigned char *data, size_t size, int port) +{ + libwebsocket_context *context = contexts[socket].context; + if(context == NULL) + return -1; + context_data *ctx_data = (context_data *)libwebsocket_context_user(context); + struct per_session_data *pss = ctx_data->port_map[port]; + if(pss == NULL) + return -1; + websocket_chunk *chunk = (websocket_chunk *)pss->send_buffer.Allocate(size + sizeof(websocket_chunk) + LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING); + if(chunk == NULL) + return -1; + chunk->size = size; + chunk->read = 0; + memcpy(&chunk->addr, &pss->addr, sizeof(sockaddr_in)); + memcpy(&chunk->data[LWS_SEND_BUFFER_PRE_PADDING], data, size); + libwebsocket_callback_on_writable(context, pss->wsi); + return size; +} + +int websocket_fd_set(int socket, fd_set *set) +{ + libwebsocket_context *context = contexts[socket].context; + if(context == NULL) + return -1; + context_data *ctx_data = (context_data *)libwebsocket_context_user(context); + int max = 0; + for(int i = 0; i < WS_CLIENTS; i++) + { + per_session_data *pss = ctx_data->port_map[i]; + if(pss == NULL) + continue; + int fd = libwebsocket_get_socket_fd(pss->wsi); + if(fd > max) + max = fd; + FD_SET(fd, set); + } + return max; +} +} +#endif diff --git a/src/engine/shared/websockets.h b/src/engine/shared/websockets.h new file mode 100644 index 00000000..95f6fc12 --- /dev/null +++ b/src/engine/shared/websockets.h @@ -0,0 +1,16 @@ +#ifndef WEBSOCKETS_H +#define WEBSOCKETS_H + +#if !defined(CONF_FAMILY_UNIX) + #error websockets only work on unix, sorry +#endif + +#include <netinet/in.h> + +int websocket_create(const char *addr, int port); +int websocket_destroy(int socket); +int websocket_recv(int socket, unsigned char *data, size_t maxsize, struct sockaddr_in *sockaddrbuf, size_t fromLen); +int websocket_send(int socket, const unsigned char *data, size_t size, int port); +int websocket_fd_set(int socket, fd_set *set); + +#endif |