Update Files

This commit is contained in:
2025-01-22 17:22:38 +01:00
parent 89b9349629
commit 4c5e729485
5132 changed files with 1195369 additions and 0 deletions

View File

@ -0,0 +1,27 @@
include_directories(../../../core
../../../core-net
../../../plat/unix
../../../plat/freertos
../../../tls
../../../event-libs
../../../roles
../../../roles/http
../../../roles/h1
../../../roles/h2
../../../roles/ws
../../../roles/mqtt
../../../roles/raw
../../../system
../../../system/smd
../../../system/fault-injection
../../../system/metrics
${LIBUV_INCLUDE_DIRS}
)
foreach(libpath ${LWS_LIB_BUILD_INC_PATHS})
include_directories(${libpath})
endforeach()

View File

@ -0,0 +1,89 @@
# SSPC client support
## Full vs LWS_ONLY_SSPC lws builds
SSPC (Secure Stream Proxy Client) apis are built into libwebsockets as you would
expect if built with `LWS_WITH_SECURE_STREAMS_PROXY_API`. These are the client
SS api implementation, the serialization and deserialization of SS protocol, and
generic transport interface.
You can also choose to build lws so it ONLY contains the SSPC pieces suitable
for the client side, with the libwebsockets build option `LWS_ONLY_SSPC`.
![lws-sspc build](../../../../doc-assets/lws-sspc-1.png)
Identical sources are used in both cases, but with `LWS_ONLY_SSPC` only the
parts related to mux and SSPC are included, coming to less than 25KB .text on
armv7 while providing the full proxied SS API. Also excluded are the normal
`lws_context` and event loop facilities, the provided apis expect to work with
an existing event loop.
`LWS_ONLY_SSPC` facilitates using full libwebsockets SSPC client support on very
small devices that don't need the rest of lws. Such devices do not need to have
tls, an IP stack or any network, yet can use full Secure Streams capabilities
via the proxy (eg, h2, including stream binding, or ws over tls).
![lws-sspc build](../../../../doc-assets/lws-sspc-2.png)
`LWS_ONLY_SSPC` does not need a normal `struct lws_context` nor the lws event
loop or other apis. To maintain compatibility with the SS apis the user code
provides a simple, static stub lws_context with a couple of members and no
creation or destruction.
You can find a minimal example that builds with `LWS_ONLY_SSPC` in
at `minimal-examples/secure-streams/minimal-secure-streams-custom-client-transport`,
implementing full SS apis over a UART link, where the client does not link to
lws nor need any network stack, tls library or internet protocol implementation.
It expects to use two USB serial adapters in loopback, one for this example and
one for the related proxy, minimal-secure-streams-custom-proxy-transport.
These two examples need different build dirs (but use the same libwebsockets
source) since the client requires lws built with `LWS_ONLY_SSPC`.
## Communication layers
Different transports have quite different semantics for connectivity. Serialized
SS communication has three abstract layers:
- link: connectivity is established. The serial cable is plugged in, the
RF link is established, or the Posix Socket has connected.
- mux: On some transports, eg, UART, there is a single logical link that we
want to be able to carry multiple SS connections, so the transport must
provide a way to negotiate multiplexing over the link.
- Serialized SS: the SS activity serialized into a stream in both directions
How these look depend on the nature of the underlying transport, some examples:
|layer|RF|UART|Unix Domain Socket|
|---|---|---|---|
|link|managed at RF stack|detect by periodic PING/PONG|Socket Connection semantics|
|mux|extra framing|extra framing|not needed, one UDS client socket for each SS|
|Serialized SS|bytestream|bytestream|Datagram stream|
SSPC can directly hook up to an lws_transport_ops structure, this is used in the
case where multiplexing is handled by the transport like Unix Domain Socket link
to the proxy. In that case, each SS makes its own personal Unix Domain Socket
link to the proxy.
The lws_transport_mux support is designed to interpose between SSPC layter and
the lws_transport itself, to provide its own multiplexing layer. This is needed
in the case where there is just a reliable bytestream link to the proxy, and no
suitable channelization or link detection, for example a simple UART transport.
lws_transport_mux provides 250 mux channels over the transport, with link
detection by three-way PING handshakes at the mux layer.
## LWS_ONLY_SSPC imports
Four system integration imports are needed by the library.
|prototype|function|
|---|---|
|lws_usec_t **lws_now_usecs**(void)|get us-resolution monotonic time|
|void **__lws_logv**(lws_log_cx_t *ignore1, lws_log_prepend_cx_t ignore2, void *ignore3, int filter, const char *_fun, const char *format, va_list ap)|log emit|
|void **lws_sul_schedule**(struct lws_context_standalone *ctx, int tsi, lws_sorted_usec_list_t *sul, sul_cb_t _cb, lws_usec_t _us)|schedule sul callback|
|void **lws_sul_cancel**(lws_sorted_usec_list_t *sul)|Cancel scheduled callback|

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 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.
*
*
* Client SSPC where the connectivity is implemented by a wsi
*/
#include <private-lib-core.h>
static int
lws_sss_transport_wsi_cb(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)lws_get_opaque_user_data(wsi);
size_t pktsize = wsi->a.context->max_http_header_data;
lws_ss_state_return_t r;
switch (reason) {
case LWS_CALLBACK_CONNECTING:
/*
* In our particular case, we want CCEs even inside the
* initial connect loop time
*/
wsi->client_suppress_CONNECTION_ERROR = 0;
break;
case LWS_CALLBACK_CLIENT_CONNECTION_ERROR:
lwsl_warn("%s: CCE: %s\n", __func__,
in ? (const char *)in : "null");
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free
* any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
lws_set_opaque_user_data(wsi, NULL);
h->txp_path.ops_in->event_connect_disposition(h, 1);
break;
case LWS_CALLBACK_RAW_CONNECTED:
lwsl_user("%s: CONNECTED\n", __func__);
if (h->txp_path.ops_in->event_connect_disposition(h, 0))
return -1;
/*
* We create the dsh at the response to the initial tx, which
* will let us know the policy's max size for it... let's
* protect the connection with a promise to complete the
* SS serialization streamtype negotation within a short period,
* we will cancel this timeout when we have the proxy's ack
* of the streamtype serialization, eg, it exists in the proxy
* policy etc
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);
break;
case LWS_CALLBACK_RAW_CLOSE:
/*
* our ss proxy Unix Domain socket has closed...
*/
lwsl_sspc_info(h, "LWS_CALLBACK_RAW_CLOSE: proxy conn down, wsi %s",
lws_wsi_tag(wsi));
if (h) {
r = h->txp_path.ops_in->event_closed(h);
h->txp_path.priv_in = NULL;
if (r == LWSSSSRET_DESTROY_ME) {
lws_set_opaque_user_data(wsi, NULL);
lws_sspc_destroy(&h);
}
}
break;
case LWS_CALLBACK_RAW_RX:
/*
* ie, the proxy has sent us something
*/
if (!h || !h->txp_path.priv_in) {
lwsl_info("%s: rx when client ss destroyed\n", __func__);
return -1;
}
lwsl_sspc_info(h, "%s: RAW_RX: rx %d\n", __func__, (int)len);
if (!len) {
lwsl_sspc_notice(h, "RAW_RX: zero len");
return -1;
}
r = h->txp_path.ops_in->event_read((lws_transport_priv_t)h,
(const uint8_t *)in, len);
switch (r) {
default:
break;
case LWSSSSRET_DISCONNECT_ME:
lwsl_info("%s: proxlicent RX ended with DISCONNECT_ME\n",
__func__);
return -1;
case LWSSSSRET_DESTROY_ME:
lwsl_info("%s: proxlicent RX ended with DESTROY_ME\n",
__func__);
lws_set_opaque_user_data(wsi, NULL);
lws_sspc_destroy(&h);
return -1;
}
if (h->state == LPCSCLI_LOCAL_CONNECTED ||
h->state == LPCSCLI_ONWARD_CONNECT)
lws_set_timeout(wsi, 0, 0);
break;
case LWS_CALLBACK_RAW_WRITEABLE:
/*
* We can transmit something to the proxy...
*/
if (!h)
break;
lwsl_sspc_debug(h, "WRITEABLE %s, state %d",
wsi->lc.gutag, h->state);
if (h->txp_path.ops_in->event_can_write(h, pktsize))
return -1;
return 0;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
const struct lws_protocols lws_sspc_protocols[] = {
{
"ssproxy-protocol",
lws_sss_transport_wsi_cb,
0,
2048, 2048, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 }
};
/*
* lws_sss_transport ops for wsi transport
*/
static int
lws_sss_transport_wsi_retry_connect(lws_txp_path_client_t *path, lws_sspc_handle_t *h)
{
struct lws_client_connect_info i;
/*
* We may have started up before the system proxy, so be prepared with
* a sul to retry at 1Hz
*/
memset(&i, 0, sizeof i);
i.context = h->context;
if (h->context->ss_proxy_port) { /* tcp */
i.address = h->context->ss_proxy_address;
i.port = h->context->ss_proxy_port;
i.iface = h->context->ss_proxy_bind;
} else {
if (h->context->ss_proxy_bind)
i.address = h->context->ss_proxy_bind;
else
#if defined(__linux__)
i.address = "+@proxy.ss.lws";
#else
i.address = "+/tmp/proxy.ss.lws";
#endif
}
i.host = i.address;
i.origin = i.address;
i.method = "RAW";
i.protocol = lws_sspc_protocols[0].name;
i.local_protocol_name = lws_sspc_protocols[0].name;
i.path = "";
i.pwsi = (struct lws **)&h->txp_path.priv_onw;
i.opaque_user_data = (void *)h;
i.ssl_connection = LCCSCF_SECSTREAM_PROXY_LINK;
lws_metrics_caliper_bind(h->cal_txn, h->context->mt_ss_cliprox_conn);
#if defined(LWS_WITH_SYS_METRICS)
lws_metrics_tag_add(&h->cal_txn.mtags_owner, "ss", h->ssi.streamtype);
#endif
/* this wsi is the link to the proxy */
if (!lws_client_connect_via_info(&i)) {
#if defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
return 1; /* going to need to retry */
}
lwsl_sspc_notice(h, "%s", ((struct lws *)(h->txp_path.priv_onw))->lc.gutag);
return 0; /* in progress */
}
static void
lws_sss_transport_wsi_req_write(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (wsi)
lws_callback_on_writable(wsi);
}
static int
lws_sss_transport_wsi_write(lws_transport_priv_t priv, uint8_t *buf, size_t len)
{
struct lws *wsi = (struct lws *)priv;
if (lws_write(wsi, buf, len, LWS_WRITE_RAW) != (ssize_t)len) {
lwsl_wsi_notice(wsi, "failed");
return -1;
}
return 0;
}
static void
lws_sss_transport_wsi_close(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (!wsi)
return;
lws_set_opaque_user_data(wsi, NULL);
lws_wsi_close(wsi, LWS_TO_KILL_ASYNC);
}
static void
lws_sss_transport_wsi_stream_up(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
}
const lws_transport_client_ops_t txp_ops_sspc_wsi = {
.name = "txp_sspc_wsi",
.event_retry_connect = lws_sss_transport_wsi_retry_connect,
.req_write = lws_sss_transport_wsi_req_write,
._write = lws_sss_transport_wsi_write,
._close = lws_sss_transport_wsi_close,
.event_stream_up = lws_sss_transport_wsi_stream_up,
.dsh_splitat = 1300,
};

View File

@ -0,0 +1,515 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 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.
*
* These are helpers used by the transport implementation. They contain the
* generic sspc actions to handle events that happen at the transport.
*/
#include <private-lib-core.h>
#if defined(STANDALONE)
#define lws_context lws_context_standalone
void
lws_ser_wu16be(uint8_t *b, uint16_t u)
{
*b++ = (uint8_t)(u >> 8);
*b = (uint8_t)u;
}
void
lws_ser_wu32be(uint8_t *b, uint32_t u32)
{
*b++ = (uint8_t)(u32 >> 24);
*b++ = (uint8_t)(u32 >> 16);
*b++ = (uint8_t)(u32 >> 8);
*b = (uint8_t)u32;
}
void
lws_ser_wu64be(uint8_t *b, uint64_t u64)
{
lws_ser_wu32be(b, (uint32_t)(u64 >> 32));
lws_ser_wu32be(b + 4, (uint32_t)u64);
}
#undef lws_malloc
#define lws_malloc(a, b) malloc(a)
#undef lws_free
#define lws_free(a) free(a)
#endif
static size_t
lws_sspc_serialize_metadata(lws_sspc_handle_t *h, lws_sspc_metadata_t *md,
uint8_t *p, uint8_t *end)
{
size_t n, txc;
if (md->name[0] == '\0') {
lwsl_sspc_info(h, "sending tx credit update %d",
md->tx_cr_adjust);
p[0] = LWSSS_SER_TXPRE_TXCR_UPDATE;
lws_ser_wu16be(&p[1], 4);
lws_ser_wu32be(&p[3], (uint32_t)md->tx_cr_adjust);
n = 7;
} else {
lwsl_sspc_info(h, "sending metadata");
p[0] = LWSSS_SER_TXPRE_METADATA;
txc = strlen(md->name);
n = txc + 1 + md->len;
if (n > 0xffff)
/* we can't serialize this metadata in 16b length */
return 0;
if (n > lws_ptr_diff_size_t(end, &p[4]))
/* we don't have space for this metadata */
return 0;
lws_ser_wu16be(&p[1], (uint16_t)n);
p[3] = (uint8_t)txc;
memcpy(&p[4], md->name, (unsigned int)txc);
memcpy(&p[4 + txc], &md[1], md->len);
n = 4 + txc + md->len;
}
lws_dll2_remove(&md->list);
lws_free(md);
return n;
}
/*
* An attempt to establish a link to the SS proxy has failed
*/
lws_ss_state_return_t
lws_sspc_txp_connect_disposition(lws_sspc_handle_t *h, int disposition)
{
lws_ss_state_return_t r;
uint64_t i;
if (!disposition) {
if (!h
#if !defined(STANDALONE)
|| lws_fi(&h->fic, "sspc_fail_on_linkup")
#endif
)
return 1;
lwsl_sspc_info(h, "CONNECTED (%s), %s", h->ssi.streamtype, h->txp_path.ops_onw->name);
h->state = LPCSCLI_SENDING_INITIAL_TX;
h->us_start_upstream = 0;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
h->txp_path.priv_onw = NULL;
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
if (!h->ssi.state)
return LWSSSSRET_OK;
i = (uint64_t)(lws_now_usecs() - h->us_start_upstream) / LWS_US_PER_MS;
if (i > 0xffffffffull)
i = 0xffffffffull;
r = h->ssi.state(lws_sspc_to_user_object(h), NULL,
LWSSSCS_UPSTREAM_LINK_RETRY, (uint32_t)i);
if (r == LWSSSSRET_DESTROY_ME)
lws_sspc_destroy(&h);
return LWSSSSRET_OK;
}
void
lws_sspc_sul_retry_cb(lws_sorted_usec_list_t *sul)
{
lws_sspc_handle_t *h = lws_container_of(sul, lws_sspc_handle_t,
sul_retry);
if (h->txp_path.ops_onw->event_retry_connect(&h->txp_path, h))
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
}
/*
* The transport connection has closed
*/
lws_ss_state_return_t
lws_sspc_txp_event_closed(lws_transport_priv_t priv)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)priv;
lws_ss_state_return_t r = LWSSSSRET_OK;
if (!h) {
lwsl_sspc_info(h, "No sspc on client proxy link close");
return LWSSSSRET_OK;
}
h->parser.ps = RPAR_TYPE;
lws_dsh_empty(h->dsh);
h->txp_path.priv_onw = NULL;
h->conn_req_state = LWSSSPC_ONW_NONE;
if (h->ss_dangling_connected && h->ssi.state) {
lwsl_sspc_notice(h, "setting _DISCONNECTED");
h->ss_dangling_connected = 0;
h->prev_ss_state = LWSSSCS_DISCONNECTED;
r = h->ssi.state(ss_to_userobj(h), NULL,
LWSSSCS_DISCONNECTED, 0);
}
if (r != LWSSSSRET_DESTROY_ME)
/*
* schedule a reconnect in 1s
*/
lws_sul_schedule(h->context, 0, &h->sul_retry,
lws_sspc_sul_retry_cb, LWS_US_PER_SEC);
return r;
}
/*
* We received rx from the proxy... caller must do destroy on DESTROY_ME
*/
lws_ss_state_return_t
lws_sspc_txp_rx_from_proxy(lws_transport_priv_t txp_priv, const uint8_t *in,
size_t len)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)txp_priv;
void *m = (void *)((uint8_t *)(h + 1));
assert(h);
#if !defined(STANDALONE)
if (lws_fi(&h->fic, "sspc_fake_rxparse_disconnect_me"))
return LWSSSSRET_DISCONNECT_ME;
if (lws_fi(&h->fic, "sspc_fake_rxparse_destroy_me"))
return LWSSSSRET_DESTROY_ME;
#endif
return lws_sspc_deserialize_parse(h, in, len, (lws_ss_handle_t **)m);
}
lws_ss_state_return_t
lws_sspc_txp_tx(lws_sspc_handle_t *h, size_t metadata_limit)
{
uint8_t *pkt = NULL, *p = NULL, *end = NULL;
void *m = (void *)((uint8_t *)(h + 1));
lws_ss_state_return_t r;
uint8_t _s[64 + LWS_PRE], *s = _s + LWS_PRE, *cp = s;
size_t txl, len;
lws_usec_t us;
int flags;
/*
* Management of ss timeout can happen any time and doesn't
* depend on wsi existence or state
*/
if (h->pending_timeout_update) {
cp = s;
*s = LWSSS_SER_TXPRE_TIMEOUT_UPDATE;
*(s + 1) = 0;
*(s + 2) = 4;
/*
* 0: use policy timeout value
* 0xffffffff: cancel the timeout
*/
lws_ser_wu32be(s + 3, h->timeout_ms);
/* in case anything else to write */
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
h->pending_timeout_update = 0;
txl = 7;
goto do_write;
}
*(s + 1) = 0;
/*
* This is the state of the link that connects us to the onward
* proxy
*/
switch (h->state) {
case LPCSCLI_SENDING_INITIAL_TX:
/*
* We are negotating the opening of a particular
* streamtype
*/
// lwsl_sspc_notice(h, "LPCSCLI_SENDING_INITIAL_TX");
txl = strlen(h->ssi.streamtype) + 1 + 4 + 4;
cp = s;
*s = LWSSS_SER_TXPRE_STREAMTYPE;
lws_ser_wu16be(s + 1, (uint16_t)txl);
/* SSSv1: add protocol version byte (initially 1) */
*(s + 3) = (uint8_t)LWS_SSS_CLIENT_PROTOCOL_VERSION;
#if defined(WIN32) || defined(LWS_PLAT_BAREMETAL)
lws_ser_wu32be(s + 4, (uint32_t)0);
#else
lws_ser_wu32be(s + 4, (uint32_t)getpid());
#endif
lws_ser_wu32be(s + 8, (uint32_t)h->txc.peer_tx_cr_est);
lws_strncpy((char *)(s + 12), h->ssi.streamtype,
(sizeof(_s) - LWS_PRE) - 12);
txl += 3;
h->state = LPCSCLI_WAITING_CREATE_RESULT;
goto do_write;
case LPCSCLI_LOCAL_CONNECTED:
// lwsl_sspc_notice(h, "LPCSCLI_LOCAL_CONNECTED");
/*
* Do we need to prioritize sending any metadata
* changes?
*/
if (h->metadata_owner.count) {
lws_sspc_metadata_t *md = lws_container_of(
lws_dll2_get_tail(&h->metadata_owner),
lws_sspc_metadata_t, list);
size_t n;
pkt = lws_malloc(metadata_limit + LWS_PRE, __func__);
if (!pkt)
goto hangup;
cp = p = pkt + LWS_PRE;
end = p + metadata_limit;
n = lws_sspc_serialize_metadata(h, md, p, end);
if (!n)
goto metadata_hangup;
txl = (size_t)n;
lwsl_sspc_debug(h, "(local_conn) metadata");
goto req_write_and_issue;
}
if (h->pending_writeable_len) {
lwsl_sspc_debug(h, "(local_conn) PAYLOAD_LENGTH_HINT %u",
(unsigned int)h->writeable_len);
cp = s;
*s = LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT;
lws_ser_wu16be(s + 1, 4);
lws_ser_wu32be(s + 3, (uint32_t)h->writeable_len);
h->pending_writeable_len = 0;
txl = 7;
goto req_write_and_issue;
}
if (h->conn_req_state >= LWSSSPC_ONW_ONGOING) {
lwsl_sspc_info(h, "conn_req_state %d",
h->conn_req_state);
break;
}
lwsl_sspc_info(h, "(local_conn) onward connect");
h->conn_req_state = LWSSSPC_ONW_ONGOING;
cp = s;
*s = LWSSS_SER_TXPRE_ONWARD_CONNECT;
*(s + 1) = 0;
*(s + 2) = 0;
txl = 3;
goto do_write;
case LPCSCLI_OPERATIONAL:
/*
*
* - Do we need to prioritize sending any metadata
* changes? (includes txcr updates)
*
* - Do we need to forward a hint about the payload
* length?
*/
pkt = lws_malloc(metadata_limit + LWS_PRE, __func__);
if (!pkt)
goto hangup;
cp = p = pkt + LWS_PRE;
end = p + metadata_limit;
if (h->metadata_owner.count) {
lws_sspc_metadata_t *md = lws_container_of(
lws_dll2_get_tail(&h->metadata_owner),
lws_sspc_metadata_t, list);
txl = lws_sspc_serialize_metadata(h, md, p, end);
if (!txl)
goto metadata_hangup;
goto req_write_and_issue;
}
if (h->pending_writeable_len) {
lwsl_sspc_info(h, "PAYLOAD_LENGTH_HINT %u",
(unsigned int)h->writeable_len);
cp = s;
*s = LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT;
lws_ser_wu16be(s + 1, 4);
lws_ser_wu32be(s + 3, (uint32_t)h->writeable_len);
h->pending_writeable_len = 0;
txl = 7;
goto req_write_and_issue;
}
/* we can't write anything if we don't have credit */
if (!h->ignore_txc && h->txc.tx_cr <= 0)
lwsl_sspc_info(h, "WRITEABLE / OPERATIONAL:"
" lack credit (%d)",
(int)h->txc.tx_cr);
len = metadata_limit - LWS_PRE - 19;
flags = 0;
if (!h->ssi.tx) {
txl = 0;
goto do_write_nz;
}
r = h->ssi.tx(m, h->ord++, pkt + LWS_PRE + 19, &len, &flags);
switch (r) {
case LWSSSSRET_TX_DONT_SEND:
txl = 0;
goto do_write_nz;
case LWSSSSRET_DISCONNECT_ME:
case LWSSSSRET_DESTROY_ME:
lwsl_sspc_warn(h, "sspc tx DISCONNECT/DESTROY TBD");
break;
default:
break;
}
h->txc.tx_cr = h->txc.tx_cr - (int)len;
cp = p;
txl = len + 19;
us = lws_now_usecs();
p[0] = LWSSS_SER_TXPRE_TX_PAYLOAD;
lws_ser_wu16be(&p[1], (uint16_t)(len + 19 - 3));
lws_ser_wu32be(&p[3], (uint32_t)flags);
/* time spent here waiting to send this */
lws_ser_wu32be(&p[7], (uint32_t)(us - h->us_earliest_write_req));
/* ust that the client write happened */
lws_ser_wu64be(&p[11], (uint64_t)us);
h->us_earliest_write_req = 0;
if (flags & LWSSS_FLAG_EOM)
if (h->rsidx + 1 < (int)LWS_ARRAY_SIZE(h->rideshare_ofs) &&
h->rideshare_ofs[h->rsidx + 1])
h->rsidx++;
goto do_write;
default:
break;
}
return LWSSSSRET_OK;
req_write_and_issue:
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
do_write_nz:
if (!txl) {
lws_free(pkt);
return LWSSSSRET_OK;
}
do_write:
if (
#if !defined(STANDALONE)
!lws_fi(&h->fic, "sspc_link_write_fail") &&
#endif
!h->txp_path.ops_onw->_write(h->txp_path.priv_onw, cp, txl)) {
if (pkt)
lws_free(pkt);
return LWSSSSRET_OK;
}
goto hangup;
metadata_hangup:
lwsl_sspc_err(h, "metadata too large");
hangup:
lws_free(pkt);
lwsl_sspc_warn(h, "hangup");
/* hang up on the proxy link */
return LWSSSSRET_DISCONNECT_ME;
}
void
lws_sspc_txp_lost_coherence(lws_transport_priv_t txp_priv)
{
lws_sspc_handle_t *h = (lws_sspc_handle_t *)txp_priv;
lwsl_sspc_warn(h, "Lost Coherence");
h->conn_req_state = LWSSSPC_ONW_NONE;
/* pass thru to lower layer, eg, mux */
h->txp_path.ops_onw->lost_coherence(h->txp_path.priv_onw);
}
/*
* The actual client transports bind to this transport ops for "inside sspc".
* It's like this so we can transparently interpose the mux.
*
* Only the apis the transport needs to call on the inside need timplementing
* for this
*/
const lws_transport_client_ops_t lws_txp_inside_sspc = {
.name = "txp_inside_sspc",
.event_connect_disposition = lws_sspc_txp_connect_disposition,
.event_read = lws_sspc_txp_rx_from_proxy,
.event_can_write = lws_sspc_txp_tx,
.event_closed = lws_sspc_txp_event_closed,
.lost_coherence = lws_sspc_txp_lost_coherence,
};

