/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2019 - 2020 Andy Green * * 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 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; }