aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcbdev <cb@cbcdn.com>2019-05-18 20:01:33 +0200
committercbdev <cb@cbcdn.com>2019-05-18 20:01:33 +0200
commit75d1e4955b220495f6d2655b18989db567faa5ce (patch)
treeb56c24fd320b6aca00de6ba565e852a9693ce152
parentf985ad1a4c11a8676ccd14d87fd11cd2c6dd75a6 (diff)
downloadwebsocksy-75d1e4955b220495f6d2655b18989db567faa5ce.tar.gz
websocksy-75d1e4955b220495f6d2655b18989db567faa5ce.tar.bz2
websocksy-75d1e4955b220495f6d2655b18989db567faa5ce.zip
Document structures, introduce basic API
-rw-r--r--websocksy.c71
-rw-r--r--websocksy.h111
-rw-r--r--ws_proto.c55
3 files changed, 181 insertions, 56 deletions
diff --git a/websocksy.c b/websocksy.c
index 0e4028f..55e8d35 100644
--- a/websocksy.c
+++ b/websocksy.c
@@ -10,6 +10,24 @@
#include <fcntl.h>
#include <ctype.h>
+/* TODO
+ * - TLS
+ * - config parsing
+ * - backend config
+ * - framing config / per peer?
+ * - per-connection framing state
+ */
+
+/*
+ * Main loop condition, to be set from signal handler
+ */
+static volatile sig_atomic_t shutdown_requested = 0;
+
+#include "websocksy.h"
+
+/*
+ * Lowercase input string in-place
+ */
char* xstr_lower(char* in){
size_t n;
for(n = 0; n < strlen(in); n++){
@@ -18,24 +36,33 @@ char* xstr_lower(char* in){
return in;
}
-#include "websocksy.h"
#include "network.c"
#include "ws_proto.c"
-/* TODO
- * - TLS
- * - config parsing
- * - Prevent http overrun
+/*
+ * WebSocket interface & peer discovery configuration
+ */
+static struct {
+ char* host;
+ char* port;
+ ws_backend backend;
+} config = {
+ .host = "::",
+ .port = "8001"
+};
+
+/*
+ * Signal handler, attached to SIGINT
*/
-
-static volatile sig_atomic_t shutdown_requested = 0;
-
void signal_handler(int signum){
shutdown_requested = 1;
}
+/*
+ * Usage info
+ */
int usage(char* fn){
- fprintf(stderr, "\nwebsocksy - Proxy between websockets and 'real' sockets\n");
+ fprintf(stderr, "\nwebsocksy v%s - Proxy between websockets and 'real' sockets\n", WEBSOCKSY_VERSION);
fprintf(stderr, "Usage:\n");
fprintf(stderr, "\t%s [-p <port>] [-l <listen address>] [-b <targeting backend>]\n", fn);
return EXIT_FAILURE;
@@ -56,6 +83,7 @@ int main(int argc, char** argv){
size_t n;
int listen_fd = -1, status, max_fd;
+ //parse command line arguments
if(args_parse(argc - 1, argv + 1)){
exit(usage(argv[0]));
}
@@ -66,6 +94,7 @@ int main(int argc, char** argv){
exit(usage(argv[0]));
}
+ //attach signal handler to catch Ctrl-C
signal(SIGINT, signal_handler);
//core loop
@@ -77,16 +106,16 @@ int main(int argc, char** argv){
//push all fds to the select set
for(n = 0; n < socks; n++){
- if(sock[n].fd >= 0){
- FD_SET(sock[n].fd, &read_fds);
- if(max_fd < sock[n].fd){
- max_fd = sock[n].fd;
+ if(sock[n].ws_fd >= 0){
+ FD_SET(sock[n].ws_fd, &read_fds);
+ if(max_fd < sock[n].ws_fd){
+ max_fd = sock[n].ws_fd;
}
- if(sock[n].peer >= 0){
- FD_SET(sock[n].peer, &read_fds);
- if(max_fd < sock[n].peer){
- max_fd = sock[n].peer;
+ if(sock[n].peer_fd >= 0){
+ FD_SET(sock[n].peer_fd, &read_fds);
+ if(max_fd < sock[n].peer_fd){
+ max_fd = sock[n].peer_fd;
}
}
}
@@ -109,16 +138,16 @@ int main(int argc, char** argv){
}
}
- //websocket & peer data
+ //websocket or peer data ready
for(n = 0; n < socks; n++){
- if(sock[n].fd >= 0){
- if(FD_ISSET(sock[n].fd, &read_fds)){
+ if(sock[n].ws_fd >= 0){
+ if(FD_ISSET(sock[n].ws_fd, &read_fds)){
if(ws_data(sock + n)){
break;
}
}
- if(sock[n].peer >= 0 && FD_ISSET(sock[n].peer, &read_fds)){
+ if(sock[n].peer_fd >= 0 && FD_ISSET(sock[n].peer_fd, &read_fds)){
if(ws_peer_data(sock + n)){
break;
}
diff --git a/websocksy.h b/websocksy.h
index 2073287..5a4f24c 100644
--- a/websocksy.h
+++ b/websocksy.h
@@ -3,16 +3,29 @@
#include <nettle/sha1.h>
#include <nettle/base64.h>
+/* Version defines */
+#define WEBSOCKSY_API_VERSION 1
+#define WEBSOCKSY_VERSION "0.1"
+
+/* HTTP/WS read buffer size & limit */
#define WS_MAX_LINE 16384
+/* Maximum number of HTTP headers to accept */
+#define WS_HEADER_LIMIT 10
+/*
+ * State machine for WebSocket connections
+ */
typedef enum {
- ws_new = 0,
- ws_http,
- ws_open,
- ws_closed
+ ws_new = 0, /* Initial state */
+ ws_http, /* HTTP Headers */
+ ws_open, /* Upgrade performed, forwarding */
+ ws_closed /* Close frame sent */
} ws_state;
-//RFC Section 5.2
+/*
+ * WebSocket frame opcodes
+ * RFC Section 5.2
+ */
typedef enum {
ws_frame_continuation = 0,
ws_frame_text = 1,
@@ -22,7 +35,10 @@ typedef enum {
ws_frame_pong = 10
} ws_operation;
-//RFC Section 7.4.1
+/*
+ * WebSocket close reasons / response codes
+ * RFC Section 7.4.1
+ */
typedef enum {
ws_close_http = 100,
ws_close_normal = 1000,
@@ -35,30 +51,91 @@ typedef enum {
ws_close_unexpected = 1011
} ws_close_reason;
-struct {
+/*
+ * HTTP header split into tag and value
+ */
+typedef struct /*_ws_http_header*/ {
+ char* tag;
+ char* value;
+} ws_http_header;
+
+/*
+ * Peer stream framing function
+ *
+ * Since the WebSocket protocol transfers its payload using discrete frames, unlike the peer's
+ * underlying TCP/Unix sockets (UDP is a different matter and may be framed directly), we need
+ * a method to indicate whether a complete frame to be forwarded has been received from the peer
+ * and with what WebSocket frame type to forward it (binary/text). Since this is largely
+ * protocol-dependent, this functionality needs to be user-extendable.
+ *
+ * We do however provide some default framing functions:
+ * * binary: Always forward all reads as binary frames
+ * * auto: Based on the content, forward every read result as binary/text
+ * * separator: Separate binary frames on a sequence of bytes
+ * * newline: Forward text frames separated by newlines (\r\n)
+ *
+ * The separator function is called once for every succesful read from the peer socket and called
+ * again when it indicates a frame boundary but there is still data in the buffer.
+ */
+typedef int64_t (*ws_framing)(void);
+
+/*
+ * Peer connection model
+ */
+typedef struct /*_ws_peer_info*/ {
+ /* Peer protocol data */
+ int transport;
char* host;
char* port;
- struct {
- char* name;
- } backend;
-} config = {
- .host = "::",
- .port = "8001",
- .backend.name = "internal"
-};
+ /* Framing function for this peer */
+ ws_framing framing;
+
+ /* WebSocket subprotocol indication index*/
+ size_t protocol;
+} ws_peer_info;
+
+/*
+ * Core connection model
+ */
typedef struct /*_web_socket*/ {
- int fd;
- int peer;
+ /* WebSocket state & data */
+ int ws_fd;
uint8_t read_buffer[WS_MAX_LINE];
size_t read_buffer_offset;
ws_state state;
+ /* HTTP request headers */
+ size_t headers;
+ ws_http_header header[WS_HEADER_LIMIT];
+
+ /* WebSocket parameters */
char* request_path;
unsigned websocket_version;
char* socket_key;
unsigned want_upgrade;
+ /* WebSocket indicated subprotocols*/
size_t protocols;
char** protocol;
+
+ /* Peer data */
+ ws_peer_info peer;
+ int peer_fd;
} websocket;
+
+/*
+ * Peer discovery backend
+ *
+ * Used to dynamically select the peer based on parameters supplied by the WebSocket connection.
+ * The backend maps WebSocket characteristics (such as the endpoint used, the supported protocols and
+ * HTTP client headers) to a TCP/UDP/Unix socket peer endpoint using some form of user-configurable
+ * provider (such as databases, files, crystal balls or sheer guesses).
+ *
+ * The return value is a structure containing the destination address and transport to be connected to
+ * the Web Socket, as well as the indicated subprotocol to use (or none, if set to the provided maximum
+ * number of protocols).
+ * The fields within the structure should be allocated with `calloc` and will be free'd by websocky
+ * after use.
+ */
+typedef ws_peer_info (*ws_backend)(char* endpoint, size_t protocols, char** protocol, size_t headers, ws_http_header* header, websocket* ws);
diff --git a/ws_proto.c b/ws_proto.c
index e5d4625..bfcce11 100644
--- a/ws_proto.c
+++ b/ws_proto.c
@@ -17,16 +17,24 @@ int ws_close(websocket* ws, ws_close_reason code, char* reason){
}
ws->state = ws_closed;
- if(ws->fd >= 0){
- close(ws->fd);
- ws->fd = -1;
+ if(ws->ws_fd >= 0){
+ close(ws->ws_fd);
+ ws->ws_fd = -1;
}
- if(ws->peer >= 0){
- close(ws->peer);
- ws->peer = -1;
+ if(ws->peer_fd >= 0){
+ close(ws->peer_fd);
+ ws->peer_fd = -1;
}
+ for(p = 0; p < ws->headers; p++){
+ free(ws->header[p].tag);
+ ws->header[p].tag = NULL;
+ free(ws->header[p].value);
+ ws->header[p].value = NULL;
+ }
+ ws->headers = 0;
+
for(p = 0; p < ws->protocols; p++){
free(ws->protocol[p]);
}
@@ -51,13 +59,13 @@ int ws_accept(int listen_fd){
size_t n = 0;
websocket ws = {
- .fd = accept(listen_fd, NULL, NULL),
- .peer = -1
+ .ws_fd = accept(listen_fd, NULL, NULL),
+ .peer_fd = -1
};
//try to find a slot to occupy
for(n = 0; n < socks; n++){
- if(sock[n].fd == -1){
+ if(sock[n].ws_fd == -1){
break;
}
}
@@ -66,7 +74,7 @@ int ws_accept(int listen_fd){
if(n == socks){
sock = realloc(sock, (socks + 1) * sizeof(websocket));
if(!sock){
- close(ws.fd);
+ close(ws.ws_fd);
fprintf(stderr, "Failed to allocate memory\n");
return 1;
}
@@ -109,9 +117,9 @@ int ws_upgrade_http(websocket* ws){
if(ws->websocket_version == 13
&& ws->socket_key
&& ws->want_upgrade == 3){
- if(network_send_str(ws->fd, "HTTP/1.1 101 Upgrading\r\n")
- || network_send_str(ws->fd, "Upgrade: websocket\r\n")
- || network_send_str(ws->fd, "Connection: Upgrade\r\n")){
+ if(network_send_str(ws->ws_fd, "HTTP/1.1 101 Upgrading\r\n")
+ || network_send_str(ws->ws_fd, "Upgrade: websocket\r\n")
+ || network_send_str(ws->ws_fd, "Connection: Upgrade\r\n")){
ws_close(ws, ws_close_http, NULL);
return 0;
}
@@ -134,8 +142,8 @@ int ws_upgrade_http(websocket* ws){
memcpy(ws_accept_key + encode_offset, "\r\n\0", 3);
//send websocket accept key
- if(network_send_str(ws->fd, "Sec-WebSocket-Accept: ")
- || network_send_str(ws->fd, ws_accept_key)){
+ if(network_send_str(ws->ws_fd, "Sec-WebSocket-Accept: ")
+ || network_send_str(ws->ws_fd, ws_accept_key)){
ws_close(ws, ws_close_http, NULL);
return 0;
}
@@ -144,7 +152,7 @@ int ws_upgrade_http(websocket* ws){
//TODO find/connect peer
ws->state = ws_open;
- if(network_send_str(ws->fd, "\r\n")){
+ if(network_send_str(ws->ws_fd, "\r\n")){
ws_close(ws, ws_close_http, NULL);
return 0;
}
@@ -196,7 +204,18 @@ int ws_handle_http(websocket* ws){
else if(!strcmp(header, "Connection") && strstr(xstr_lower(value), "upgrade")){
ws->want_upgrade |= 2;
}
- //TODO parse websocket protocol offers
+ else if(!strcmp(header, "Sec-WebSocket-Protocol")){
+ //TODO parse websocket protocol offers
+ }
+ else if(ws->headers < WS_HEADER_LIMIT){
+ ws->header[ws->headers].tag = strdup(header);
+ ws->header[ws->headers].value = strdup(value);
+ ws->headers++;
+ }
+ else{
+ //limit the number of stored headers to prevent abuse
+ ws_close(ws, ws_close_http, "500 Header limit");
+ }
return 0;
}
@@ -316,7 +335,7 @@ int ws_data(websocket* ws){
ssize_t bytes_read, n, bytes_left = sizeof(ws->read_buffer) - ws->read_buffer_offset;
int rv = 0;
- bytes_read = recv(ws->fd, ws->read_buffer + ws->read_buffer_offset, bytes_left - 1, 0);
+ bytes_read = recv(ws->ws_fd, ws->read_buffer + ws->read_buffer_offset, bytes_left - 1, 0);
if(bytes_read < 0){
fprintf(stderr, "Failed to receive from websocket: %s\n", strerror(errno));
ws_close(ws, ws_close_unexpected, NULL);