View File

@ -0,0 +1,851 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 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.
*/
#include <private-lib-core.h>
extern const uint32_t ss_state_txn_validity[17];
#if defined(STANDALONE)
#define lws_context lws_context_standalone
static const char *state_names[] = {
"(unset)",
"LWSSSCS_CREATING",
"LWSSSCS_DISCONNECTED",
"LWSSSCS_UNREACHABLE",
"LWSSSCS_AUTH_FAILED",
"LWSSSCS_CONNECTED",
"LWSSSCS_CONNECTING",
"LWSSSCS_DESTROYING",
"LWSSSCS_POLL",
"LWSSSCS_ALL_RETRIES_FAILED",
"LWSSSCS_QOS_ACK_REMOTE",
"LWSSSCS_QOS_NACK_REMOTE",
"LWSSSCS_QOS_ACK_LOCAL",
"LWSSSCS_QOS_NACK_LOCAL",
"LWSSSCS_TIMEOUT",
"LWSSSCS_SERVER_TXN",
"LWSSSCS_SERVER_UPGRADE",
"LWSSSCS_EVENT_WAIT_CANCELLED",
"LWSSSCS_UPSTREAM_LINK_RETRY",
};
const char *
lws_ss_state_name(int state)
{
if (state >= LWSSSCS_USER_BASE)
return "user state";
if (state >= (int)LWS_ARRAY_SIZE(state_names))
return "unknown";
return state_names[state];
}
const uint32_t ss_state_txn_validity[] = {
/* if we was last in this state... we can legally go to these states */
[0] = (1 << LWSSSCS_CREATING) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CREATING] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_SERVER_UPGRADE) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_DISCONNECTED] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_UNREACHABLE] = (1 << LWSSSCS_ALL_RETRIES_FAILED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_CONNECTING) |
/* win conn failure > retry > succ */
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_AUTH_FAILED] = (1 << LWSSSCS_ALL_RETRIES_FAILED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CONNECTED] = (1 << LWSSSCS_SERVER_UPGRADE) |
(1 << LWSSSCS_SERVER_TXN) |
(1 << LWSSSCS_AUTH_FAILED) |
(1 << LWSSSCS_QOS_ACK_REMOTE) |
(1 << LWSSSCS_QOS_NACK_REMOTE) |
(1 << LWSSSCS_QOS_ACK_LOCAL) |
(1 << LWSSSCS_QOS_NACK_LOCAL) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_POLL) | /* proxy retry */
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_CONNECTING] = (1 << LWSSSCS_UNREACHABLE) |
(1 << LWSSSCS_AUTH_FAILED) |
(1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) | /* proxy retry */
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_DESTROYING] = 0,
[LWSSSCS_POLL] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_ALL_RETRIES_FAILED] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_ACK_REMOTE] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
#if defined(LWS_ROLE_MQTT)
(1 << LWSSSCS_QOS_ACK_REMOTE) |
#endif
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_NACK_REMOTE] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_ACK_LOCAL] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_QOS_NACK_LOCAL] = (1 << LWSSSCS_DESTROYING) |
(1 << LWSSSCS_TIMEOUT),
/* he can get the timeout at any point and take no action... */
[LWSSSCS_TIMEOUT] = (1 << LWSSSCS_CONNECTING) |
(1 << LWSSSCS_CONNECTED) |
(1 << LWSSSCS_QOS_ACK_REMOTE) |
(1 << LWSSSCS_QOS_NACK_REMOTE) |
(1 << LWSSSCS_POLL) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_UNREACHABLE) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_SERVER_TXN] = (1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DESTROYING),
[LWSSSCS_SERVER_UPGRADE] = (1 << LWSSSCS_SERVER_TXN) |
(1 << LWSSSCS_TIMEOUT) |
(1 << LWSSSCS_DISCONNECTED) |
(1 << LWSSSCS_DESTROYING),
};
char *
lws_strncpy(char *dest, const char *src, size_t size)
{
strncpy(dest, src, size - 1);
dest[size - 1] = '\0';
return dest;
}
#undef lws_malloc
#define lws_malloc(a, b) malloc(a)
#undef lws_free
#define lws_free(a) free(a)
extern void
__lws_logv(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj,
int filter, const char *_fun, const char *format, va_list ap);
void _lws_logv(int filter, const char *format, va_list ap)
{
__lws_logv(NULL, NULL, NULL, filter, NULL, format, ap);
}
void
_lws_log(int filter, const char *format, ...)
{
va_list ap;
va_start(ap, format);
_lws_logv(filter, format, ap);
va_end(ap);
}
void
_lws_log_cx(lws_log_cx_t *cx, lws_log_prepend_cx_t prep, void *obj,
int filter, const char *_fun, const char *format, ...)
{
va_list ap;
va_start(ap, format);
__lws_logv(cx, prep, obj, filter, _fun, format, ap);
va_end(ap);
}
#endif
int
lws_ss_check_next_state_sspc(lws_sspc_handle_t *ss, uint8_t *prevstate,
lws_ss_constate_t cs)
{
if (cs >= LWSSSCS_USER_BASE || cs == LWSSSCS_EVENT_WAIT_CANCELLED)
/*
* we can't judge user or transient states, leave the old state
* and just wave them through
*/
return 0;
if (cs >= LWS_ARRAY_SIZE(ss_state_txn_validity)) {
/* we don't recognize this state as usable */
lwsl_sspc_err(ss, "bad new state %u", cs);
assert(0);
return 1;
}
if (*prevstate >= LWS_ARRAY_SIZE(ss_state_txn_validity)) {
/* existing state is broken */
lwsl_sspc_err(ss, "bad existing state %u",
(unsigned int)*prevstate);
assert(0);
return 1;
}
if (ss_state_txn_validity[*prevstate] & (1u << cs)) {
lwsl_sspc_notice(ss, "%s -> %s",
lws_ss_state_name((int)*prevstate),
lws_ss_state_name((int)cs));
/* this is explicitly allowed, update old state to new */
*prevstate = (uint8_t)cs;
return 0;
}
lwsl_sspc_err(ss, "transition from %s -> %s is illegal",
lws_ss_state_name((int)*prevstate),
lws_ss_state_name((int)cs));
assert(0);
return 1;
}
lws_ss_state_return_t
lws_sspc_event_helper(lws_sspc_handle_t *h, lws_ss_constate_t cs,
lws_ss_tx_ordinal_t flags)
{
lws_ss_state_return_t ret;
if (!h)
return LWSSSSRET_OK;
if (lws_ss_check_next_state_sspc(h, &h->prev_ss_state, cs))
return LWSSSSRET_DESTROY_ME;
if (!h->ssi.state)
return LWSSSSRET_OK;
h->h_in_svc = h;
ret = h->ssi.state((void *)((uint8_t *)(h + 1)), NULL, cs, flags);
h->h_in_svc = NULL;
return ret;
}
int
lws_sspc_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,
void *opaque_user_data, lws_sspc_handle_t **ppss,
void *reserved, const char **ppayload_fmt)
{
lws_sspc_handle_t *h;
uint8_t *ua;
char *p;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(context, tsi);
#endif
/* allocate the handle (including ssi), the user alloc,
* and the streamname */
h = malloc(sizeof(lws_sspc_handle_t) + ssi->user_alloc +
strlen(ssi->streamtype) + 1);
if (!h)
return 1;
memset(h, 0, sizeof(*h));
#if !defined(STANDALONE)
h->lc.log_cx = context->log_cx;
#endif
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_FAULT_INJECTION)
h->fic.name = "sspc";
lws_xos_init(&h->fic.xos, lws_xos(&context->fic.xos));
if (ssi->fic.fi_owner.count)
lws_fi_import(&h->fic, &ssi->fic);
lws_fi_inherit_copy(&h->fic, &context->fic, "ss", ssi->streamtype);
if (lws_fi(&h->fic, "sspc_create_oom")) {
/*
* We have to do this a little later, so we can cleanly inherit
* the OOM pieces and drain the info fic
*/
lws_fi_destroy(&h->fic);
free(h);
return 1;
}
#endif
#if !defined(STANDALONE)
__lws_lc_tag(context, &context->lcg[LWSLCG_SSP_CLIENT], &h->lc,
ssi->streamtype);
#else
snprintf(h->lc.gutag, sizeof(h->lc.gutag), "[sspc|%s|%x]",
ssi->streamtype,
(unsigned int)(context->ssidx++));
#endif
h->txp_path = context->txp_cpath;
h->txp_path.ops_in = &lws_txp_inside_sspc;
h->txp_path.priv_in = (lws_transport_priv_t)h;
/* priv_onw filled in by onw transport */
lwsl_sspc_info(h, "txp path %s -> %s", h->txp_path.ops_in->name,
h->txp_path.ops_onw->name);
memcpy(&h->ssi, ssi, sizeof(*ssi));
ua = (uint8_t *)(h + 1);
memset(ua, 0, ssi->user_alloc);
p = (char *)ua + ssi->user_alloc;
memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1);
h->ssi.streamtype = (const char *)p;
h->context = context;
h->us_start_upstream = lws_now_usecs();
if (!ssi->manual_initial_tx_credit)
h->txc.peer_tx_cr_est = 500000000;
else
h->txc.peer_tx_cr_est = ssi->manual_initial_tx_credit;
#if defined(LWS_WITH_NETWORK) && defined(LWS_WITH_SYS_SMD)
if (!strcmp(ssi->streamtype, LWS_SMD_STREAMTYPENAME))
h->ignore_txc = 1;
#endif
lws_dll2_add_head(&h->client_list, &context->
#if !defined(STANDALONE)
pt[tsi].
#endif
ss_client_owner);
/* fill in the things the real api does for the caller */
*((void **)(ua + ssi->opaque_user_data_offset)) = opaque_user_data;
*((void **)(ua + ssi->handle_offset)) = h;
if (ppss)
*ppss = h;
/* try the actual connect */
lws_sspc_sul_retry_cb(&h->sul_retry);
return 0;
}
/* used on context destroy when iterating listed lws_ss on a pt */
int
lws_sspc_destroy_dll(struct lws_dll2 *d, void *user)
{
lws_sspc_handle_t *h = lws_container_of(d, lws_sspc_handle_t,
client_list);
lws_sspc_destroy(&h);
return 0;
}
void
lws_sspc_rxmetadata_destroy(lws_sspc_handle_t *h)
{
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner_rx)) {
lws_sspc_metadata_t *md =
lws_container_of(d, lws_sspc_metadata_t, list);
lws_dll2_remove(&md->list);
lws_free(md);
} lws_end_foreach_dll_safe(d, d1);
}
void
lws_sspc_destroy(lws_sspc_handle_t **ph)
{
lws_sspc_handle_t *h;
if (!*ph)
return;
h = *ph;
if (h == h->h_in_svc) {
lwsl_err("%s: illegal destroy, return LWSSSSRET_DESTROY_ME instead\n",
__func__);
assert(0);
return;
}
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (h->destroying)
return;
h->destroying = 1;
/* if this caliper is still dangling at destroy, we failed */
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_METRICS)
/*
* If any hanging caliper measurement, dump it, and free any tags
*/
lws_metrics_caliper_report_hist(h->cal_txn, (struct lws *)NULL);
#endif
if (h->ss_dangling_connected && h->ssi.state) {
lws_sspc_event_helper(h, LWSSSCS_DISCONNECTED, 0);
h->ss_dangling_connected = 0;
}
#if !defined(STANDALONE) && defined(LWS_WITH_SYS_FAULT_INJECTION)
lws_fi_destroy(&h->fic);
#endif
lws_sul_cancel(&h->sul_retry);
lws_dll2_remove(&h->client_list);
if (h->dsh)
lws_dsh_destroy(&h->dsh);
h->txp_path.ops_onw->_close(h->txp_path.priv_onw);
/* clean out any pending metadata changes that didn't make it */
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&(*ph)->metadata_owner)) {
lws_sspc_metadata_t *md =
lws_container_of(d, lws_sspc_metadata_t, list);
lws_dll2_remove(&md->list);
lws_free(md);
} lws_end_foreach_dll_safe(d, d1);
lws_sspc_rxmetadata_destroy(h);
lws_sspc_event_helper(h, LWSSSCS_DESTROYING, 0);
*ph = NULL;
lws_sul_cancel(&h->sul_retry);
#if !defined(STANDALONE)
/* confirm no sul left scheduled in handle or user allocation object */
lws_sul_debug_zombies(h->context, h, sizeof(*h) + h->ssi.user_alloc,
__func__);
#endif
#if !defined(STANDALONE)
__lws_lc_untag(h->context, &h->lc);
#endif
free(h);
}
lws_ss_state_return_t
lws_sspc_request_tx(lws_sspc_handle_t *h)
{
if (!h || !h->txp_path.priv_onw)
return LWSSSSRET_OK;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (!h->us_earliest_write_req)
h->us_earliest_write_req = lws_now_usecs();
lwsl_info("%s: state %u, conn_req_state %u\n", __func__,
(unsigned int)h->state,
(unsigned int)h->conn_req_state);
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
/*
* Currently we fulfil the writeable part locally by just enabling POLLOUT on
* the UDS link, without serialization footprint, which is reasonable as far as
* it goes.
*
* But for the ..._len() variant, the expected payload length hint we are being
* told is something that must be serialized to the onward peer, since either
* that guy or someone upstream of him is the guy who will compose the framing
* with it that actually goes out.
*
* This information is needed at the upstream guy before we have sent any
* payload, eg, for http POST, he has to prepare the content-length in the
* headers, before any payload. So we have to issue a serialization of the
* length at this point.
*/
lws_ss_state_return_t
lws_sspc_request_tx_len(lws_sspc_handle_t *h, unsigned long len)
{
/*
* for client conns, they cannot even complete creation of the handle
* without the onwared connection to the proxy, it's not legal to start
* using it until it's operation and has the onward connection (and the
* link has called CREATED state)
*/
if (!h)
return LWSSSSRET_OK;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lwsl_sspc_notice(h, "setting writeable_len %u", (unsigned int)len);
h->writeable_len = len;
h->pending_writeable_len = 1;
if (!h->us_earliest_write_req)
h->us_earliest_write_req = lws_now_usecs();
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
/*
* We're going to use this up with serializing h->writeable_len... that
* will request again.
*/
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return LWSSSSRET_OK;
}
lws_ss_state_return_t
lws_sspc_client_connect(struct lws_sspc_handle *h)
{
if (!h || h->state == LPCSCLI_OPERATIONAL)
return 0;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
assert(h->state == LPCSCLI_LOCAL_CONNECTED);
if (h->state == LPCSCLI_LOCAL_CONNECTED &&
h->conn_req_state == LWSSSPC_ONW_NONE)
h->conn_req_state = LWSSSPC_ONW_REQ;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return 0;
}
struct lws_context *
lws_sspc_get_context(struct lws_sspc_handle *h)
{
return h->context;
}
const char *
lws_sspc_rideshare(struct lws_sspc_handle *h)
{
/*
* ...the serialized RX rideshare name if any...
*/
if (h->parser.rideshare[0]) {
lwsl_sspc_info(h, "parser %s", h->parser.rideshare);
return h->parser.rideshare;
}
/*
* The tx rideshare index
*/
if (h->rideshare_list[0]) {
lwsl_sspc_info(h, "tx list %s",
&h->rideshare_list[h->rideshare_ofs[h->rsidx]]);
return &h->rideshare_list[h->rideshare_ofs[h->rsidx]];
}
/*
* ... otherwise default to our stream type name
*/
lwsl_sspc_info(h, "def %s\n", h->ssi.streamtype);
return h->ssi.streamtype;
}
static int
_lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
const void *value, size_t len, int tx_cr_adjust)
{
lws_sspc_metadata_t *md;
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
/*
* Are we replacing a pending metadata of the same name? It's not
* efficient to do this but user code can do what it likes... let's
* optimize away the old one.
*
* Tx credit adjust always has name ""
*/
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner)) {
md = lws_container_of(d, lws_sspc_metadata_t, list);
if (!strcmp(name, md->name)) {
lws_dll2_remove(&md->list);
lws_free(md);
break;
}
} lws_end_foreach_dll_safe(d, d1);
/*
* We have to stash the metadata and pass it to the proxy
*/
#if !defined(STANDALONE)
if (lws_fi(&h->fic, "sspc_fail_metadata_set"))
md = NULL;
else
#endif
md = lws_malloc(sizeof(*md) + len, "set metadata");
if (!md) {
lwsl_sspc_err(h, "OOM");
return 1;
}
memset(md, 0, sizeof(*md));
md->tx_cr_adjust = tx_cr_adjust;
h->txc.peer_tx_cr_est += tx_cr_adjust;
lws_strncpy(md->name, name, sizeof(md->name));
md->len = len;
if (len)
memcpy(&md[1], value, len);
lws_dll2_add_tail(&md->list, &h->metadata_owner);
if (len) {
#if !defined(STANDALONE)
lwsl_sspc_info(h, "set metadata %s", name);
lwsl_hexdump_sspc_info(h, value, len);
#endif
} else
lwsl_sspc_info(h, "serializing tx cr adj %d",
(int)tx_cr_adjust);
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
return 0;
}
void
lws_sspc_server_ack(struct lws_sspc_handle *h, int nack)
{
//h->txn_resp = nack;
//h->txn_resp_set = 1;
}
int
lws_sspc_set_metadata(struct lws_sspc_handle *h, const char *name,
const void *value, size_t len)
{
return _lws_sspc_set_metadata(h, name, value, len, 0);
}
int
lws_sspc_get_metadata(struct lws_sspc_handle *h, const char *name,
const void **value, size_t *len)
{
lws_sspc_metadata_t *md;
/*
* client side does not have access to policy
* and any metadata are new to it each time,
* we allocate them, removing any existing with
* the same name first
*/
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(&h->metadata_owner_rx)) {
md = lws_container_of(d,
lws_sspc_metadata_t, list);
if (!strcmp(md->name, name)) {
*len = md->len;
*value = &md[1];
return 0;
}
} lws_end_foreach_dll_safe(d, d1);
return 1;
}
int
lws_sspc_add_peer_tx_credit(struct lws_sspc_handle *h, int32_t bump)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
lwsl_sspc_notice(h, "%d\n", (int)bump);
return _lws_sspc_set_metadata(h, "", NULL, 0, (int)bump);
}
int
lws_sspc_get_est_peer_tx_credit(struct lws_sspc_handle *h)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
return h->txc.peer_tx_cr_est;
}
void
lws_sspc_start_timeout(struct lws_sspc_handle *h, unsigned int timeout_ms)
{
#if !defined(STANDALONE)
lws_service_assert_loop_thread(h->context, 0);
#endif
if (!h->txp_path.priv_onw)
/* we can't fulfil it */
return;
h->timeout_ms = (uint32_t)timeout_ms;
h->pending_timeout_update = 1;
h->txp_path.ops_onw->req_write(h->txp_path.priv_onw);
}
void
lws_sspc_cancel_timeout(struct lws_sspc_handle *h)
{
lws_sspc_start_timeout(h, (unsigned int)-1);
}
void *
lws_sspc_to_user_object(struct lws_sspc_handle *h)
{
return (void *)(h + 1);
}
struct lws_log_cx *
lwsl_sspc_get_cx(struct lws_sspc_handle *sspc)
{
if (!sspc)
return NULL;
return sspc->lc.log_cx;
}
void
lws_log_prepend_sspc(struct lws_log_cx *cx, void *obj, char **p, char *e)
{
struct lws_sspc_handle *h = (struct lws_sspc_handle *)obj;
#if defined(STANDALONE)
snprintf(*p, lws_ptr_diff_size_t(e, (*p)), "%s: ", h->lc.gutag);
#else
*p += lws_snprintf(*p, lws_ptr_diff_size_t(e, (*p)), "%s: ",
lws_sspc_tag(h));
#endif
}
void
lws_sspc_change_handlers(struct lws_sspc_handle *h, lws_sscb_rx rx,
lws_sscb_tx tx, lws_sscb_state state)
{
if (rx)
h->ssi.rx = rx;
if (tx)
h->ssi.tx = tx;
if (state)
h->ssi.state = state;
}
const char *
lws_sspc_tag(struct lws_sspc_handle *h)
{
if (!h)
return "[null sspc]";
#if defined(STANDALONE)
return h->lc.gutag;
#else
return lws_lc_tag(&h->lc);
#endif
}
int
lws_sspc_cancel_notify_dll(struct lws_dll2 *d, void *user)
{
lws_sspc_handle_t *h = lws_container_of(d, lws_sspc_handle_t,
client_list);
lws_sspc_event_helper(h, LWSSSCS_EVENT_WAIT_CANCELLED, 0);
return 0;
}

