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.
582 lines
13 KiB
582 lines
13 KiB
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2019 - 2020 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>
|
|
|
|
static const struct ss_pcols *ss_pcols[] = {
|
|
#if defined(LWS_ROLE_H1)
|
|
&ss_pcol_h1, /* LWSSSP_H1 */
|
|
#else
|
|
NULL,
|
|
#endif
|
|
#if defined(LWS_ROLE_H2)
|
|
&ss_pcol_h2, /* LWSSSP_H2 */
|
|
#else
|
|
NULL,
|
|
#endif
|
|
#if defined(LWS_ROLE_WS)
|
|
&ss_pcol_ws, /* LWSSSP_WS */
|
|
#else
|
|
NULL,
|
|
#endif
|
|
#if defined(LWS_ROLE_MQTT)
|
|
&ss_pcol_mqtt, /* LWSSSP_MQTT */
|
|
#else
|
|
NULL,
|
|
#endif
|
|
};
|
|
|
|
static const char *state_names[] = {
|
|
"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",
|
|
};
|
|
|
|
const char *
|
|
lws_ss_state_name(int state)
|
|
{
|
|
if (state >= (int)LWS_ARRAY_SIZE(state_names))
|
|
return "unknown";
|
|
|
|
return state_names[state];
|
|
}
|
|
|
|
int
|
|
lws_ss_event_helper(lws_ss_handle_t *h, lws_ss_constate_t cs)
|
|
{
|
|
if (!h)
|
|
return 0;
|
|
|
|
#if defined(LWS_WITH_SEQUENCER)
|
|
/*
|
|
* A parent sequencer for the ss is optional, if we have one, keep it
|
|
* informed of state changes on the ss connection
|
|
*/
|
|
if (h->seq && cs != LWSSSCS_DESTROYING)
|
|
lws_seq_queue_event(h->seq, LWSSEQ_SS_STATE_BASE + cs,
|
|
(void *)h, NULL);
|
|
#endif
|
|
|
|
if (h->h_sink &&h->h_sink->info.state(h->sink_obj, h->h_sink, cs, 0))
|
|
return 1;
|
|
|
|
return h->info.state(ss_to_userobj(h), NULL, cs, 0);
|
|
}
|
|
|
|
static void
|
|
lws_ss_timeout_sul_check_cb(lws_sorted_usec_list_t *sul)
|
|
{
|
|
lws_ss_handle_t *h = lws_container_of(sul, lws_ss_handle_t, sul);
|
|
|
|
lwsl_err("%s: retrying ss h %p after backoff\n", __func__, h);
|
|
/* we want to retry... */
|
|
h->seqstate = SSSEQ_DO_RETRY;
|
|
|
|
lws_ss_request_tx(h);
|
|
}
|
|
|
|
int
|
|
lws_ss_exp_cb_metadata(void *priv, const char *name, char *out, size_t *pos,
|
|
size_t olen, size_t *exp_ofs)
|
|
{
|
|
lws_ss_handle_t *h = (lws_ss_handle_t *)priv;
|
|
const char *replace = NULL;
|
|
size_t total, budget;
|
|
lws_ss_metadata_t *md = lws_ss_policy_metadata(h->policy, name);
|
|
|
|
if (!md) {
|
|
lwsl_err("%s: Unknown metadata %s\n", __func__, name);
|
|
|
|
return LSTRX_FATAL_NAME_UNKNOWN;
|
|
}
|
|
|
|
lwsl_info("%s %s %d\n", __func__, name, (int)md->length);
|
|
|
|
replace = h->metadata[md->length].value;
|
|
total = h->metadata[md->length].length;
|
|
// lwsl_hexdump_err(replace, total);
|
|
|
|
budget = olen - *pos;
|
|
total -= *exp_ofs;
|
|
if (total < budget)
|
|
budget = total;
|
|
|
|
memcpy(out + *pos, replace + (*exp_ofs), budget);
|
|
*exp_ofs += budget;
|
|
*pos += budget;
|
|
|
|
if (budget == total)
|
|
return LSTRX_DONE;
|
|
|
|
return LSTRX_FILLED_OUT;
|
|
}
|
|
|
|
int
|
|
lws_ss_set_timeout_us(lws_ss_handle_t *h, lws_usec_t us)
|
|
{
|
|
struct lws_context_per_thread *pt = &h->context->pt[h->tsi];
|
|
|
|
h->sul.cb = lws_ss_timeout_sul_check_cb;
|
|
__lws_sul_insert(&pt->pt_sul_owner, &h->sul, us);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_ss_backoff(lws_ss_handle_t *h)
|
|
{
|
|
uint64_t ms;
|
|
char conceal;
|
|
|
|
if (h->seqstate == SSSEQ_RECONNECT_WAIT)
|
|
return 0;
|
|
|
|
/* figure out what we should do about another retry */
|
|
|
|
lwsl_info("%s: ss %p: retry backoff after failure\n", __func__, h);
|
|
ms = lws_retry_get_delay_ms(h->context, h->policy->retry_bo,
|
|
&h->retry, &conceal);
|
|
if (!conceal) {
|
|
lwsl_info("%s: ss %p: abandon conn attempt \n",__func__, h);
|
|
h->seqstate = SSSEQ_IDLE;
|
|
lws_ss_event_helper(h, LWSSSCS_ALL_RETRIES_FAILED);
|
|
return 1;
|
|
}
|
|
|
|
h->seqstate = SSSEQ_RECONNECT_WAIT;
|
|
lws_ss_set_timeout_us(h, ms * LWS_US_PER_MS);
|
|
|
|
lwsl_info("%s: ss %p: retry wait %"PRIu64"ms\n", __func__, h, ms);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_ss_client_connect(lws_ss_handle_t *h)
|
|
{
|
|
struct lws_client_connect_info i;
|
|
const struct ss_pcols *ssp;
|
|
size_t used_in, used_out;
|
|
union lws_ss_contemp ct;
|
|
char path[128], ep[96];
|
|
lws_strexp_t exp;
|
|
|
|
if (!h->policy) {
|
|
lwsl_err("%s: ss with no policy\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* We are already bound to a sink?
|
|
*/
|
|
|
|
if (h->h_sink)
|
|
return 0;
|
|
|
|
memset(&i, 0, sizeof i); /* otherwise uninitialized garbage */
|
|
i.context = h->context;
|
|
|
|
if (h->policy->flags & LWSSSPOLF_TLS) {
|
|
lwsl_info("%s: using tls\n", __func__);
|
|
i.ssl_connection = LCCSCF_USE_SSL;
|
|
|
|
if (!h->policy->trust_store)
|
|
lwsl_info("%s: using platform trust store\n", __func__);
|
|
else {
|
|
|
|
i.vhost = lws_get_vhost_by_name(h->context,
|
|
h->policy->trust_store->name);
|
|
if (!i.vhost) {
|
|
lwsl_err("%s: missing vh for policy ca\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* expand metadata ${symbols} that may be inside the endpoint string */
|
|
|
|
lws_strexp_init(&exp, (void *)h, lws_ss_exp_cb_metadata, ep, sizeof(ep));
|
|
|
|
if (lws_strexp_expand(&exp, h->policy->endpoint,
|
|
strlen(h->policy->endpoint),
|
|
&used_in, &used_out) != LSTRX_DONE) {
|
|
lwsl_err("%s: address strexp failed\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
|
|
i.address = ep;
|
|
i.port = h->policy->port;
|
|
i.host = i.address;
|
|
i.origin = i.address;
|
|
i.opaque_user_data = h;
|
|
i.seq = h->seq;
|
|
i.retry_and_idle_policy = h->policy->retry_bo;
|
|
i.sys_tls_client_cert = h->policy->client_cert;
|
|
|
|
i.path = "";
|
|
|
|
ssp = ss_pcols[(int)h->policy->protocol];
|
|
if (!ssp) {
|
|
lwsl_err("%s: unsupported protocol\n", __func__);
|
|
|
|
return -1;
|
|
}
|
|
i.alpn = ssp->alpn;
|
|
|
|
/*
|
|
* For http, we can get the method from the http object, override in
|
|
* the protocol-specific munge callback below if not http
|
|
*/
|
|
i.method = h->policy->u.http.method;
|
|
i.protocol = ssp->protocol_name; /* lws protocol name */
|
|
i.local_protocol_name = i.protocol;
|
|
|
|
ssp->munge(h, path, sizeof(path), &i, &ct);
|
|
|
|
i.pwsi = &h->wsi;
|
|
|
|
if (h->policy->plugins[0] && h->policy->plugins[0]->munge)
|
|
h->policy->plugins[0]->munge(h, path, sizeof(path));
|
|
|
|
lwsl_info("%s: connecting %s, '%s' '%s' %s\n", __func__, i.method,
|
|
i.alpn, i.address, i.path);
|
|
|
|
h->txn_ok = 0;
|
|
if (lws_ss_event_helper(h, LWSSSCS_CONNECTING))
|
|
return -1;
|
|
|
|
if (!lws_client_connect_via_info(&i)) {
|
|
lws_ss_event_helper(h, LWSSSCS_UNREACHABLE);
|
|
lws_ss_backoff(h);
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Public API
|
|
*/
|
|
|
|
/*
|
|
* Create either a stream or a sink
|
|
*/
|
|
|
|
int
|
|
lws_ss_create(struct lws_context *context, int tsi, const lws_ss_info_t *ssi,
|
|
void *opaque_user_data, lws_ss_handle_t **ppss,
|
|
struct lws_sequencer *seq_owner, const char **ppayload_fmt)
|
|
{
|
|
struct lws_context_per_thread *pt = &context->pt[tsi];
|
|
const lws_ss_policy_t *pol;
|
|
lws_ss_metadata_t *smd;
|
|
lws_ss_handle_t *h;
|
|
size_t size;
|
|
void **v;
|
|
char *p;
|
|
int n;
|
|
|
|
pol = lws_ss_policy_lookup(context, ssi->streamtype);
|
|
if (!pol) {
|
|
lwsl_info("%s: unknown stream type %s\n", __func__,
|
|
ssi->streamtype);
|
|
return 1;
|
|
}
|
|
|
|
if (ssi->register_sink) {
|
|
/*
|
|
* This can register a secure streams sink as well as normal
|
|
* secure streams connections. If that's what's happening,
|
|
* confirm the policy agrees that this streamtype should be
|
|
* directed to a sink.
|
|
*/
|
|
if (!(pol->flags & LWSSSPOLF_LOCAL_SINK)) {
|
|
/*
|
|
* Caller wanted to create a sink for this streamtype,
|
|
* but the policy does not agree the streamtype should
|
|
* be routed to a local sink.
|
|
*/
|
|
lwsl_err("%s: %s policy does not allow local sink\n",
|
|
__func__, ssi->streamtype);
|
|
|
|
return 1;
|
|
}
|
|
} else {
|
|
|
|
if (!(pol->flags & LWSSSPOLF_LOCAL_SINK)) {
|
|
|
|
}
|
|
// lws_dll2_foreach_safe(&pt->ss_owner, NULL, lws_ss_destroy_dll);
|
|
}
|
|
|
|
/*
|
|
* We overallocate and point to things in the overallocation...
|
|
*
|
|
* 1) the user_alloc from the stream info
|
|
* 2) network auth plugin instantiation data
|
|
* 3) stream auth plugin instantiation data
|
|
* 4) as many metadata pointer structs as the policy tells
|
|
* 5) the streamtype name (length is not aligned)
|
|
*
|
|
* ... when we come to destroy it, just one free to do.
|
|
*/
|
|
|
|
size = sizeof(*h) + ssi->user_alloc + strlen(ssi->streamtype) + 1;
|
|
if (pol->plugins[0])
|
|
size += pol->plugins[0]->alloc;
|
|
if (pol->plugins[1])
|
|
size += pol->plugins[1]->alloc;
|
|
size += pol->metadata_count * sizeof(lws_ss_metadata_t);
|
|
|
|
h = lws_zalloc(size, __func__);
|
|
if (!h)
|
|
return 2;
|
|
|
|
h->info = *ssi;
|
|
h->policy = pol;
|
|
h->context = context;
|
|
h->tsi = tsi;
|
|
h->seq = seq_owner;
|
|
|
|
/* start of overallocated area */
|
|
p = (char *)&h[1];
|
|
|
|
/* set the handle pointer in the user data struct */
|
|
v = (void **)(p + ssi->handle_offset);
|
|
*v = h;
|
|
|
|
/* set the opaque user data in the user data struct */
|
|
v = (void **)(p + ssi->opaque_user_data_offset);
|
|
*v = opaque_user_data;
|
|
|
|
p += ssi->user_alloc;
|
|
|
|
if (pol->plugins[0]) {
|
|
h->nauthi = p;
|
|
p += pol->plugins[0]->alloc;
|
|
}
|
|
if (pol->plugins[1]) {
|
|
h->sauthi = p;
|
|
p += pol->plugins[1]->alloc;
|
|
}
|
|
|
|
if (pol->metadata_count) {
|
|
h->metadata = (lws_ss_metadata_t *)p;
|
|
p += pol->metadata_count * sizeof(lws_ss_metadata_t);
|
|
|
|
lwsl_info("%s: %s metadata count %d\n", __func__,
|
|
pol->streamtype, pol->metadata_count);
|
|
}
|
|
|
|
smd = pol->metadata;
|
|
for (n = 0; n < pol->metadata_count; n++) {
|
|
h->metadata[n].name = smd->name;
|
|
if (n + 1 == pol->metadata_count)
|
|
h->metadata[n].next = NULL;
|
|
else
|
|
h->metadata[n].next = &h->metadata[n + 1];
|
|
smd = smd->next;
|
|
}
|
|
|
|
memcpy(p, ssi->streamtype, strlen(ssi->streamtype) + 1);
|
|
h->info.streamtype = p;
|
|
|
|
lws_pt_lock(pt, __func__);
|
|
lws_dll2_add_head(&h->list, &pt->ss_owner);
|
|
lws_pt_unlock(pt);
|
|
|
|
if (ppss)
|
|
*ppss = h;
|
|
|
|
if (ppayload_fmt)
|
|
*ppayload_fmt = pol->payload_fmt;
|
|
|
|
if (ssi->register_sink) {
|
|
/*
|
|
*
|
|
*/
|
|
}
|
|
|
|
lws_ss_event_helper(h, LWSSSCS_CREATING);
|
|
|
|
if (!ssi->register_sink && (h->policy->flags & LWSSSPOLF_NAILED_UP))
|
|
if (lws_ss_client_connect(h))
|
|
lws_ss_backoff(h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
lws_ss_destroy(lws_ss_handle_t **ppss)
|
|
{
|
|
struct lws_context_per_thread *pt;
|
|
lws_ss_handle_t *h = *ppss;
|
|
lws_ss_metadata_t *pmd;
|
|
|
|
if (!h)
|
|
return;
|
|
|
|
if (h->wsi) {
|
|
/*
|
|
* Don't let the wsi point to us any more,
|
|
* we (the ss object bound to the wsi) are going away now
|
|
*/
|
|
// lws_set_opaque_user_data(h->wsi, NULL);
|
|
lws_set_timeout(h->wsi, 1, LWS_TO_KILL_SYNC);
|
|
}
|
|
|
|
pt = &h->context->pt[h->tsi];
|
|
|
|
lws_pt_lock(pt, __func__);
|
|
*ppss = NULL;
|
|
lws_dll2_remove(&h->list);
|
|
lws_dll2_remove(&h->to_list);
|
|
lws_ss_event_helper(h, LWSSSCS_DESTROYING);
|
|
lws_pt_unlock(pt);
|
|
|
|
/* in proxy case, metadata value on heap may need cleaning up */
|
|
|
|
pmd = h->metadata;
|
|
while (pmd) {
|
|
lwsl_info("%s: pmd %p\n", __func__, pmd);
|
|
if (pmd->value_on_lws_heap)
|
|
lws_free_set_NULL(pmd->value);
|
|
pmd = pmd->next;
|
|
}
|
|
|
|
lws_sul_schedule(h->context, 0, &h->sul, NULL, LWS_SET_TIMER_USEC_CANCEL);
|
|
|
|
lws_free_set_NULL(h);
|
|
}
|
|
|
|
void
|
|
lws_ss_request_tx(lws_ss_handle_t *h)
|
|
{
|
|
lwsl_info("%s: wsi %p\n", __func__, h->wsi);
|
|
|
|
if (h->wsi) {
|
|
lws_callback_on_writable(h->wsi);
|
|
|
|
return;
|
|
}
|
|
|
|
if (h->seqstate != SSSEQ_IDLE &&
|
|
h->seqstate != SSSEQ_DO_RETRY)
|
|
return;
|
|
|
|
h->seqstate = SSSEQ_TRY_CONNECT;
|
|
lws_ss_event_helper(h, LWSSSCS_POLL);
|
|
|
|
if (lws_ss_client_connect(h))
|
|
lws_ss_backoff(h);
|
|
}
|
|
|
|
void
|
|
lws_ss_request_tx_len(lws_ss_handle_t *h, unsigned long len)
|
|
{
|
|
if (h->wsi)
|
|
h->wsi->http.writeable_len = len;
|
|
else
|
|
h->writeable_len = len;
|
|
lws_ss_request_tx(h);
|
|
}
|
|
|
|
/*
|
|
* private helpers
|
|
*/
|
|
|
|
/* used on context destroy when iterating listed lws_ss on a pt */
|
|
|
|
int
|
|
lws_ss_destroy_dll(struct lws_dll2 *d, void *user)
|
|
{
|
|
lws_ss_handle_t *h = lws_container_of(d, lws_ss_handle_t, list);
|
|
|
|
lws_ss_destroy(&h);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct lws_sequencer *
|
|
lws_ss_get_sequencer(lws_ss_handle_t *h)
|
|
{
|
|
return h->seq;
|
|
}
|
|
|
|
struct lws_context *
|
|
lws_ss_get_context(struct lws_ss_handle *h)
|
|
{
|
|
return h->context;
|
|
}
|
|
|
|
const char *
|
|
lws_ss_rideshare(struct lws_ss_handle *h)
|
|
{
|
|
if (!h->rideshare)
|
|
return h->policy->streamtype;
|
|
|
|
return h->rideshare->streamtype;
|
|
}
|
|
|
|
int
|
|
lws_ss_add_peer_tx_credit(struct lws_ss_handle *h, int32_t bump)
|
|
{
|
|
const struct ss_pcols *ssp;
|
|
|
|
ssp = ss_pcols[(int)h->policy->protocol];
|
|
|
|
if (h->wsi && ssp && ssp->tx_cr_add)
|
|
return ssp->tx_cr_add(h, bump);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_ss_get_est_peer_tx_credit(struct lws_ss_handle *h)
|
|
{
|
|
const struct ss_pcols *ssp;
|
|
|
|
ssp = ss_pcols[(int)h->policy->protocol];
|
|
|
|
if (h->wsi && ssp && ssp->tx_cr_add)
|
|
return ssp->tx_cr_est(h);
|
|
|
|
return 0;
|
|
}
|