You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
591 lines
15 KiB
591 lines
15 KiB
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#if !defined (LWS_PLUGIN_STATIC)
|
|
#define LWS_DLL
|
|
#define LWS_INTERNAL
|
|
#include <libwebsockets.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <fcntl.h>
|
|
|
|
#define RING_DEPTH 8
|
|
|
|
struct packet {
|
|
void *payload;
|
|
uint32_t len;
|
|
uint32_t ticket;
|
|
};
|
|
|
|
enum {
|
|
ACC,
|
|
ONW
|
|
};
|
|
|
|
/*
|
|
* Because both sides of the connection want to share this, we allocate it
|
|
* during accepted adoption and both sides have a pss that is just a wrapper
|
|
* pointing to this.
|
|
*
|
|
* The last one of the accepted side and the onward side to close frees it.
|
|
* This removes any chance of one side or the other having an invalidated
|
|
* pointer to the pss.
|
|
*/
|
|
|
|
struct conn {
|
|
struct lws *wsi[2];
|
|
|
|
/* rings containing unsent rx from accepted and onward sides */
|
|
struct lws_ring *r[2];
|
|
uint32_t t[2]; /* ring tail */
|
|
|
|
uint32_t ticket_next;
|
|
uint32_t ticket_retired;
|
|
|
|
char rx_enabled[2];
|
|
char closed[2];
|
|
char established[2];
|
|
};
|
|
|
|
struct raw_pss {
|
|
struct conn *conn;
|
|
};
|
|
|
|
/* one of these is created for each vhost our protocol is used with */
|
|
|
|
struct raw_vhd {
|
|
char addr[128];
|
|
uint16_t port;
|
|
char ipv6;
|
|
};
|
|
|
|
static void
|
|
__destroy_packet(void *_pkt)
|
|
{
|
|
struct packet *pkt = _pkt;
|
|
|
|
free(pkt->payload);
|
|
pkt->payload = NULL;
|
|
pkt->len = 0;
|
|
}
|
|
|
|
static void
|
|
destroy_conn(struct raw_vhd *vhd, struct raw_pss *pss)
|
|
{
|
|
struct conn *conn = pss->conn;
|
|
|
|
if (conn->r[ACC])
|
|
lws_ring_destroy(conn->r[ACC]);
|
|
if (conn->r[ONW])
|
|
lws_ring_destroy(conn->r[ONW]);
|
|
|
|
pss->conn = NULL;
|
|
|
|
free(conn);
|
|
}
|
|
|
|
static int
|
|
connect_client(struct raw_vhd *vhd, struct raw_pss *pss)
|
|
{
|
|
struct lws_client_connect_info i;
|
|
char host[128];
|
|
struct lws *cwsi;
|
|
|
|
lws_snprintf(host, sizeof(host), "%s:%u", vhd->addr, vhd->port);
|
|
|
|
memset(&i, 0, sizeof(i));
|
|
|
|
i.method = "RAW";
|
|
i.context = lws_get_context(pss->conn->wsi[ACC]);
|
|
i.port = vhd->port;
|
|
i.address = vhd->addr;
|
|
i.host = host;
|
|
i.origin = host;
|
|
i.ssl_connection = 0;
|
|
i.vhost = lws_get_vhost(pss->conn->wsi[ACC]);
|
|
i.local_protocol_name = "raw-proxy";
|
|
i.protocol = "raw-proxy";
|
|
i.path = "/";
|
|
/*
|
|
* The "onward" client wsi has its own pss but shares the "conn"
|
|
* created when the inbound connection was accepted. We need to stash
|
|
* the address of the shared conn and apply it to the client psss
|
|
* when the client connection completes.
|
|
*/
|
|
i.opaque_user_data = pss->conn;
|
|
i.pwsi = &pss->conn->wsi[ONW];
|
|
|
|
lwsl_info("%s: onward: %s:%d%s\n", __func__, i.address, i.port, i.path);
|
|
|
|
cwsi = lws_client_connect_via_info(&i);
|
|
if (!cwsi)
|
|
lwsl_err("%s: client connect failed early\n", __func__);
|
|
|
|
return !cwsi;
|
|
}
|
|
|
|
static int
|
|
flow_control(struct conn *conn, int side, int enable)
|
|
{
|
|
if (conn->closed[side] ||
|
|
enable == conn->rx_enabled[side] ||
|
|
!conn->established[side])
|
|
return 0;
|
|
|
|
if (lws_rx_flow_control(conn->wsi[side], enable))
|
|
return 1;
|
|
|
|
conn->rx_enabled[side] = enable;
|
|
lwsl_info("%s: %s side: %s\n", __func__, side ? "ONW" : "ACC",
|
|
enable ? "rx enabled" : "rx flow controlled");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
callback_raw_proxy(struct lws *wsi, enum lws_callback_reasons reason,
|
|
void *user, void *in, size_t len)
|
|
{
|
|
struct raw_pss *pss = (struct raw_pss *)user;
|
|
struct raw_vhd *vhd = (struct raw_vhd *)lws_protocol_vh_priv_get(
|
|
lws_get_vhost(wsi), lws_get_protocol(wsi));
|
|
const struct packet *ppkt;
|
|
struct conn *conn = NULL;
|
|
struct lws_tokenize ts;
|
|
lws_tokenize_elem e;
|
|
struct packet pkt;
|
|
const char *cp;
|
|
int n;
|
|
|
|
if (pss)
|
|
conn = pss->conn;
|
|
|
|
switch (reason) {
|
|
case LWS_CALLBACK_PROTOCOL_INIT:
|
|
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
|
lws_get_protocol(wsi), sizeof(struct raw_vhd));
|
|
if (lws_pvo_get_str(in, "onward", &cp)) {
|
|
lwsl_err("%s: vh %s: pvo 'onward' required\n", __func__,
|
|
lws_get_vhost_name(lws_get_vhost(wsi)));
|
|
|
|
return -1;
|
|
}
|
|
lws_tokenize_init(&ts, cp, LWS_TOKENIZE_F_DOT_NONTERM |
|
|
LWS_TOKENIZE_F_MINUS_NONTERM |
|
|
LWS_TOKENIZE_F_NO_FLOATS);
|
|
ts.len = strlen(cp);
|
|
|
|
if (lws_tokenize(&ts) != LWS_TOKZE_TOKEN)
|
|
goto bad_onward;
|
|
if (!strncmp(ts.token, "ipv6", ts.token_len))
|
|
vhd->ipv6 = 1;
|
|
else
|
|
if (strncmp(ts.token, "ipv4", ts.token_len))
|
|
goto bad_onward;
|
|
|
|
/* then the colon */
|
|
if (lws_tokenize(&ts) != LWS_TOKZE_DELIMITER)
|
|
goto bad_onward;
|
|
|
|
e = lws_tokenize(&ts);
|
|
if (!vhd->ipv6) {
|
|
if (e != LWS_TOKZE_TOKEN ||
|
|
ts.token_len + 1 >= (int)sizeof(vhd->addr))
|
|
goto bad_onward;
|
|
|
|
lws_strncpy(vhd->addr, ts.token, ts.token_len + 1);
|
|
e = lws_tokenize(&ts);
|
|
if (e == LWS_TOKZE_DELIMITER) {
|
|
/* there should be a port then */
|
|
e = lws_tokenize(&ts);
|
|
if (e != LWS_TOKZE_INTEGER)
|
|
goto bad_onward;
|
|
vhd->port = atoi(ts.token);
|
|
e = lws_tokenize(&ts);
|
|
}
|
|
if (e != LWS_TOKZE_ENDED)
|
|
goto bad_onward;
|
|
} else
|
|
lws_strncpy(vhd->addr, ts.token, sizeof(vhd->addr));
|
|
|
|
lwsl_notice("%s: vh %s: onward %s:%s:%d\n", __func__,
|
|
lws_get_vhost_name(lws_get_vhost(wsi)),
|
|
vhd->ipv6 ? "ipv6": "ipv4", vhd->addr, vhd->port);
|
|
break;
|
|
|
|
bad_onward:
|
|
lwsl_err("%s: onward pvo format must be ipv4:addr[:port] "
|
|
" or ipv6:addr, not '%s'\n", __func__, cp);
|
|
return -1;
|
|
|
|
case LWS_CALLBACK_PROTOCOL_DESTROY:
|
|
break;
|
|
|
|
/* callbacks related to client "onward side" */
|
|
|
|
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
|
|
lwsl_err("CLIENT_CONNECTION_ERROR: %s\n",
|
|
in ? (char *)in : "(null)");
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_CLI_ADOPT:
|
|
lwsl_debug("%s: %p: LWS_CALLBACK_RAW_CLI_ADOPT: pss %p\n", __func__, wsi, pss);
|
|
if (conn || !pss)
|
|
break;
|
|
conn = pss->conn = lws_get_opaque_user_data(wsi);
|
|
if (!conn)
|
|
break;
|
|
conn->established[ONW] = 1;
|
|
/* they start enabled */
|
|
conn->rx_enabled[ACC] = 1;
|
|
conn->rx_enabled[ONW] = 1;
|
|
|
|
/* he disabled his rx while waiting for use to be established */
|
|
flow_control(conn, ACC, 1);
|
|
|
|
lws_callback_on_writable(wsi);
|
|
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_CLI_CLOSE:
|
|
lwsl_debug("LWS_CALLBACK_RAW_PROXY_CLI_CLOSE\n");
|
|
if (!conn)
|
|
break;
|
|
|
|
conn->closed[ONW] = 1;
|
|
|
|
if (conn->closed[ACC])
|
|
destroy_conn(vhd, pss);
|
|
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_CLI_RX:
|
|
lwsl_debug("LWS_CALLBACK_RAW_PROXY_CLI_RX: %d\n", (int)len);
|
|
|
|
if (!conn)
|
|
return 0;
|
|
|
|
if (!pss || !conn->wsi[ACC] || conn->closed[ACC]) {
|
|
lwsl_info(" pss %p, wsi[ACC] %p, closed[ACC] %d\n",
|
|
pss, conn->wsi[ACC], conn->closed[ACC]);
|
|
return -1;
|
|
}
|
|
pkt.payload = malloc(len);
|
|
if (!pkt.payload) {
|
|
lwsl_notice("OOM: dropping\n");
|
|
return -1;
|
|
}
|
|
pkt.len = len;
|
|
pkt.ticket = conn->ticket_next++;
|
|
|
|
memcpy(pkt.payload, in, len);
|
|
if (!lws_ring_insert(conn->r[ONW], &pkt, 1)) {
|
|
__destroy_packet(&pkt);
|
|
lwsl_notice("dropping!\n");
|
|
return -1;
|
|
}
|
|
|
|
lwsl_debug("After onward RX: acc free: %d...\n",
|
|
(int)lws_ring_get_count_free_elements(conn->r[ONW]));
|
|
|
|
if (conn->rx_enabled[ONW] &&
|
|
lws_ring_get_count_free_elements(conn->r[ONW]) < 2)
|
|
flow_control(conn, ONW, 0);
|
|
|
|
if (!conn->closed[ACC])
|
|
lws_callback_on_writable(conn->wsi[ACC]);
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE:
|
|
lwsl_debug("LWS_CALLBACK_RAW_PROXY_CLI_WRITEABLE\n");
|
|
|
|
if (!conn)
|
|
break;
|
|
|
|
ppkt = lws_ring_get_element(conn->r[ACC], &conn->t[ACC]);
|
|
if (!ppkt) {
|
|
lwsl_info("%s: CLI_WRITABLE had nothing in acc ring\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
if (ppkt->ticket != conn->ticket_retired + 1) {
|
|
lwsl_info("%s: acc ring has %d but next %d\n", __func__,
|
|
ppkt->ticket, conn->ticket_retired + 1);
|
|
lws_callback_on_writable(conn->wsi[ACC]);
|
|
break;
|
|
}
|
|
|
|
n = lws_write(wsi, ppkt->payload, ppkt->len, LWS_WRITE_RAW);
|
|
if (n < 0) {
|
|
lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
|
|
|
|
return -1;
|
|
}
|
|
|
|
conn->ticket_retired = ppkt->ticket;
|
|
lws_ring_consume(conn->r[ACC], &conn->t[ACC], NULL, 1);
|
|
lws_ring_update_oldest_tail(conn->r[ACC], conn->t[ACC]);
|
|
|
|
lwsl_debug("acc free: %d...\n",
|
|
(int)lws_ring_get_count_free_elements(conn->r[ACC]));
|
|
|
|
if (!conn->rx_enabled[ACC] &&
|
|
lws_ring_get_count_free_elements(conn->r[ACC]) > 2)
|
|
flow_control(conn, ACC, 1);
|
|
|
|
ppkt = lws_ring_get_element(conn->r[ACC], &conn->t[ACC]);
|
|
lwsl_debug("%s: CLI_WRITABLE: next acc pkt %p idx %d vs %d\n",
|
|
__func__, ppkt, ppkt ? ppkt->ticket : 0,
|
|
conn->ticket_retired + 1);
|
|
|
|
if (ppkt && ppkt->ticket == conn->ticket_retired + 1)
|
|
lws_callback_on_writable(wsi);
|
|
else {
|
|
/*
|
|
* defer checking for accepted side closing until we
|
|
* sent everything in the ring to onward
|
|
*/
|
|
if (conn->closed[ACC])
|
|
/*
|
|
* there is never going to be any more... but
|
|
* we may have some tx still in tx buflist /
|
|
* partial
|
|
*/
|
|
return lws_raw_transaction_completed(wsi);
|
|
|
|
if (lws_ring_get_element(conn->r[ONW], &conn->t[ONW]))
|
|
lws_callback_on_writable(conn->wsi[ACC]);
|
|
}
|
|
break;
|
|
|
|
/* callbacks related to raw socket descriptor "accepted side" */
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_SRV_ADOPT:
|
|
lwsl_debug("LWS_CALLBACK_RAW_SRV_ADOPT\n");
|
|
if (!pss)
|
|
return -1;
|
|
conn = pss->conn = malloc(sizeof(struct conn));
|
|
if (!pss->conn)
|
|
return -1;
|
|
memset(conn, 0, sizeof(*conn));
|
|
|
|
conn->wsi[ACC] = wsi;
|
|
conn->ticket_next = 1;
|
|
|
|
conn->r[ACC] = lws_ring_create(sizeof(struct packet),
|
|
RING_DEPTH, __destroy_packet);
|
|
if (!conn->r[ACC]) {
|
|
lwsl_err("%s: OOM\n", __func__);
|
|
return -1;
|
|
}
|
|
conn->r[ONW] = lws_ring_create(sizeof(struct packet),
|
|
RING_DEPTH, __destroy_packet);
|
|
if (!conn->r[ONW]) {
|
|
lws_ring_destroy(conn->r[ACC]);
|
|
conn->r[ACC] = NULL;
|
|
lwsl_err("%s: OOM\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
|
|
conn->established[ACC] = 1;
|
|
|
|
/* disable any rx until the client side is up */
|
|
flow_control(conn, ACC, 0);
|
|
|
|
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
|
|
|
/* try to create the onward client connection */
|
|
connect_client(vhd, pss);
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_SRV_CLOSE:
|
|
lwsl_debug("LWS_CALLBACK_RAW_PROXY_SRV_CLOSE:\n");
|
|
|
|
if (!conn)
|
|
break;
|
|
|
|
conn->closed[ACC] = 1;
|
|
if (conn->closed[ONW])
|
|
destroy_conn(vhd, pss);
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_SRV_RX:
|
|
lwsl_debug("LWS_CALLBACK_RAW_PROXY_SRV_RX: rx %d\n", (int)len);
|
|
|
|
if (!conn || !conn->wsi[ONW]) {
|
|
lwsl_err("%s: LWS_CALLBACK_RAW_PROXY_SRV_RX: "
|
|
"conn->wsi[ONW] NULL\n", __func__);
|
|
return -1;
|
|
}
|
|
if (conn->closed[ONW]) {
|
|
lwsl_info(" closed[ONW] %d\n", conn->closed[ONW]);
|
|
return -1;
|
|
}
|
|
|
|
if (!len)
|
|
return 0;
|
|
|
|
pkt.payload = malloc(len);
|
|
if (!pkt.payload) {
|
|
lwsl_notice("OOM: dropping\n");
|
|
return -1;
|
|
}
|
|
pkt.len = len;
|
|
pkt.ticket = conn->ticket_next++;
|
|
|
|
memcpy(pkt.payload, in, len);
|
|
if (!lws_ring_insert(conn->r[ACC], &pkt, 1)) {
|
|
__destroy_packet(&pkt);
|
|
lwsl_notice("dropping!\n");
|
|
return -1;
|
|
}
|
|
|
|
lwsl_debug("After acc RX: acc free: %d...\n",
|
|
(int)lws_ring_get_count_free_elements(conn->r[ACC]));
|
|
|
|
if (conn->rx_enabled[ACC] &&
|
|
lws_ring_get_count_free_elements(conn->r[ACC]) <= 2)
|
|
flow_control(conn, ACC, 0);
|
|
|
|
if (conn->established[ONW] && !conn->closed[ONW])
|
|
lws_callback_on_writable(conn->wsi[ONW]);
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE:
|
|
lwsl_debug("LWS_CALLBACK_RAW_PROXY_SRV_WRITEABLE\n");
|
|
|
|
if (!conn || !conn->established[ONW] || conn->closed[ONW])
|
|
break;
|
|
|
|
ppkt = lws_ring_get_element(conn->r[ONW], &conn->t[ONW]);
|
|
if (!ppkt) {
|
|
lwsl_info("%s: SRV_WRITABLE nothing in onw ring\n",
|
|
__func__);
|
|
break;
|
|
}
|
|
|
|
if (ppkt->ticket != conn->ticket_retired + 1) {
|
|
lwsl_info("%s: onw ring has %d but next %d\n", __func__,
|
|
ppkt->ticket, conn->ticket_retired + 1);
|
|
lws_callback_on_writable(conn->wsi[ONW]);
|
|
break;
|
|
}
|
|
|
|
n = lws_write(wsi, ppkt->payload, ppkt->len, LWS_WRITE_RAW);
|
|
if (n < 0) {
|
|
lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
|
|
|
|
return -1;
|
|
}
|
|
|
|
conn->ticket_retired = ppkt->ticket;
|
|
lws_ring_consume(conn->r[ONW], &conn->t[ONW], NULL, 1);
|
|
lws_ring_update_oldest_tail(conn->r[ONW], conn->t[ONW]);
|
|
|
|
lwsl_debug("onward free: %d... waiting %d\n",
|
|
(int)lws_ring_get_count_free_elements(conn->r[ONW]),
|
|
(int)lws_ring_get_count_waiting_elements(conn->r[ONW],
|
|
&conn->t[ONW]));
|
|
|
|
if (!conn->rx_enabled[ONW] &&
|
|
lws_ring_get_count_free_elements(conn->r[ONW]) > 2)
|
|
flow_control(conn, ONW, 1);
|
|
|
|
ppkt = lws_ring_get_element(conn->r[ONW], &conn->t[ONW]);
|
|
lwsl_debug("%s: SRV_WRITABLE: next onw pkt %p idx %d vs %d\n",
|
|
__func__, ppkt, ppkt ? ppkt->ticket : 0,
|
|
conn->ticket_retired + 1);
|
|
|
|
if (ppkt && ppkt->ticket == conn->ticket_retired + 1)
|
|
lws_callback_on_writable(wsi);
|
|
else {
|
|
/*
|
|
* defer checking for onward side closing until we
|
|
* sent everything in the ring to accepted side
|
|
*/
|
|
if (conn->closed[ONW])
|
|
/*
|
|
* there is never going to be any more... but
|
|
* we may have some tx still in tx buflist /
|
|
* partial
|
|
*/
|
|
return lws_raw_transaction_completed(wsi);
|
|
|
|
if (lws_ring_get_element(conn->r[ACC], &conn->t[ACC]))
|
|
lws_callback_on_writable(conn->wsi[ONW]);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return lws_callback_http_dummy(wsi, reason, user, in, len);
|
|
}
|
|
|
|
#define LWS_PLUGIN_PROTOCOL_RAW_PROXY { \
|
|
"raw-proxy", \
|
|
callback_raw_proxy, \
|
|
sizeof(struct raw_pss), \
|
|
8192, \
|
|
8192, NULL, 0 \
|
|
}
|
|
|
|
#if !defined (LWS_PLUGIN_STATIC)
|
|
|
|
static const struct lws_protocols protocols[] = {
|
|
LWS_PLUGIN_PROTOCOL_RAW_PROXY
|
|
};
|
|
|
|
LWS_VISIBLE int
|
|
init_protocol_lws_raw_proxy(struct lws_context *context,
|
|
struct lws_plugin_capability *c)
|
|
{
|
|
if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
|
|
lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
|
|
c->api_magic);
|
|
return 1;
|
|
}
|
|
|
|
c->protocols = protocols;
|
|
c->count_protocols = LWS_ARRAY_SIZE(protocols);
|
|
c->extensions = NULL;
|
|
c->count_extensions = 0;
|
|
|
|
return 0;
|
|
}
|
|
|
|
LWS_VISIBLE int
|
|
destroy_protocol_lws_raw_proxy(struct lws_context *context)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|