View File

@ -0,0 +1,853 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 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.
*
*
* Serialized Secure Streams deserializer for Proxy side
*/
#include <private-lib-core.h>
/*
* event loop is consuming dsh-buffered, already-serialized tx from the
* foreign side
*/
int
lws_ss_deserialize_tx_payload(struct lws_dsh *dsh, struct lws *wsi,
lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
uint8_t *p;
size_t si;
if (lws_dsh_get_head(dsh, KIND_C_TO_P, (void **)&p, &si)) {
*len = 0;
return 0;
}
/*
* The packet in the dsh has a proxying serialization header, process
* and strip it so we just forward the payload
*/
if (*len <= si - 23 || si < 23) {
/*
* What comes out of the dsh needs to fit in the tx buffer...
* we have arrangements at the proxy rx of the client UDS to
* chop chunks larger than 1380 into seuqential lumps of 1380
*/
lwsl_err("%s: *len = %d, si = %d\n", __func__, (int)*len, (int)si);
assert(0);
return 1;
}
if (p[0] != LWSSS_SER_TXPRE_TX_PAYLOAD) {
assert(0);
return 1;
}
*len = (size_t)(lws_ser_ru16be(&p[1]) - (23 - 3));
if (*len != si - 23) {
/*
* We cannot accept any length that doesn't reflect the actual
* length of what came in from the dsh, either something nasty
* happened with truncation or we are being attacked
*/
assert(0);
return 1;
}
memcpy(buf, p + 23, si - 23);
*flags = (int)lws_ser_ru32be(&p[3]);
lws_dsh_free((void **)&p);
return 0;
}
/*
* event loop side is consuming serialized data from the client via dsh, parse
* it using a bytewise parser for the serialization header(s)...
* it's possibly coalesced
*
* client: pss is pointing to the start of userdata. We can use
* pss_to_sspc_h(_pss, _ssi) to convert that to a pointer to the sspc
* handle
*
* proxy: pss is pointing to &conn->ss, a pointer to the ss handle
*
* Returns one of
*
* LWSSSSRET_OK
* LWSSSSRET_DISCONNECT_ME
* LWSSSSRET_DESTROY_ME
*/
/* convert userdata ptr _pss to handle pointer, allowing for any layout in
* userdata */
#define client_pss_to_sspc_h(_pss, _ssi) (*((lws_sspc_handle_t **) \
((uint8_t *)_pss) + _ssi->handle_offset))
/* client pss to sspc userdata */
#define client_pss_to_userdata(_pss) ((void *)_pss)
/* proxy convert pss to ss handle */
#define proxy_pss_to_ss_h(_pss) (*_pss)
/* convert userdata ptr _pss to handle pointer, allowing for any layout in
* userdata */
#define client_pss_to_sspc_h(_pss, _ssi) (*((lws_sspc_handle_t **) \
((uint8_t *)_pss) + _ssi->handle_offset))
/* client pss to sspc userdata */
#define client_pss_to_userdata(_pss) ((void *)_pss)
/* proxy convert pss to ss handle */
#define proxy_pss_to_ss_h(_pss) (*_pss)
int
lws_ss_proxy_deserialize_parse(struct lws_ss_serialization_parser *par,
struct lws_context *context,
struct lws_dsh *dsh, const uint8_t *cp,
size_t len, lws_ss_conn_states_t *state,
void *parconn, lws_ss_handle_t **pss,
lws_ss_info_t *ssi)
{
lws_ss_state_return_t r;
lws_ss_metadata_t *pm;
uint8_t pre[23];
uint32_t flags;
lws_usec_t us;
uint8_t *p;
int n;
while (len--) {
switch (par->ps) {
case RPAR_TYPE:
par->type = *cp++;
par->ps++;
break;
case RPAR_LEN_MSB: /* this is remaining frame length */
par->rem = (uint16_t)((*cp++) << 8);
par->ps++;
break;
case RPAR_LEN_LSB:
par->rem = (uint16_t)(par->rem | *cp++);
switch (par->type) {
/* event loop side */
case LWSSS_SER_TXPRE_TX_PAYLOAD:
if (*state != LPCSPROX_OPERATIONAL)
goto hangup;
par->ps = RPAR_FLAG_B3;
break;
case LWSSS_SER_TXPRE_DESTROYING:
par->ps = RPAR_TYPE;
lwsl_cx_notice(context, "DESTROYING");
goto hangup;
case LWSSS_SER_TXPRE_ONWARD_CONNECT:
if (*state != LPCSPROX_OPERATIONAL)
goto hangup;
par->ps = RPAR_TYPE;
lwsl_cx_notice(context, "ONWARD_CONNECT");
/*
* Shrug it off if we are already connecting or
* connected
*/
if (!proxy_pss_to_ss_h(pss) ||
proxy_pss_to_ss_h(pss)->wsi)
break;
/*
* We're going to try to do the onward connect
*/
if ((proxy_pss_to_ss_h(pss) &&
lws_fi(&proxy_pss_to_ss_h(pss)->fic,
"ssproxy_onward_conn_fail")) ||
_lws_ss_client_connect(proxy_pss_to_ss_h(pss),
0, parconn) ==
LWSSSSRET_DESTROY_ME)
goto hangup;
break;
case LWSSS_SER_TXPRE_STREAMTYPE:
if (*state != LPCSPROX_WAIT_INITIAL_TX)
goto hangup;
if (par->rem < 1 + 4 + 1)
goto hangup;
par->ps = RPAR_INIT_PROVERS;
break;
case LWSSS_SER_TXPRE_METADATA:
if (par->rem < 3)
goto hangup;
par->ctr = 0;
par->ps = RPAR_METADATA_NAMELEN;
break;
case LWSSS_SER_TXPRE_TXCR_UPDATE:
par->ps = RPAR_TXCR0;
par->ctr = 0;
break;
case LWSSS_SER_TXPRE_TIMEOUT_UPDATE:
if (par->rem != 4)
goto hangup;
par->ps = RPAR_TIMEOUT0;
par->ctr = 0;
break;
case LWSSS_SER_TXPRE_PAYLOAD_LENGTH_HINT:
if (par->rem != 4)
goto hangup;
par->ps = RPAR_PAYLEN0;
par->ctr = 0;
break;
/* client side */
case LWSSS_SER_RXPRE_RX_PAYLOAD:
case LWSSS_SER_RXPRE_CREATE_RESULT:
case LWSSS_SER_RXPRE_CONNSTATE:
case LWSSS_SER_RXPRE_METADATA:
goto hangup;
case LWSSS_SER_RXPRE_TXCR_UPDATE:
par->ctr = 0;
par->ps = RPAR_RX_TXCR_UPDATE;
break;
case LWSSS_SER_RXPRE_PERF:
par->ctr = 0;
if (!par->rem)
goto hangup;
par->ps = RPAR_PERF;
break;
default:
lwsl_cx_notice(context, "bad type 0x%x",
par->type);
goto hangup;
}
break;
case RPAR_FLAG_B3:
case RPAR_FLAG_B2:
case RPAR_FLAG_B1:
case RPAR_FLAG_B0:
par->flags <<= 8;
par->flags |= *cp++;
par->ps++;
if (!par->rem--)
goto hangup;
break;
case RPAR_LATA3:
case RPAR_LATA2:
case RPAR_LATA1:
case RPAR_LATA0:
par->usd_phandling <<= 8;
par->usd_phandling |= *cp++;
par->ps++;
if (!par->rem--)
goto hangup;
break;
case RPAR_LATB7:
case RPAR_LATB6:
case RPAR_LATB5:
case RPAR_LATB4:
case RPAR_LATB3:
case RPAR_LATB2:
case RPAR_LATB1:
case RPAR_LATB0:
par->ust_pwait <<= 8;
par->ust_pwait |= *cp++;
par->ps++;
par->frag1 = 1;
if (!par->rem--)
goto hangup;
if (par->ps == RPAR_RIDESHARE_LEN &&
!(par->flags & LWSSS_FLAG_RIDESHARE))
par->ps = RPAR_PAYLOAD;
if (par->rem)
break;
/* fallthru - handle 0-length payload */
if (!(par->flags & LWSSS_FLAG_RIDESHARE))
goto payload_ff;
goto hangup;
/*
* Inbound rideshare info is provided on the RX packet
* itself
*/
case RPAR_RIDESHARE_LEN:
par->slen = *cp++;
par->ctr = 0;
par->ps++;
if (par->rem-- < par->slen)
goto hangup;
break;
case RPAR_PERF:
n = (int)len + 1;
if (n > par->rem)
n = par->rem;
if (n) {
cp += n;
par->rem = (uint16_t)(par->rem -
(uint16_t)(unsigned int)n);
len = (len + 1) - (unsigned int)n;
}
if (!par->rem)
par->ps = RPAR_TYPE;
break;
case RPAR_RIDESHARE:
par->rideshare[par->ctr++] = (char)*cp++;
if (!par->rem--)
goto hangup;
if (par->ctr != par->slen)
break;
par->ps = RPAR_PAYLOAD;
if (par->rem)
break;
/* fallthru - handle 0-length payload */
case RPAR_PAYLOAD:
payload_ff:
n = (int)len + 1;
if (n > par->rem)
n = par->rem;
/*
* We get called with a serialized buffer of a size
* chosen by the client. We can only create dsh entries
* with up to 1380 payload, to guarantee we can emit
* them on the onward connection atomically.
*
* If 1380 isn't enough to cover what was handed to us,
* we'll stop at 1380 and go around again and create
* more dsh entries for the rest, with their own
* headers.
*/
if (n > 1380)
n = 1380;
/*
* Since we're in the business of fragmenting client
* serialized payloads at 1380, we have to deal with
* refragmenting the SOM / EOM flags that covered the
* whole client serialized packet, so they apply to
* each dsh entry we split it into correctly
*/
flags = par->flags & LWSSS_FLAG_RELATED_START;
if (par->frag1)
/*
* Only set the first time we came to this
* state after deserialization of the header
*/
flags |= par->flags &
(LWSSS_FLAG_SOM | LWSSS_FLAG_POLL);
if (par->rem == n)
/*
* We are going to complete the advertised
* payload length from the client on this dsh,
* so give him the EOM type flags if any
*/
flags |= par->flags & (LWSSS_FLAG_EOM |
LWSSS_FLAG_RELATED_END);
par->frag1 = 0;
us = lws_now_usecs();
{
lws_ss_handle_t *hss;
/*
* Proxy - we received some serialized tx from
* the client.
*
* The header for buffering private to the
* proxy is 23 bytes vs 19, so we can hold the
* current time when it was buffered
* additionally
*/
hss = proxy_pss_to_ss_h(pss);
if (hss)
lwsl_ss_info(hss, "C2P RX: len %d", (int)n);
p = pre;
pre[0] = LWSSS_SER_TXPRE_TX_PAYLOAD;
lws_ser_wu16be(&p[1], (uint16_t)((unsigned int)n + 23 - 3));
lws_ser_wu32be(&p[3], flags);
/* us held at client before written */
lws_ser_wu32be(&p[7], par->usd_phandling);
/* us taken for transit to proxy */
lws_ser_wu32be(&p[11], (uint32_t)(us -
(lws_usec_t)par->ust_pwait));
/* time used later to find proxy hold time */
lws_ser_wu64be(&p[15], (uint64_t)us);
if ((hss &&
lws_fi(&hss->fic, "ssproxy_dsh_c2p_pay_oom")) ||
lws_dsh_alloc_tail(dsh, KIND_C_TO_P, pre,
23, cp, (unsigned int)n)) {
lwsl_ss_err(hss, "unable to alloc in dsh 3");
return LWSSSSRET_DISCONNECT_ME;
}
lwsl_notice("%s: dsh c2p %d, p2c %d\n", __func__,
(int)lws_dsh_get_size(dsh, KIND_C_TO_P),
(int)lws_dsh_get_size(dsh, 1));
if (hss)
_lws_ss_request_tx(hss);
}
if (n) {
cp += n;
par->rem = (uint16_t)(par->rem -
(uint16_t)(unsigned int)n);
len = (len + 1) - (unsigned int)n;
/*
* if we didn't consume it all, we'll come
* around again and produce more dsh entries up
* to 1380 each until it is gone
*/
}
if (!par->rem)
par->ps = RPAR_TYPE;
break;
case RPAR_RX_TXCR_UPDATE:
goto hangup;
case RPAR_INIT_PROVERS:
/* Protocol version byte for this connection */
par->protocol_version = *cp++;
/*
* So we have to know what versions of the serialization
* protocol we can support at the proxy side, and
* reject anythng we don't know how to deal with
* noisily in the logs.
*/
if (par->protocol_version != 1) {
lwsl_err("%s: Rejecting client with "
"unsupported SSv%d protocol\n",
__func__, par->protocol_version);
goto hangup;
}
if (!--par->rem)
goto hangup;
par->ctr = 0;
par->ps = RPAR_INIT_PID;
break;
case RPAR_INIT_PID:
if (!--par->rem)
goto hangup;
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4)
break;
par->client_pid = (uint32_t)par->temp32;
par->ctr = 0;
par->ps = RPAR_INITTXC0;
break;
case RPAR_INITTXC0:
if (!--par->rem)
goto hangup;
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4)
break;
par->txcr_out = par->temp32;
par->ctr = 0;
par->ps = RPAR_STREAMTYPE;
break;
/*
* These are the client adjusting our / the remote peer ability
* to send back to him. He's sending a signed u32 BE
*/
case RPAR_TXCR0:
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4) {
if (!--par->rem)
goto hangup;
break;
}
if (--par->rem)
goto hangup;
/*
* We're the proxy, being told by the client
* that it wants to allow more tx from the peer
* on the onward connection towards it.
*/
#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)
if (proxy_pss_to_ss_h(pss) &&
proxy_pss_to_ss_h(pss)->wsi) {
lws_wsi_tx_credit(
proxy_pss_to_ss_h(pss)->wsi,
LWSTXCR_PEER_TO_US,
par->temp32);
lwsl_notice("%s: proxy RX_PEER_TXCR: +%d (est %d)\n",
__func__, par->temp32,
proxy_pss_to_ss_h(pss)->wsi->
txc.peer_tx_cr_est);
_lws_ss_request_tx(proxy_pss_to_ss_h(pss));
} else
#endif
lwsl_info("%s: dropping TXCR\n", __func__);
par->ps = RPAR_TYPE;
break;
case RPAR_TIMEOUT0:
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4) {
if (!--par->rem)
goto hangup;
break;
}
if (--par->rem)
goto hangup;
/*
* Proxy...
*
* *pss may have gone away asynchronously inbetweentimes
*/
if (proxy_pss_to_ss_h(pss)) {
if ((unsigned int)par->temp32 == 0xffffffff) {
lwsl_notice("%s: cancel ss timeout\n",
__func__);
lws_ss_cancel_timeout(
proxy_pss_to_ss_h(pss));
} else {
if (!par->temp32)
par->temp32 = (int)
proxy_pss_to_ss_h(pss)->
policy->timeout_ms;
lwsl_notice("%s: set ss timeout for +%ums\n",
__func__, par->temp32);
lws_ss_start_timeout(
proxy_pss_to_ss_h(pss),
(unsigned int)par->temp32);
}
}
par->ps = RPAR_TYPE;
break;
case RPAR_PAYLEN0:
/*
* It's the length from lws_ss_request_tx_len() being
* passed up to the proxy
*/
par->temp32 = (par->temp32 << 8) | *cp++;
if (++par->ctr < 4) {
if (!--par->rem)
goto hangup;
break;
}
if (--par->rem)
goto hangup;
lwsl_notice("%s: set payload len %u\n", __func__,
par->temp32);
par->ps = RPAR_TYPE;
if (proxy_pss_to_ss_h(pss)) {
r = lws_ss_request_tx_len(proxy_pss_to_ss_h(pss),
(unsigned long)par->temp32);
if (r == LWSSSSRET_DESTROY_ME)
goto hangup;
}
break;
case RPAR_METADATA_NAMELEN:
/* both client and proxy */
if (!--par->rem)
goto hangup;
par->slen = *cp++;
if (par->slen >= sizeof(par->metadata_name) - 1)
goto hangup;
par->ctr = 0;
par->ps++;
break;
case RPAR_METADATA_NAME:
/* both client and proxy */
if (!--par->rem)
goto hangup;
par->metadata_name[par->ctr++] = (char)*cp++;
if (par->ctr != par->slen)
break;
par->metadata_name[par->ctr] = '\0';
par->ps = RPAR_METADATA_VALUE;
/* proxy side is receiving it */
if (!proxy_pss_to_ss_h(pss))
goto hangup;
if (!proxy_pss_to_ss_h(pss)->policy) {
lwsl_err("%s: null policy\n", __func__);
goto hangup;
}
/*
* This is the policy's metadata list for the given
* name
*/
pm = lws_ss_policy_metadata(
proxy_pss_to_ss_h(pss)->policy,
par->metadata_name);
if (!pm) {
lwsl_err("%s: metadata %s not in proxy policy\n",
__func__, par->metadata_name);
goto hangup;
}
par->ssmd = lws_ss_get_handle_metadata(
proxy_pss_to_ss_h(pss),
par->metadata_name);
if (par->ssmd) {
if (par->ssmd->value_on_lws_heap)
lws_free_set_NULL(par->ssmd->value__may_own_heap);
par->ssmd->value_on_lws_heap = 0;
if (proxy_pss_to_ss_h(pss) &&
lws_fi(&proxy_pss_to_ss_h(pss)->fic, "ssproxy_rx_metadata_oom"))
par->ssmd->value__may_own_heap = NULL;
else
par->ssmd->value__may_own_heap =
lws_malloc((unsigned int)par->rem + 1, "metadata");
if (!par->ssmd->value__may_own_heap) {
lwsl_err("%s: OOM mdv\n", __func__);
goto hangup;
}
par->ssmd->length = par->rem;
((uint8_t *)par->ssmd->value__may_own_heap)[par->rem] = '\0';
/* mark it as needing cleanup */
par->ssmd->value_on_lws_heap = 1;
}
par->ctr = 0;
break;
case RPAR_METADATA_VALUE:
/* both client and proxy */
if (!par->ssmd) {
/* we don't recognize the name */
cp++;
if (--par->rem)
break;
par->ps = RPAR_TYPE;
break;
}
((uint8_t *)(par->ssmd->value__may_own_heap))[par->ctr++] = *cp++;
if (--par->rem)
break;
/* we think we got all the value */
lwsl_ss_info(proxy_pss_to_ss_h(pss),
"RPAR_METADATA_VALUE for %s (len %d)",
par->ssmd->name,
(int)par->ssmd->length);
lwsl_hexdump_ss_info(proxy_pss_to_ss_h(pss),
par->ssmd->value__may_own_heap,
par->ssmd->length);
par->ps = RPAR_TYPE;
break;
case RPAR_STREAMTYPE:
/* only the proxy can get these */
if (par->ctr == sizeof(par->streamtype) - 1)
goto hangup;
/*
* We can only expect to get this if we ourselves are
* in the state that we're waiting for it. If it comes
* later it's a protocol error.
*/
if (*state != LPCSPROX_WAIT_INITIAL_TX)
goto hangup;
/*
* We're the proxy, creating an SS on behalf of a
* client
*/
par->streamtype[par->ctr++] = (char)*cp++;
if (--par->rem)
break;
par->ps = RPAR_TYPE;
par->streamtype[par->ctr] = '\0';
lwsl_info("%s: proxy ss '%s', sssv%d, txcr %d\n",
__func__, par->streamtype,
par->protocol_version, par->txcr_out);
ssi->streamtype = par->streamtype;
if (par->txcr_out) // !!!
ssi->manual_initial_tx_credit = par->txcr_out;
/*
* Even for a synthetic SS proxing action like _lws_smd,
* we create an actual SS in the proxy representing the
* connection
*/
ssi->flags |= LWSSSINFLAGS_PROXIED;
ssi->sss_protocol_version = par->protocol_version;
ssi->client_pid = par->client_pid;
if (lws_ss_create(context, 0, ssi, parconn, pss,
NULL, NULL)) {
/*
* We're unable to create the onward secure
* stream he asked for... schedule a chance to
* inform him
*/
lwsl_err("%s: create '%s' fail\n", __func__,
par->streamtype);
*state = LPCSPROX_REPORTING_FAIL;
break;
} else {
lwsl_debug("%s: create '%s' OK\n",
__func__, par->streamtype);
*state = LPCSPROX_REPORTING_OK;
}
if (*pss) {
(*pss)->being_serialized = 1;
#if defined(LWS_WITH_SYS_SMD)
if ((*pss)->policy != &pol_smd)
/*
* In SMD case we overloaded the
* initial credit to be the class mask
*/
#endif
{
lwsl_info("%s: Created SS initial credit %d\n",
__func__, par->txcr_out);
(*pss)->info.manual_initial_tx_credit = par->txcr_out;
}
}
/* parent needs to schedule write on client conn */
break;
/* clientside states */
case RPAR_RESULT_CREATION:
case RPAR_RESULT_CREATION_RIDESHARE:
case RPAR_RESULT_CREATION_DSH:
case RPAR_STATEINDEX:
case RPAR_ORD3:
case RPAR_ORD2:
case RPAR_ORD1:
case RPAR_ORD0:
goto hangup;
default:
goto hangup;
}
}
return LWSSSSRET_OK;
hangup:
lwsl_cx_notice(context, "hangup");
return LWSSSSRET_DISCONNECT_ME;
}

