diff options
-rw-r--r-- | index.html | 10 | ||||
-rw-r--r-- | makefile | 3 | ||||
-rw-r--r-- | network.c | 99 | ||||
-rw-r--r-- | websocksy.c | 86 | ||||
-rw-r--r-- | websocksy.h | 4 | ||||
-rw-r--r-- | ws_proto.c | 70 |
6 files changed, 176 insertions, 96 deletions
@@ -1,8 +1,16 @@ <html> <head> <script type="text/javascript"> + var webSocket = null; + + function openSocket(){ + webSocket = new WebSocket("ws://localhost:8001/foo/bar"); + webSocket.onclose = openSocket; + } + function init(){ - var webSocket = new WebSocket("ws://localhost:8001/foo/bar"); + openSocket(); + setInterval(function(){webSocket.send("foo");}, 1000); } </script> </head> @@ -1,9 +1,10 @@ CFLAGS=-g -Wall -Wpedantic +LDLIBS=-lnettle all: websocksy websocksy: websocksy.c websocksy.h ws_proto.c - $(CC) $(CFLAGS) $< -o $@ + $(CC) $(CFLAGS) $(LDLIBS) $< -o $@ clean: rm websocksy diff --git a/network.c b/network.c new file mode 100644 index 0000000..9719b27 --- /dev/null +++ b/network.c @@ -0,0 +1,99 @@ +int network_socket(char* host, char* port, int socktype, int listener){ + int fd = -1, status, yes = 1, flags; + struct addrinfo hints = { + .ai_family = AF_UNSPEC, + .ai_socktype = socktype, + .ai_flags = (listener ? AI_PASSIVE : 0) + }; + struct addrinfo *info, *addr_it; + + status = getaddrinfo(host, port, &hints, &info); + if(status){ + fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(status)); + return -1; + } + + //traverse the result list + for(addr_it = info; addr_it; addr_it = addr_it->ai_next){ + fd = socket(addr_it->ai_family, addr_it->ai_socktype, addr_it->ai_protocol); + if(fd < 0){ + continue; + } + + //set required socket options + yes = 1; + if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to enable SO_REUSEADDR on socket: %s\n", strerror(errno)); + } + + yes = 0; + if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&yes, sizeof(yes)) < 0){ + fprintf(stderr, "Failed to unset IPV6_V6ONLY on socket: %s\n", strerror(errno)); + } + + //TODO loop, bcast for udp + + if(listener){ + status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); + if(status < 0){ + close(fd); + continue; + } + } + else{ + status = connect(fd, addr_it->ai_addr, addr_it->ai_addrlen); + if(status < 0){ + close(fd); + continue; + } + } + + break; + } + freeaddrinfo(info); + + if(!addr_it){ + fprintf(stderr, "Failed to create socket for %s port %s\n", host, port); + return -1; + } + + //set nonblocking + flags = fcntl(fd, F_GETFL, 0); + if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ + fprintf(stderr, "Failed to set socket nonblocking: %s\n", strerror(errno)); + close(fd); + return -1; + } + + if(!listener){ + return fd; + } + + if(socktype == SOCK_STREAM){ + status = listen(fd, SOMAXCONN); + if(status < 0){ + fprintf(stderr, "Failed to listen on socket: %s\n", strerror(errno)); + close(fd); + return -1; + } + } + + return fd; +} + +int network_send(int fd, uint8_t* data, size_t length){ + ssize_t total = 0, sent; + while(total < length){ + sent = send(fd, data + total, length - total, 0); + if(sent < 0){ + fprintf(stderr, "Failed to send: %s\n", strerror(errno)); + return 1; + } + total += sent; + } + return 0; +} + +int network_send_str(int fd, char* data){ + return network_send(fd, (uint8_t*) data, strlen(data)); +}
\ No newline at end of file diff --git a/websocksy.c b/websocksy.c index f193ae6..ca7193b 100644 --- a/websocksy.c +++ b/websocksy.c @@ -10,6 +10,7 @@ #include <fcntl.h> #include "websocksy.h" +#include "network.c" #include "ws_proto.c" /* TODO @@ -36,89 +37,6 @@ int args_parse(int argc, char** argv){ return 0; } -int network_socket(char* host, char* port, int socktype, int listener){ - int fd = -1, status, yes = 1, flags; - struct addrinfo hints = { - .ai_family = AF_UNSPEC, - .ai_socktype = socktype, - .ai_flags = (listener ? AI_PASSIVE : 0) - }; - struct addrinfo *info, *addr_it; - - status = getaddrinfo(host, port, &hints, &info); - if(status){ - fprintf(stderr, "Failed to parse address %s port %s: %s\n", host, port, gai_strerror(status)); - return -1; - } - - //traverse the result list - for(addr_it = info; addr_it; addr_it = addr_it->ai_next){ - fd = socket(addr_it->ai_family, addr_it->ai_socktype, addr_it->ai_protocol); - if(fd < 0){ - continue; - } - - //set required socket options - yes = 1; - if(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to enable SO_REUSEADDR on socket: %s\n", strerror(errno)); - } - - yes = 0; - if(setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (void*)&yes, sizeof(yes)) < 0){ - fprintf(stderr, "Failed to unset IPV6_V6ONLY on socket: %s\n", strerror(errno)); - } - - //TODO loop, bcast for udp - - if(listener){ - status = bind(fd, addr_it->ai_addr, addr_it->ai_addrlen); - if(status < 0){ - close(fd); - continue; - } - } - else{ - status = connect(fd, addr_it->ai_addr, addr_it->ai_addrlen); - if(status < 0){ - close(fd); - continue; - } - } - - break; - } - freeaddrinfo(info); - - if(!addr_it){ - fprintf(stderr, "Failed to create socket for %s port %s\n", host, port); - return -1; - } - - //set nonblocking - flags = fcntl(fd, F_GETFL, 0); - if(fcntl(fd, F_SETFL, flags | O_NONBLOCK) < 0){ - fprintf(stderr, "Failed to set socket nonblocking: %s\n", strerror(errno)); - close(fd); - return -1; - } - - if(!listener){ - return fd; - } - - if(socktype == SOCK_STREAM){ - status = listen(fd, SOMAXCONN); - if(status < 0){ - fprintf(stderr, "Failed to listen on socket: %s\n", strerror(errno)); - close(fd); - return -1; - } - } - - return fd; -} - int ws_peer_data(websocket* ws){ //TODO return -1; @@ -139,6 +57,8 @@ int main(int argc, char** argv){ exit(usage(argv[0])); } + signal(SIGINT, signal_handler); + //core loop while(!shutdown_requested){ FD_ZERO(&read_fds); diff --git a/websocksy.h b/websocksy.h index 794b4ed..275b9ab 100644 --- a/websocksy.h +++ b/websocksy.h @@ -1,12 +1,14 @@ #include <stdint.h> #include <stdlib.h> +#include <nettle/sha1.h> +#include <nettle/base64.h> #define WS_MAX_LINE 16384 typedef enum { ws_new = 0, ws_http, - ws_rfc6455 + ws_open } ws_state; struct { @@ -1,5 +1,7 @@ #include <ctype.h> +#define RFC6455_MAGIC_KEY "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + static size_t socks = 0; static websocket* sock = NULL; @@ -96,15 +98,52 @@ int ws_handle_new(websocket* ws){ int ws_upgrade_http(websocket* ws){ if(ws->websocket_version == 13 + && ws->socket_key && ws->want_upgrade){ + 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")){ + //TODO might want to disconnect here + return 1; + } - return 0; + //calculate the websocket key which for some reason is defined as + //base64(sha1(concat(trim(client-key), "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))) + //which requires not one but 2 unnecessarily complex operations + size_t encode_offset = 0; + struct sha1_ctx ws_accept_ctx; + struct base64_encode_ctx ws_accept_encode; + uint8_t ws_accept_digest[SHA1_DIGEST_SIZE]; + char ws_accept_key[BASE64_ENCODE_LENGTH(SHA1_DIGEST_SIZE) + 3] = ""; + sha1_init(&ws_accept_ctx); + sha1_update(&ws_accept_ctx, strlen(ws->socket_key), (uint8_t*) ws->socket_key); + sha1_update(&ws_accept_ctx, strlen(RFC6455_MAGIC_KEY), (uint8_t*) RFC6455_MAGIC_KEY); + sha1_digest(&ws_accept_ctx, sizeof(ws_accept_digest), (uint8_t*) &ws_accept_digest); + base64_encode_init(&ws_accept_encode); + encode_offset = base64_encode_update(&ws_accept_encode, (uint8_t*) ws_accept_key, SHA1_DIGEST_SIZE, ws_accept_digest); + encode_offset += base64_encode_final(&ws_accept_encode, (uint8_t*) ws_accept_key + encode_offset); + 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)){ + return 1; + } + + //TODO Sec-Websocket-Protocol + //TODO find/connect peer + + ws->state = ws_open; + return network_send_str(ws->fd, "\r\n") ? 1 : 0; } + //TODO RFC 4.2.2.4: An unsupported version must be answered with HTTP 426 return 1; } int ws_handle_http(websocket* ws){ char* header, *value; + ssize_t p; + if(!ws->read_buffer[0]){ return ws_upgrade_http(ws); } @@ -130,6 +169,10 @@ int ws_handle_http(websocket* ws){ ws->websocket_version = strtoul(value, NULL, 10); } else if(!strcmp(header, "Sec-WebSocket-Key")){ + //right-trim the key + for(p = strlen(value) - 1; p >= 0 && isspace(value[p]); p--){ + value[p] = 0; + } ws->socket_key = strdup(value); } else if(!strcmp(header, "Upgrade") && !strcmp(value, "websocket")){ @@ -139,8 +182,15 @@ int ws_handle_http(websocket* ws){ return 0; } +int ws_frame(websocket* ws){ + //TODO check for complete WS frame + //read/handle/forward data + fprintf(stderr, "Incoming websocket data\n"); + return 0; +} + int ws_data(websocket* ws){ - ssize_t bytes_read, u, bytes_left = sizeof(ws->read_buffer) - ws->read_buffer_offset; + 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); @@ -162,10 +212,10 @@ int ws_data(websocket* ws){ case ws_new: case ws_http: //scan for newline, handle line - for(u = 0; u < bytes_read - 1; u++){ - if(!strncmp((char*) ws->read_buffer + ws->read_buffer_offset + u, "\r\n", 2)){ + for(n = 0; n < bytes_read - 1; n++){ + if(!strncmp((char*) ws->read_buffer + ws->read_buffer_offset + n, "\r\n", 2)){ //terminate line - ws->read_buffer[ws->read_buffer_offset + u] = 0; + ws->read_buffer[ws->read_buffer_offset + n] = 0; if(ws->state == ws_new){ rv |= ws_handle_new(ws); @@ -176,17 +226,17 @@ int ws_data(websocket* ws){ //TODO handle rv //remove from buffer - bytes_read -= (u + 2); - memmove(ws->read_buffer, ws->read_buffer + ws->read_buffer_offset + u + 2, bytes_read); + bytes_read -= (n + 2); + memmove(ws->read_buffer, ws->read_buffer + ws->read_buffer_offset + n + 2, bytes_read); ws->read_buffer_offset = 0; //restart from the beginning - u = -1; + n = -1; } } break; - //case ws_rfc6455: - //TODO parse websocket encap, forward to peer + case ws_open: + rv |= ws_frame(ws); } //update read buffer offset |