about summary refs log tree commit diff
diff options
context:
space:
mode:
authoreeeee <eeeee@qwe123.info>2015-02-07 14:15:58 -0800
committerdef <dennis@felsin9.de>2015-04-19 16:05:39 +0200
commited763c52869ed05f326edf73c79a8ac8a6e35980 (patch)
tree2f81c70e8c74ed37e7efb17a8c5fd884ed50706e
parent424ce4987ced7deda3e355b7b6373680bf506646 (diff)
downloadzcatch-ed763c52869ed05f326edf73c79a8ac8a6e35980.tar.gz
zcatch-ed763c52869ed05f326edf73c79a8ac8a6e35980.zip
websockets support in server
Conflicts:
	bam.lua
-rw-r--r--bam.lua14
-rw-r--r--src/base/system.c73
-rw-r--r--src/base/system.h4
-rw-r--r--src/engine/shared/websockets.cpp275
-rw-r--r--src/engine/shared/websockets.h16
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