View File

@ -0,0 +1,291 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 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.
*
*
* Proxy side of Client <-> Proxy wsi connection, usually on Unix Domain Socket
*/
#include <private-lib-core.h>
struct raw_pss {
struct lws_sss_proxy_conn *conn;
};
static int
lws_sss_proxy_transport_wsi_cb(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
struct raw_pss *pss = (struct raw_pss *)user;
struct lws_sss_proxy_conn *conn = NULL;
if (pss)
conn = pss->conn;
switch (reason) {
/* callbacks related to raw socket descriptor "accepted side" */
case LWS_CALLBACK_RAW_ADOPT:
lwsl_user("LWS_CALLBACK_RAW_ADOPT %s\n", lws_txp_inside_proxy.name);
if (lws_txp_inside_proxy.event_new_conn(
wsi->a.context,
&lws_txp_inside_proxy,
(lws_transport_priv_t)conn,
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
&wsi->fic,
#endif
&pss->conn,
(lws_transport_priv_t)wsi)) {
lwsl_err("%s: hangup from new_conn\n", __func__);
return -1;
}
/* dsh is allocated when the onward ss is done */
wsi->bound_ss_proxy_conn = 1; /* opaque is conn */
lws_set_opaque_user_data(wsi, pss->conn);
pss->conn->state = LPCSPROX_WAIT_INITIAL_TX;
/*
* Client is expected to follow the unix domain socket
* acceptance up rapidly with an initial tx containing the
* streamtype name. We can't create the stream until then.
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, 3);
lwsl_user("%s: ADOPT: accepted\n", __func__);
break;
case LWS_CALLBACK_RAW_CLOSE:
lwsl_info("LWS_CALLBACK_RAW_CLOSE:\n");
if (!conn)
break;
/*
* the client unix domain socket connection (wsi / conn->wsi)
* has closed... eg, client has exited or otherwise has
* definitively finished with the proxying and onward connection
*
* But right now, the SS and possibly the SS onward wsi are
* still live...
*/
assert(conn->txp_path.priv_onw == wsi);
// if (conn->ss)
// conn->ss = NULL;
/* sever relationship with conn */
lws_set_opaque_user_data(wsi, NULL);
lws_txp_inside_proxy.event_close_conn(conn);
/* pss is about to be deleted */
if (pss)
pss->conn = NULL;
lwsl_notice("%s: close finished ok\n", __func__);
break;
case LWS_CALLBACK_RAW_RX:
/*
* ie, the proxy is receiving something from a client
*/
lwsl_info("%s: RX: rx %d\n", __func__, (int)len);
if (!conn) {
lwsl_err("%s: rx with conn %p / priv_in %p\n", __func__,
conn, conn->txp_path.priv_in);
return -1;
}
if (conn->txp_path.ops_in->proxy_read(
conn, in, len))
return -1;
break;
case LWS_CALLBACK_RAW_WRITEABLE:
lwsl_debug("%s: %s: LWS_CALLBACK_RAW_WRITEABLE, state 0x%x\n",
__func__, lws_wsi_tag(wsi), lwsi_state(wsi));
/*
* We can transmit something back to the client from the dsh
* of stuff we received on its behalf from the ss
*/
if (!conn)
break;
assert_is_conn(conn);
if (lws_txp_inside_proxy.event_proxy_can_write(conn
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
, &wsi->fic
#endif
))
return -1;
break;
default:
break;
}
return lws_callback_http_dummy(wsi, reason, user, in, len);
}
static const struct lws_protocols protocols[] = {
{
"ssproxy-protocol",
lws_sss_proxy_transport_wsi_cb,
sizeof(struct raw_pss),
2048, 2048, NULL, 0
},
{ NULL, NULL, 0, 0, 0, NULL, 0 }
};
static void
lws_sss_proxy_wsi_onward_bind(lws_transport_priv_t priv, lws_ss_handle_t *h)
{
struct lws *wsi = (struct lws *)priv;
__lws_lc_tag_append(&wsi->lc, lws_ss_tag(h));
}
static void
lws_sss_proxy_wsi_req_write(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (wsi)
lws_callback_on_writable(wsi);
}
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
static const lws_fi_ctx_t *
lws_sss_proxy_wsi_fault_context(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (!wsi)
return NULL;
return &wsi->fic;
}
#endif
static int
lws_sss_proxy_wsi_write(lws_transport_priv_t priv, uint8_t *buf, size_t *len)
{
struct lws *wsi = (struct lws *)priv;
if (lws_write(wsi, buf, *len, LWS_WRITE_RAW) != (ssize_t)*len) {
lwsl_wsi_notice(wsi, "failed");
return -1;
}
/* leave *len alone */
return 0;
}
int
lws_sss_proxy_wsi_init_proxy_server(struct lws_context *context,
const struct lws_transport_proxy_ops *txp_ops_inward,
lws_transport_priv_t txp_priv_inward,
lws_txp_path_proxy_t *txp_ppath,
const void *txp_info,
const char *bind, int port)
{
struct lws_context_creation_info info;
memset(&info, 0, sizeof(info));
info.vhost_name = "ssproxy";
info.options = LWS_SERVER_OPTION_ADOPT_APPLY_LISTEN_ACCEPT_CONFIG |
LWS_SERVER_OPTION_SS_PROXY;
info.port = port;
if (!port) {
if (!bind)
#if defined(__linux__)
bind = "@proxy.ss.lws";
#else
bind = "/tmp/proxy.ss.lws";
#endif
info.options |= LWS_SERVER_OPTION_UNIX_SOCK;
}
info.iface = bind;
#if defined(__linux__)
info.unix_socket_perms = "root:root";
#else
#endif
info.listen_accept_role = "raw-skt";
info.listen_accept_protocol = "ssproxy-protocol";
info.protocols = protocols;
if (!lws_create_vhost(context, &info)) {
lwsl_err("%s: Failed to create ss proxy vhost\n", __func__);
return 1;
}
return 0;
}
static void
lws_sss_proxy_wsi_client_up(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
lws_set_timeout(wsi, 0, 0);
}
static int
lws_sss_proxy_check_write_more(lws_transport_priv_t priv)
{
struct lws *wsi = (struct lws *)priv;
if (lws_send_pipe_choked(wsi))
return 0;
return 1;
}
const lws_transport_proxy_ops_t txp_ops_ssproxy_wsi = {
.name = "txp_proxy_wsi",
.init_proxy_server = lws_sss_proxy_wsi_init_proxy_server,
.proxy_req_write = lws_sss_proxy_wsi_req_write,
.proxy_write = lws_sss_proxy_wsi_write,
.event_onward_bind = lws_sss_proxy_wsi_onward_bind,
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
.fault_context = lws_sss_proxy_wsi_fault_context,
#endif
.event_client_up = lws_sss_proxy_wsi_client_up,
.proxy_check_write_more = lws_sss_proxy_check_write_more,
};

