diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | makefile | 8 | ||||
-rw-r--r-- | websocksy.c | 272 | ||||
-rw-r--r-- | websocksy.h | 25 |
4 files changed, 307 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ac4e99 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.swp +websocksy diff --git a/makefile b/makefile new file mode 100644 index 0000000..a7a0b45 --- /dev/null +++ b/makefile @@ -0,0 +1,8 @@ +CFLAGS=-g -Wall -Wpedantic + +all: websocksy + +websocksy: websocksy.c websocksy.h + +clean: + rm websocksy diff --git a/websocksy.c b/websocksy.c new file mode 100644 index 0000000..dba8124 --- /dev/null +++ b/websocksy.c @@ -0,0 +1,272 @@ +#include <stdio.h> +#include <stdlib.h> +#include <signal.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <fcntl.h> + +#include "websocksy.h" + +/* TODO + * - TLS + * - config parsing + */ + +static size_t socks = 0; +static websocket* sock = NULL; + +static volatile sig_atomic_t shutdown_requested = 0; + +void signal_handler(int signum){ + shutdown_requested = 1; +} + +int usage(char* fn){ + fprintf(stderr, "\nwebsocksy - Proxy between websockets and 'real' sockets\n"); + fprintf(stderr, "Usage:\n"); + fprintf(stderr, "\t%s [-p <port>] [-l <listen address>] [-b <targeting backend>]\n", fn); + return EXIT_FAILURE; +} + +int args_parse(int argc, char** argv){ + //TODO + 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_accept(int listen_fd){ + size_t n = 0; + struct sockaddr_storage sa_storage; + socklen_t sa_length; + + websocket ws = { + .fd = accept(listen_fd, (struct sockaddr*)&sa_storage, &sa_length) + }; + + //try to find a slot to occupy + for(n = 0; n < socks; n++){ + if(sock[n].fd == -1){ + break; + } + } + + //none found, need to extend + if(n == socks){ + sock = realloc(sock, (socks + 1) * sizeof(websocket)); + if(!sock){ + close(ws.fd); + fprintf(stderr, "Failed to allocate memory\n"); + shutdown_requested = 1; + return 1; + } + socks++; + } + + sock[n] = ws; + + return 0; +} + +int ws_data(websocket* ws){ + //TODO + return -1; +} + +int ws_peer_data(websocket* ws, size_t proto){ + //TODO + return -1; +} + +int ws_close(websocket* ws){ + size_t p; + + if(ws->fd >= 0){ + close(ws->fd); + ws->fd = -1; + } + + for(p = 0; p < ws->protocols; p++){ + if(ws->protocol[p].fd >= 0){ + close(ws->protocol[p].fd); + ws->protocol[p].fd = -1; + free(ws->protocol[p].name); + } + } + + return 0; +} + +int main(int argc, char** argv){ + fd_set read_fds; + size_t n, p; + int listen_fd = -1, status, max_fd; + + if(args_parse(argc - 1, argv + 1)){ + exit(usage(argv[0])); + } + + //open listening socket + listen_fd = network_socket(config.host, config.port, SOCK_STREAM, 1); + if(listen_fd < 0){ + exit(usage(argv[0])); + } + + //core loop + while(!shutdown_requested){ + FD_ZERO(&read_fds); + + FD_SET(listen_fd, &read_fds); + max_fd = listen_fd; + + //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; + } + + for(p = 0; p < sock[n].protocols; p++){ + if(sock[n].protocol[p].fd >= 0){ + FD_SET(sock[n].protocol[p].fd, &read_fds); + if(max_fd < sock[n].protocol[p].fd){ + max_fd = sock[n].protocol[p].fd; + } + } + } + } + } + + //block until something happens + status = select(max_fd + 1, &read_fds, NULL, NULL, NULL); + if(status < 0){ + fprintf(stderr, "Failed to select: %s\n", strerror(errno)); + break; + } + else if(status == 0){ + //timeout in select - ignore for now + } + else{ + //new websocket client + if(FD_ISSET(listen_fd, &read_fds)){ + if(ws_accept(listen_fd)){ + break; + } + } + + //websocket & peer data + for(n = 0; n < socks; n++){ + if(sock[n].fd >= 0){ + if(FD_ISSET(sock[n].fd, &read_fds)){ + if(ws_data(sock + n)){ + break; + } + } + + for(p = 0; p < sock[n].protocols; p++){ + if(sock[n].protocol[p].fd >= 0 && FD_ISSET(sock[n].protocol[p].fd, &read_fds)){ + if(ws_peer_data(sock + n, p)){ + break; + } + } + } + } + } + } + } + + //cleanup + for(n = 0; n < socks; n++){ + ws_close(sock + n); + } + free(sock); + socks = 0; + close(listen_fd); + return 0; +} diff --git a/websocksy.h b/websocksy.h new file mode 100644 index 0000000..36a713d --- /dev/null +++ b/websocksy.h @@ -0,0 +1,25 @@ +#include <stdint.h> +#include <stdlib.h> + +struct { + char* host; + char* port; + struct { + char* name; + } backend; +} config = { + .host = "::", + .port = "8001", + .backend.name = "internal" +}; + +typedef struct { + char* name; + int fd; +} ws_proto; + +typedef struct /*_web_socket*/ { + int fd; + size_t protocols; + ws_proto* protocol; +} websocket; |