Implement WebSocket reverse proxy
This commit is contained in:
@ -108,6 +108,7 @@ typedef enum {
|
||||
typedef struct {
|
||||
unsigned short status;
|
||||
http_error_origin origin;
|
||||
const char* ws_key;
|
||||
} http_status_ctx;
|
||||
|
||||
extern const http_status http_statuses[];
|
||||
|
@ -5,10 +5,11 @@
|
||||
* Lorenz Stechauner, 2021-01-07
|
||||
*/
|
||||
|
||||
#include "../necronda.h"
|
||||
#include "../server.h"
|
||||
#include "rev_proxy.h"
|
||||
#include "utils.h"
|
||||
#include "compress.h"
|
||||
#include "../server.h"
|
||||
|
||||
#include <openssl/ssl.h>
|
||||
#include <string.h>
|
||||
@ -33,8 +34,6 @@ int rev_proxy_preload() {
|
||||
int rev_proxy_request_header(http_req *req, int enc) {
|
||||
char buf1[256], buf2[256];
|
||||
int p_len;
|
||||
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Connection", "keep-alive");
|
||||
|
||||
const char *via = http_get_header_field(&req->hdr, "Via");
|
||||
sprintf(buf1, "HTTP/%s %s", req->version, SERVER_NAME);
|
||||
@ -184,12 +183,12 @@ int rev_proxy_response_header(http_req *req, http_res *res, host_config *conf) {
|
||||
|
||||
int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_config *conf, sock *client, http_status *custom_status, char *err_msg) {
|
||||
char buffer[CHUNK_SIZE];
|
||||
const char *connection, *upgrade, *ws_version;
|
||||
long ret;
|
||||
int tries = 0, retry = 0;
|
||||
|
||||
if (rev_proxy.socket != 0 && strcmp(rev_proxy_host, conf->name) == 0 && sock_check(&rev_proxy) == 0) {
|
||||
if (rev_proxy.socket != 0 && strcmp(rev_proxy_host, conf->name) == 0 && sock_check(&rev_proxy) == 0)
|
||||
goto rev_proxy;
|
||||
}
|
||||
|
||||
retry:
|
||||
if (rev_proxy.socket != 0) {
|
||||
@ -290,6 +289,22 @@ int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_conf
|
||||
print(BLUE_STR "Established new connection with " BLD_STR "[%s]:%i" CLR_STR, buffer, conf->rev_proxy.port);
|
||||
|
||||
rev_proxy:
|
||||
connection = http_get_header_field(&req->hdr, "Connection");
|
||||
if (connection != NULL && (strstr(connection, "upgrade") != NULL || strstr(connection, "Upgrade") != NULL)) {
|
||||
upgrade = http_get_header_field(&req->hdr, "Upgrade");
|
||||
ws_version = http_get_header_field(&req->hdr, "Sec-WebSocket-Version");
|
||||
if (upgrade != NULL && ws_version != NULL && strcmp(upgrade, "websocket") == 0 && strcmp(ws_version, "13") == 0) {
|
||||
ctx->ws_key = http_get_header_field(&req->hdr, "Sec-WebSocket-Key");
|
||||
} else {
|
||||
res->status = http_get_status(501);
|
||||
ctx->origin = INTERNAL;
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
http_remove_header_field(&req->hdr, "Connection", HTTP_REMOVE_ALL);
|
||||
http_add_header_field(&req->hdr, "Connection", "keep-alive");
|
||||
}
|
||||
|
||||
ret = rev_proxy_request_header(req, (int) client->enc);
|
||||
if (ret != 0) {
|
||||
res->status = http_get_status(500);
|
||||
@ -454,7 +469,6 @@ int rev_proxy_init(http_req *req, http_res *res, http_status_ctx *ctx, host_conf
|
||||
}
|
||||
|
||||
int rev_proxy_send(sock *client, unsigned long len_to_send, int flags) {
|
||||
// TODO handle websockets
|
||||
char buffer[CHUNK_SIZE], comp_out[CHUNK_SIZE], buf[256], *ptr;
|
||||
long ret = 0, len, snd_len;
|
||||
int finish_comp = 0;
|
||||
|
@ -130,11 +130,12 @@ int sock_poll(sock *sockets[], sock *ready[], short events, int n_sock, int time
|
||||
int ret = poll(fds, n_sock, timeout_ms);
|
||||
if (ret < 0 || ready == NULL) return ret;
|
||||
|
||||
for (int i = 0, j = 0; i < ret; j++) {
|
||||
int j = 0;
|
||||
for (int i = 0; i < n_sock; i++) {
|
||||
if (fds[i].revents & events)
|
||||
ready[i++] = sockets[j];
|
||||
ready[j++] = sockets[i];
|
||||
}
|
||||
return ret;
|
||||
return j;
|
||||
}
|
||||
|
||||
int sock_poll_read(sock *sockets[], sock *readable[], int n_sock, int timeout_ms) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
#define NECRONDA_SERVER_SOCK_H
|
||||
|
||||
#include <openssl/crypto.h>
|
||||
#include <sys/socket.h>
|
||||
|
||||
typedef struct {
|
||||
unsigned int enc:1;
|
||||
|
@ -192,6 +192,7 @@ int base64_encode(void *data, unsigned long data_len, char *output, unsigned lon
|
||||
|
||||
for (int i = 0; i < base64_mod_table[data_len % 3]; i++)
|
||||
output[out_len - 1 - i] = '=';
|
||||
output[out_len] = 0;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
202
src/lib/websocket.c
Normal file
202
src/lib/websocket.c
Normal file
@ -0,0 +1,202 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* WebSocket reverse proxy
|
||||
* src/lib/websocket.c
|
||||
* Lorenz Stechauner, 2022-08-16
|
||||
*/
|
||||
|
||||
#include "../necronda.h"
|
||||
#include "websocket.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <openssl/sha.h>
|
||||
#include <errno.h>
|
||||
#include <signal.h>
|
||||
|
||||
|
||||
int terminate = 0;
|
||||
|
||||
void ws_terminate() {
|
||||
terminate = 1;
|
||||
}
|
||||
|
||||
int ws_calc_accept_key(const char *key, char *accept_key) {
|
||||
if (key == NULL || accept_key == NULL)
|
||||
return -1;
|
||||
|
||||
char input[256] = "";
|
||||
unsigned char output[SHA_DIGEST_LENGTH];
|
||||
strcat(input, key);
|
||||
strcat(input, ws_key_uuid);
|
||||
|
||||
if (SHA1((unsigned char *) input, strlen(input), output) == NULL) {
|
||||
return -2;
|
||||
}
|
||||
|
||||
base64_encode(output, sizeof(output), accept_key, NULL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ws_recv_frame_header(sock *s, ws_frame *frame) {
|
||||
unsigned char buf[12];
|
||||
|
||||
long ret = sock_recv(s, buf, 2, 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to receive from socket: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (ret != 2) {
|
||||
print(ERR_STR "Unable to receive 2 bytes from socket" CLR_STR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
unsigned short bits = (buf[0] << 8) | buf[1];
|
||||
frame->f_fin = (bits >> 15) & 1;
|
||||
frame->f_rsv1 = (bits >> 14) & 1;
|
||||
frame->f_rsv2 = (bits >> 13) & 1;
|
||||
frame->f_rsv3 = (bits >> 12) & 1;
|
||||
frame->opcode = (bits >> 8) & 0xF;
|
||||
frame->f_mask = (bits >> 7) & 1;
|
||||
unsigned short len = (bits & 0x7F);
|
||||
|
||||
int remaining = frame->f_mask ? 4 : 0;
|
||||
if (len == 126) {
|
||||
remaining += 2;
|
||||
} else if (len == 127) {
|
||||
remaining += 8;
|
||||
}
|
||||
|
||||
ret = sock_recv(s, buf, remaining, 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to receive from socket: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (ret != remaining) {
|
||||
print(ERR_STR "Unable to receive correct number of bytes from socket" CLR_STR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
if (len == 126) {
|
||||
frame->len = (((unsigned long) buf[0]) << 8) | ((unsigned long) buf[1]);
|
||||
} else if (len == 127) {
|
||||
frame->len =
|
||||
(((unsigned long) buf[0]) << 56) |
|
||||
(((unsigned long) buf[1]) << 48) |
|
||||
(((unsigned long) buf[2]) << 40) |
|
||||
(((unsigned long) buf[3]) << 32) |
|
||||
(((unsigned long) buf[4]) << 24) |
|
||||
(((unsigned long) buf[5]) << 16) |
|
||||
(((unsigned long) buf[6]) << 8) |
|
||||
(((unsigned long) buf[7]) << 0);
|
||||
} else {
|
||||
frame->len = len;
|
||||
}
|
||||
|
||||
if (frame->f_mask) memcpy(frame->masking_key, buf + (remaining - 4), 4);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ws_send_frame_header(sock *s, ws_frame *frame) {
|
||||
unsigned char buf[14], *ptr = buf;
|
||||
|
||||
unsigned short len;
|
||||
if (frame->len > 0x7FFF) {
|
||||
len = 127;
|
||||
} else if (frame->len > 125) {
|
||||
len = 126;
|
||||
} else {
|
||||
len = frame->len;
|
||||
}
|
||||
|
||||
unsigned short bits =
|
||||
(frame->f_fin << 15) |
|
||||
(frame->f_rsv1 << 14) |
|
||||
(frame->f_rsv2 << 13) |
|
||||
(frame->f_rsv3 << 12) |
|
||||
(frame->opcode << 8) |
|
||||
(frame->f_mask << 7) |
|
||||
len;
|
||||
|
||||
ptr++[0] = bits >> 8;
|
||||
ptr++[0] = bits & 0xFF;
|
||||
|
||||
if (len >= 126) {
|
||||
for (int i = (len == 126 ? 2 : 8) - 1; i >= 0; i--)
|
||||
ptr++[0] = (unsigned char) ((frame->len >> (i * 8)) & 0xFF);
|
||||
}
|
||||
|
||||
if (frame->f_mask) {
|
||||
memcpy(ptr, frame->masking_key, 4);
|
||||
ptr += 4;
|
||||
}
|
||||
|
||||
long ret = sock_send(s, buf, ptr - buf, frame->len != 0 ? MSG_MORE : 0);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to send to socket: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (ret != ptr - buf) {
|
||||
print(ERR_STR "Unable to send to socket" CLR_STR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ws_handle_connection(sock *s1, sock *s2) {
|
||||
sock *poll_socks[2] = {s1, s2};
|
||||
sock *readable[2];
|
||||
int n_sock = 2;
|
||||
ws_frame frame;
|
||||
char buf[CHUNK_SIZE];
|
||||
int poll, closes = 0;
|
||||
long ret;
|
||||
|
||||
signal(SIGINT, ws_terminate);
|
||||
signal(SIGTERM, ws_terminate);
|
||||
|
||||
while (!terminate && closes != 3) {
|
||||
poll = sock_poll_read(poll_socks, readable, n_sock, WS_TIMEOUT * 1000);
|
||||
if (terminate) {
|
||||
break;
|
||||
} else if (poll < 0) {
|
||||
print(ERR_STR "Unable to poll sockets: %s" CLR_STR, strerror(errno));
|
||||
return -1;
|
||||
} else if (poll == 0) {
|
||||
print(ERR_STR "Connection timed out" CLR_STR);
|
||||
return -2;
|
||||
}
|
||||
|
||||
for (int i = 0; i < poll; i++) {
|
||||
sock *s = readable[i];
|
||||
sock *o = (s == s1) ? s2 : s1;
|
||||
if (ws_recv_frame_header(s, &frame) != 0) return -3;
|
||||
|
||||
if (frame.opcode == 0x8) {
|
||||
n_sock--;
|
||||
if (s == s1) {
|
||||
poll_socks[0] = s2;
|
||||
closes |= 1;
|
||||
} else {
|
||||
closes |= 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (ws_send_frame_header(o, &frame) != 0) return -3;
|
||||
|
||||
if (frame.len > 0) {
|
||||
ret = sock_splice(o, s, buf, sizeof(buf), frame.len);
|
||||
if (ret < 0) {
|
||||
print(ERR_STR "Unable to forward data in WebSocket: %s" CLR_STR, strerror(errno));
|
||||
return -3;
|
||||
} else if (ret != frame.len) {
|
||||
print(ERR_STR "Unable to forward correct number of bytes in WebSocket" CLR_STR);
|
||||
return -3;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
36
src/lib/websocket.h
Normal file
36
src/lib/websocket.h
Normal file
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Necronda Web Server
|
||||
* WebSocket reverse proxy (header file)
|
||||
* src/lib/websocket.h
|
||||
* Lorenz Stechauner, 2022-08-16
|
||||
*/
|
||||
|
||||
#ifndef NECRONDA_SERVER_WEBSOCKET_H
|
||||
#define NECRONDA_SERVER_WEBSOCKET_H
|
||||
|
||||
#include "sock.h"
|
||||
|
||||
#define WS_TIMEOUT 3600
|
||||
|
||||
const char *ws_key_uuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
|
||||
|
||||
typedef struct {
|
||||
unsigned char f_fin:1;
|
||||
unsigned char f_rsv1:1;
|
||||
unsigned char f_rsv2:1;
|
||||
unsigned char f_rsv3:1;
|
||||
unsigned char opcode:4;
|
||||
unsigned char f_mask:1;
|
||||
unsigned long len;
|
||||
char masking_key[4];
|
||||
} ws_frame;
|
||||
|
||||
int ws_calc_accept_key(const char *key, char *accept_key);
|
||||
|
||||
int ws_recv_frame_header(sock *s, ws_frame *frame);
|
||||
|
||||
int ws_send_frame_header(sock *s, ws_frame *frame);
|
||||
|
||||
int ws_handle_connection(sock *s1, sock *s2);
|
||||
|
||||
#endif // NECRONDA_SERVER_WEBSOCKET_H
|
Reference in New Issue
Block a user