View File

@ -0,0 +1,423 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 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.
*/
#include <private-lib-core.h>
/*
* Proxy has received a new connection from a client
*/
static lws_ss_state_return_t
lws_ssproxy_txp_new_conn(struct lws_context *cx,
const struct lws_transport_proxy_ops *txp_ops_inward,
lws_transport_priv_t txp_priv_inward,
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
const lws_fi_ctx_t *fic,
#endif
struct lws_sss_proxy_conn **conn,
lws_transport_priv_t txp_priv)
{
if (
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
fic &&
#endif
lws_fi(fic, "ssproxy_client_adopt_oom"))
*conn = NULL;
else
*conn = lws_zalloc(sizeof(**conn), __func__);
if (!*conn)
return 1;
/* dsh is allocated when the onward ss is done */
#if defined(_DEBUG)
(*conn)->magic = LWS_PROXY_CONN_MAGIC;
#endif
(*conn)->state = LPCSPROX_WAIT_INITIAL_TX;
(*conn)->txp_path = cx->txp_ppath;
(*conn)->txp_path.priv_onw = txp_priv;
(*conn)->txp_path.ops_in = txp_ops_inward;
(*conn)->txp_path.priv_in = txp_priv_inward;
(*conn)->cx = cx;
return LWSSSSRET_OK;
}
/*
* Proxy has received a close indication from a client
*/
static lws_ss_state_return_t
lws_ssproxy_txp_close_conn(struct lws_sss_proxy_conn *conn)
{
lws_transport_priv_t epriv;
conn->txp_path.priv_onw = NULL;
epriv = conn->txp_path.priv_onw;
/*
* If there's an outgoing, proxied SS conn on our behalf, we
* have to destroy it
*
* Wsi related stuff in here is talking about the onward wsi / ss
* connection, it doesn't introduce any dependency on the proxy -
* client link transport
*/
if (conn->ss) {
struct lws *cw;
cw = conn->ss->wsi;
/*
* conn->ss is the onward connection SS
*/
lwsl_info("%s: destroying %s, wsi %s\n",
__func__, lws_ss_tag(conn->ss),
lws_wsi_tag(conn->ss->wsi));
/* sever conn relationship with onward ss about to be deleted */
conn->ss->wsi = NULL;
if (cw && epriv != (lws_transport_priv_t)cw) {
/* disconnect onward SS from its wsi */
lws_set_opaque_user_data(cw, NULL);
/*
* The wsi doing the onward connection can no
* longer relate to the conn... otherwise when
* he gets callbacks he wants to bind to
* the ss we are about to delete
*/
lws_wsi_close(cw, LWS_TO_KILL_ASYNC);
}
/* destroy the onward ss (setting conn->ss NULL) */
lws_ss_destroy(&conn->ss);
/*
* Conn may have gone, at ss destroy handler in
* ssi.state for proxied ss
*/
return LWSSSSRET_OK;
}
if (conn->state == LPCSPROX_DESTROYED || !conn->ss) {
/*
* There's no onward secure stream and our client
* connection is closing. Destroy the conn.
*/
lws_dsh_destroy(&conn->dsh);
lws_free(conn);
} else
lwsl_debug("%s: CLOSE; %s\n", __func__, lws_ss_tag(conn->ss));
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
lws_ssproxy_txp_rx(lws_transport_priv_t txp_priv, const uint8_t *in, size_t len)
{
struct lws_sss_proxy_conn *conn = (struct lws_sss_proxy_conn *)txp_priv;
lws_ss_state_return_t r;
lws_ss_info_t ssi;
assert_is_conn(conn);
// lwsl_hexdump_info(in, len);
if (conn->state == LPCSPROX_WAIT_INITIAL_TX) {
memset(&ssi, 0, sizeof(ssi));
ssi.user_alloc = sizeof(ss_proxy_t);
ssi.handle_offset = offsetof(ss_proxy_t, ss);
ssi.opaque_user_data_offset = offsetof(ss_proxy_t, conn);
ssi.rx = lws_sss_proxy_onward_rx;
ssi.tx = lws_sss_proxy_onward_tx;
}
ssi.state = lws_sss_proxy_onward_state;
ssi.flags = 0;
// coverity[uninit_use_in_call]
r = lws_ss_proxy_deserialize_parse(&conn->parser, conn->cx, conn->dsh,
in, len, &conn->state, conn,
&conn->ss, &ssi);
switch (r) {
default:
break;
case LWSSSSRET_DISCONNECT_ME:
return r;
case LWSSSSRET_DESTROY_ME:
if (conn->ss)
lws_ss_destroy(&conn->ss);
return r;
}
if ((conn->state == LPCSPROX_REPORTING_FAIL ||
conn->state == LPCSPROX_REPORTING_OK) &&
conn->txp_path.priv_onw)
conn->txp_path.ops_onw->proxy_req_write(conn->txp_path.priv_onw);
return LWSSSSRET_OK;
}
static lws_ss_state_return_t
lws_ssproxy_txp_proxy_can_write(lws_transport_priv_t priv
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
, const lws_fi_ctx_t *fic
#endif
)
{
struct lws_sss_proxy_conn *conn = (struct lws_sss_proxy_conn *)priv;
const lws_ss_policy_t *rsp;
lws_ss_metadata_t *md;
const uint8_t *cp;
char _s[1580 + LWS_PRE], *s = _s + LWS_PRE;
size_t si, csi;
uint8_t *p;
char pay;
int n;
assert_is_conn(conn);
n = 0;
pay = 0;
*(s + 3) = 0;
cp = (const uint8_t *)s;
switch (conn->state) {
case LPCSPROX_REPORTING_FAIL:
*(s + 3) = 1;
/* fallthru */
case LPCSPROX_REPORTING_OK:
*s = LWSSS_SER_RXPRE_CREATE_RESULT;
*(s + 1) = 0;
*(s + 2) = 1;
n = 8;
lws_ser_wu32be((uint8_t *)s + 4, conn->ss &&
conn->ss->policy ?
conn->ss->policy->client_buflen : 0);
/*
* If there's rideshare sequencing, it's added after the
* first 4 bytes or the create result, comma-separated
*/
if (conn->ss) {
rsp = conn->ss->policy;
while (rsp) {
if (n != 4 && n < (int)sizeof(_s) - LWS_PRE - 2)
*(s + (n++)) = ',';
n += lws_snprintf(s + n, sizeof(_s) - LWS_PRE - (unsigned int)n,
"%s", rsp->streamtype);
rsp = lws_ss_policy_lookup(conn->cx,
rsp->rideshare_streamtype);
}
}
*(s + 2) = (char)(n - 3);
conn->state = LPCSPROX_OPERATIONAL;
conn->txp_path.ops_onw->event_client_up(conn->txp_path.priv_onw);
break;
case LPCSPROX_OPERATIONAL:
/*
* returning [onward -> ] proxy]-> client
* rx metadata has priority 1
*/
md = conn->ss->metadata;
while (md) {
// lwsl_notice("%s: check %s: %d\n", __func__,
// md->name, md->pending_onward);
if (md->pending_onward) {
size_t naml = strlen(md->name);
// lwsl_notice("%s: proxy issuing rxmd\n", __func__);
if (4 + naml + md->length > sizeof(_s) - LWS_PRE) {
lwsl_err("%s: rxmdata too big\n",
__func__);
goto hangup;
}
md->pending_onward = 0;
p = (uint8_t *)s;
p[0] = LWSSS_SER_RXPRE_METADATA;
lws_ser_wu16be(&p[1], (uint16_t)(1 + naml +
md->length));
p[3] = (uint8_t)naml;
memcpy(&p[4], md->name, naml);
p += 4 + naml;
memcpy(p, md->value__may_own_heap,
md->length);
p += md->length;
n = lws_ptr_diff(p, cp);
goto do_write_nz;
}
md = md->next;
}
/*
* If we have performance data, render it in JSON
* and send that in LWSSS_SER_RXPRE_PERF has
* priority 2
*/
#if defined(LWS_WITH_CONMON)
if (conn->ss->conmon_json) {
unsigned int xlen = conn->ss->conmon_len;
if (xlen > sizeof(s) - 3)
xlen = sizeof(s) - 3;
cp = (uint8_t *)s;
p = (uint8_t *)s;
p[0] = LWSSS_SER_RXPRE_PERF;
lws_ser_wu16be(&p[1], (uint16_t)xlen);
memcpy(&p[3], conn->ss->conmon_json, xlen);
lws_free_set_NULL(conn->ss->conmon_json);
n = (int)(xlen + 3);
pay = 0;
goto do_write_nz;
}
#endif
/*
* if no fresh rx metadata, just pass through incoming
* dsh
*/
if (lws_dsh_get_head(conn->dsh, KIND_SS_TO_P, (void **)&p, &si))
break;
cp = p;
pay = 1;
n = (int)si;
break;
default:
break;
}
do_write_nz:
if (!n)
return LWSSSSRET_OK;
if (
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
fic &&
#endif
lws_fi(fic, "ssproxy_client_write_fail"))
n = -1;
else {
si = csi = (size_t)n;
n = conn->txp_path.ops_onw->proxy_write(conn->txp_path.priv_onw,
(uint8_t *)cp, &csi);
}
if (n < 0) {
lwsl_info("%s: WRITEABLE: %d\n", __func__, n);
goto hangup;
}
switch (conn->state) {
case LPCSPROX_REPORTING_FAIL:
goto hangup;
case LPCSPROX_OPERATIONAL:
if (!conn)
break;
if (pay) {
if (si == csi)
lws_dsh_free((void **)&p);
else
lws_dsh_consume(conn->dsh, KIND_SS_TO_P, csi);
/*
* Did we go below the rx flow threshold for
* this dsh?
*/
if (conn->onward_in_flow_control &&
conn->ss->policy->proxy_buflen_rxflow_on_above &&
conn->ss->wsi &&
lws_dsh_get_size(conn->dsh, KIND_SS_TO_P) <
conn->ss->policy->proxy_buflen_rxflow_off_below) {
lwsl_user("%s: %s: rxflow enabling rx (%lu / %lu, lwm %lu)\n", __func__,
lws_wsi_tag(conn->ss->wsi),
(unsigned long)lws_dsh_get_size(conn->dsh, KIND_SS_TO_P),
(unsigned long)conn->ss->policy->proxy_buflen,
(unsigned long)conn->ss->policy->proxy_buflen_rxflow_off_below);
/*
* Resume receiving taking in rx once
* below the low threshold
*/
lws_rx_flow_control(conn->ss->wsi,
LWS_RXFLOW_ALLOW);
conn->onward_in_flow_control = 0;
}
}
if (!lws_dsh_get_head(conn->dsh, KIND_SS_TO_P,
(void **)&p, &si)) {
if (conn && conn->txp_path.ops_onw->proxy_check_write_more &&
conn->txp_path.ops_onw->proxy_check_write_more(
conn->txp_path.priv_onw)) {
cp = p;
pay = 1;
n = (int)si;
goto do_write_nz;
}
conn->txp_path.ops_onw->proxy_req_write(
conn->txp_path.priv_onw);
}
default:
break;
}
return LWSSSSRET_OK;
hangup:
return LWSSSSRET_DISCONNECT_ME;
}
const lws_transport_proxy_ops_t lws_txp_inside_proxy = {
.name = "txp_inside_proxy",
.event_new_conn = lws_ssproxy_txp_new_conn,
.proxy_read = lws_ssproxy_txp_rx,
.event_close_conn = lws_ssproxy_txp_close_conn,
.event_proxy_can_write = lws_ssproxy_txp_proxy_can_write,
};

View File

@ -0,0 +1,492 @@
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2019 - 2021 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.
*
*
* When the user code is in a different process, a non-tls unix domain socket
* proxy is used to asynchronusly transfer buffers in each direction via the
* network stack, without explicit IPC
*
* user_process{ [user code] | shim | socket-}------ lws_process{ lws }
*
* Lws exposes a listening unix domain socket in this case, the user processes
* connect to it and pass just info.streamtype in an initial tx packet. All
* packets are prepended by a 1-byte type field when used in this mode. See
* lws-secure-streams.h for documentation and definitions.
*
* Proxying in either direction can face the situation it cannot send the onward
* packet immediately and is subject to separating the write request from the
* write action. To make the best use of memory, a single preallocated buffer
* stashes pending packets in all four directions (c->p, p->c, p->ss, ss->p).
* This allows it to adapt to different traffic patterns without wasted areas
* dedicated to traffic that isn't coming in a particular application.
*
* A shim is provided to monitor the process' unix domain socket and regenerate
* the secure sockets api there with callbacks happening in the process thread
* context.
*
* This file implements the listening unix domain socket proxy... this code is
* only going to run on a Linux-class device with its implications about memory
* availability.
*/
#include <private-lib-core.h>
/*
* Proxy - onward secure-stream handler
*/
void
lws_proxy_clean_conn_ss(struct lws *wsi)
{
#if 0
lws_ss_handle_t *h = (lws_ss_handle_t *)wsi->a.opaque_user_data;
struct lws_sss_proxy_conn *conn = h->conn_if_sspc_onw;
if (!wsi)
return;
if (conn && conn->ss)
conn->ss->wsi = NULL;
#endif
}
void
ss_proxy_onward_link_proxy_req_writeable(lws_ss_handle_t *h_onward)
{
ss_proxy_t *m = (ss_proxy_t *)&h_onward[1];
if (m->conn->txp_path.priv_onw)
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
}
int
__lws_ss_proxy_bind_ss_to_conn_wsi(void *parconn, size_t dsh_size)
{
struct lws_sss_proxy_conn *conn = (struct lws_sss_proxy_conn *)parconn;
struct lws_context_per_thread *pt;
if (!conn || !conn->txp_path.priv_onw || !conn->ss)
return -1;
pt = &conn->ss->context->pt[(int)conn->ss->tsi];
if (lws_fi(&conn->ss->fic, "ssproxy_dsh_create_oom"))
return -1;
conn->dsh = lws_dsh_create(&pt->ss_dsh_owner, dsh_size,
(int)(conn->txp_path.ops_onw->flags | 2));
if (!conn->dsh)
return -1;
conn->dsh->splitat = 1300;
conn->txp_path.ops_onw->event_onward_bind(conn->txp_path.priv_onw,
conn->ss);
return 0;
}
/*
* event loop received something and is queueing it for the foreign side of
* the dsh to consume later as serialized rx
*/
static int
lws_ss_serialize_rx_payload(struct lws_dsh *dsh, const uint8_t *buf,
size_t len, int flags, const char *rsp)
{
lws_usec_t us = lws_now_usecs();
uint8_t pre[128];
int est = 19, l = 0;
if (flags & LWSSS_FLAG_RIDESHARE) {
/*
* We should have the rideshare name if we have been told it's
* on a non-default rideshare
*/
assert(rsp);
if (!rsp)
return 1;
l = (int)strlen(rsp);
est += 1 + l;
} else
assert(!rsp);
// lwsl_user("%s: len %d, flags: %d\n", __func__, (int)len, flags);
// lwsl_hexdump_info(buf, len);
pre[0] = LWSSS_SER_RXPRE_RX_PAYLOAD;
lws_ser_wu16be(&pre[1], (uint16_t)(len + (size_t)est - 3));
lws_ser_wu32be(&pre[3], (uint32_t)flags);
lws_ser_wu32be(&pre[7], 0); /* write will compute latency here... */
lws_ser_wu64be(&pre[11], (uint64_t)us); /* ... and set this to the write time */
/*
* If we are on a non-default rideshare, append the non-default name to
* the headers of the payload part, 1-byte length first
*/
if (flags & LWSSS_FLAG_RIDESHARE) {
pre[19] = (uint8_t)l;
memcpy(&pre[20], rsp, (unsigned int)l);
}
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, (unsigned int)est, buf, len)) {
#if defined(_DEBUG)
lws_dsh_describe(dsh, __func__);
#endif
lwsl_err("%s: unable to alloc in dsh 1\n", __func__);
return 1;
}
lwsl_notice("%s: dsh c2p %d, p2c %d\n", __func__,
(int)lws_dsh_get_size(dsh, KIND_C_TO_P),
(int)lws_dsh_get_size(dsh, KIND_SS_TO_P));
return 0;
}
/* Onward secure streams payload interface */
lws_ss_state_return_t
lws_sss_proxy_onward_rx(void *userobj, const uint8_t *buf, size_t len, int flags)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
const char *rsp = NULL;
int n;
// lwsl_notice("%s: len %d\n", __func__, (int)len);
/*
* The onward secure stream connection has received something.
*/
if (m->ss->rideshare != m->ss->policy && m->ss->rideshare) {
rsp = m->ss->rideshare->streamtype;
flags |= LWSSS_FLAG_RIDESHARE;
}
/*
* Apply SSS framing around this chunk of RX and stash it in the dsh
* in ss -> proxy [ -> client] direction. This can fail...
*/
n = 1;
if (m->conn->dsh && !lws_fi(&m->ss->fic, "ssproxy_dsh_rx_queue_oom"))
n = lws_ss_serialize_rx_payload(m->conn->dsh, buf, len,
flags, rsp);
if (n) {
if (m->conn->dsh) {
#if defined(_DEBUG)
lws_dsh_describe(m->conn->dsh, __func__);
#endif
/*
* We couldn't buffer this rx, eg due to OOM, let's
* escalate it to be a "loss of connection", which it
* basically is... as part of that, drop the dshes.
*
* This just affects the one stream that owns the
* dsh, caller should enter stream close flow and not
* send any further payload.
*/
lwsl_warn("%s: dropping SS dsh due to OOM\n", __func__);
lws_dsh_empty(m->conn->dsh);
}
return LWSSSSRET_DISCONNECT_ME;
}
/*
* Manage rx flow on the SS (onward) side according to our situation
* in the dsh holding proxy->client serialized forwarding rx
*/
if (!m->conn->onward_in_flow_control && m->ss->wsi &&
m->ss->policy->proxy_buflen_rxflow_on_above &&
lws_dsh_get_size(m->conn->dsh, KIND_SS_TO_P) >=
m->ss->policy->proxy_buflen_rxflow_on_above) {
lwsl_ss_user(m->ss, "rxflow disabling rx (%lu / %lu, hwm %lu)",
(unsigned long)lws_dsh_get_size(m->conn->dsh,
KIND_SS_TO_P),
(unsigned long)m->ss->policy->proxy_buflen,
(unsigned long)m->ss->policy->proxy_buflen_rxflow_on_above);
/*
* stop taking in rx once the onward wsi rx is above the
* high water mark
*/
lws_rx_flow_control(m->ss->wsi, 0);
m->conn->onward_in_flow_control = 1;
}
if (m->conn->txp_path.priv_onw) /* if possible, request client conn write */
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
return LWSSSSRET_OK;
}
/*
* we are transmitting buffered payload originally from the client on to the ss
*/
lws_ss_state_return_t
lws_sss_proxy_onward_tx(void *userobj, lws_ss_tx_ordinal_t ord, uint8_t *buf,
size_t *len, int *flags)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
void *p;
size_t si;
if (!m->conn->ss || m->conn->state != LPCSPROX_OPERATIONAL) {
lwsl_notice("%s: ss not ready\n", __func__);
*len = 0;
return LWSSSSRET_TX_DONT_SEND;
}
/*
* The onward secure stream says that we could send something to it
* (by putting it in buf, and setting *len and *flags)... dredge the
* next thing out of the dsh
*/
if (lws_ss_deserialize_tx_payload(m->conn->dsh, m->ss->wsi,
ord, buf, len, flags))
return LWSSSSRET_TX_DONT_SEND;
/* ... there's more we want to send? */
if (!lws_dsh_get_head(m->conn->dsh, KIND_C_TO_P, (void **)&p, &si))
_lws_ss_request_tx(m->conn->ss);
if (!*len && !*flags)
/* we don't actually want to send anything */
return LWSSSSRET_TX_DONT_SEND;
lwsl_info("%s: onward tx %d fl 0x%x\n", __func__, (int)*len, *flags);
return LWSSSSRET_OK;
}
/*
* event loop side is issuing state, serialize and put it in the dbuf for
* the foreign side to consume later
*/
static int
lws_ss_serialize_state(struct lws_sss_proxy_conn *conn, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
const lws_fi_ctx_t *fic = conn->txp_path.ops_onw->fault_context(
conn->txp_path.priv_onw);
#endif
struct lws_dsh *dsh = conn->dsh;
uint8_t pre[12];
int n = 4;
if (state == LWSSSCS_EVENT_WAIT_CANCELLED)
return 0;
lwsl_info("%s: %s, ord 0x%x\n", __func__, lws_ss_state_name((int)state),
(unsigned int)ack);
if (!dsh) {
/* he can't store anything further on the link */
lwsl_notice("%s: dsh for conn was destroyed\n", __func__);
return 0;
}
pre[0] = LWSSS_SER_RXPRE_CONNSTATE;
pre[1] = 0;
if (state > 255) {
pre[2] = 8;
lws_ser_wu32be(&pre[3], state);
n = 7;
} else {
pre[2] = 5;
pre[3] = (uint8_t)state;
}
lws_ser_wu32be(&pre[n], ack);
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, (unsigned int)n + 4, NULL, 0)
#if defined(LWS_WITH_SYS_FAULT_INJECTION)
|| (fic && lws_fi(fic, "sspc_dsh_ss2p_oom"))
#endif
) {
lwsl_err("%s: unable to alloc in dsh 2\n", __func__);
return 1;
}
return 0;
}
lws_ss_state_return_t
lws_sss_proxy_onward_state(void *userobj, void *sh, lws_ss_constate_t state,
lws_ss_tx_ordinal_t ack)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
size_t dsh_size;
switch (state) {
case LWSSSCS_CREATING:
/*
* conn is private to -process.c, call thru to a) adjust
* the accepted incoming proxy link wsi tag name to be
* appended with the onward ss tag information now we
* have it, and b) allocate the dsh buffer now we
* can find out the policy about it for the streamtype.
*/
dsh_size = m->ss->policy->proxy_buflen ?
m->ss->policy->proxy_buflen : 32768;
lwsl_notice("%s: %s: initializing dsh max len %lu\n",
__func__, lws_ss_tag(m->ss),
(unsigned long)dsh_size);
/* this includes ssproxy_dsh_create_oom fault generation */
if (__lws_ss_proxy_bind_ss_to_conn_wsi(m->conn, dsh_size)) {
/* failed to allocate the dsh */
lwsl_notice("%s: dsh init failed\n", __func__);
return LWSSSSRET_DESTROY_ME;
}
break;
case LWSSSCS_DESTROYING:
if (!m->conn)
break;
if (!m->conn->txp_path.priv_onw) {
/*
* Our onward secure stream is closing and our client
* connection has already gone away... destroy the conn.
*/
lwsl_notice("%s: Destroying conn\n", __func__);
lws_dsh_empty(m->conn->dsh);
if (!m->conn->ss) {
lws_dsh_destroy(&m->conn->dsh);
free(m->conn);
m->conn = NULL;
}
return 0;
} else
lwsl_info("%s: ss DESTROYING, wsi up\n", __func__);
break;
default:
break;
}
if (!m->conn) {
lwsl_warn("%s: dropping state due to conn not up\n", __func__);
return LWSSSSRET_OK;
}
if (lws_ss_serialize_state(m->conn, state, ack))
/*
* Failed to alloc state packet that we want to send in dsh,
* we will lose coherence and have to disconnect the link
*/
return LWSSSSRET_DISCONNECT_ME;
if (state != LWSSSCS_DESTROYING &&
m->conn->txp_path.priv_onw) /* if possible, request client conn write */
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
return LWSSSSRET_OK;
}
/*
* event loop side was told about remote peer tx credit window update, serialize
* and put it in the dbuf for the foreign side to consume later
*/
static int
lws_ss_serialize_txcr(struct lws_dsh *dsh, int txcr)
{
uint8_t pre[7];
lwsl_info("%s: %d\n", __func__, txcr);
pre[0] = LWSSS_SER_RXPRE_TXCR_UPDATE;
pre[1] = 0;
pre[2] = 4;
lws_ser_wu32be(&pre[3], (uint32_t)txcr);
if (lws_dsh_alloc_tail(dsh, KIND_SS_TO_P, pre, 7, NULL, 0)) {
lwsl_err("%s: unable to alloc in dsh 2\n", __func__);
return 1;
}
return 0;
}
void
ss_proxy_onward_txcr(void *userobj, int bump)
{
ss_proxy_t *m = (ss_proxy_t *)userobj;
if (!m->conn)
return;
lws_ss_serialize_txcr(m->conn->dsh, bump);
if (m->conn->txp_path.priv_onw) /* if possible, request client conn write */
m->conn->txp_path.ops_onw->proxy_req_write(m->conn->txp_path.priv_onw);
}
/*
* called from create_context()
*/
int
lws_ss_proxy_create(struct lws_context *cx, const char *bind, int port)
{
assert(cx->txp_ppath.ops_onw);
return cx->txp_ppath.ops_onw->init_proxy_server(cx,
&lws_txp_inside_proxy,
NULL,
&cx->txp_ppath,
cx->txp_ssproxy_info,
bind, port);
}
lws_ss_state_return_t
lws_ss_proxy_destroy(struct lws_context *cx)
{
if (!cx->txp_ppath.ops_onw)
return 0;
if (!cx->txp_ppath.ops_onw->destroy_proxy_server)
return 0;
return cx->txp_ppath.ops_onw->destroy_proxy_server(cx);
}