/* * libwebsockets - small server side websockets and web server implementation * * Copyright (C) 2010 - 2019 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 #include "private-lib-core.h" struct lws * lws_client_connect_via_info(const struct lws_client_connect_info *i) { const char *local = i->protocol; struct lws *wsi, *safe = NULL; const struct lws_protocols *p; size_t s = sizeof(struct lws); const char *cisin[CIS_COUNT]; int tid = 0, n, m; size_t size; char *pc; if (i->context->requested_kill) return NULL; if (!i->context->protocol_init_done) if (lws_protocol_init(i->context)) return NULL; /* * If we have .local_protocol_name, use it to select the local protocol * handler to bind to. Otherwise use .protocol if http[s]. */ if (i->local_protocol_name) local = i->local_protocol_name; lws_stats_bump(&i->context->pt[tid], LWSSTATS_C_CONNS_CLIENT, 1); /* PHASE 1: create a bare wsi */ #if defined(LWS_WITH_EVENT_LIBS) s += i->context->event_loop_ops->evlib_size_wsi; #endif wsi = lws_zalloc(s, "client wsi"); if (wsi == NULL) goto bail; #if defined(LWS_WITH_EVENT_LIBS) wsi->evlib_wsi = (uint8_t *)wsi + sizeof(*wsi); #endif /* * Until we exit, we can report connection failure directly to the * caller without needing to call through to protocol CONNECTION_ERROR. */ wsi->client_suppress_CONNECTION_ERROR = 1; if (i->keep_warm_secs) wsi->keep_warm_secs = i->keep_warm_secs; else wsi->keep_warm_secs = 5; wsi->a.context = i->context; wsi->desc.sockfd = LWS_SOCK_INVALID; wsi->seq = i->seq; wsi->flags = i->ssl_connection; if (i->retry_and_idle_policy) wsi->retry_policy = i->retry_and_idle_policy; else wsi->retry_policy = &i->context->default_retry; #if defined(LWS_WITH_DETAILED_LATENCY) if (i->context->detailed_latency_cb) wsi->detlat.earliest_write_req_pre_write = lws_now_usecs(); #endif if (i->ssl_connection & LCCSCF_WAKE_SUSPEND__VALIDITY) wsi->conn_validity_wakesuspend = 1; wsi->a.vhost = NULL; if (!i->vhost) { struct lws_vhost *v = i->context->vhost_list; if (!v) { /* coverity */ lwsl_err("%s: no vhost\n", __func__); goto bail; } if (!strcmp(v->name, "system")) v = v->vhost_next; lws_vhost_bind_wsi(v, wsi); } else lws_vhost_bind_wsi(i->vhost, wsi); if (!wsi->a.vhost) { lwsl_err("%s: No vhost in the context\n", __func__); goto bail; } #if LWS_MAX_SMP > 1 tid = wsi->a.vhost->protocols[0].callback(wsi, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0); #endif /* * PHASE 2: if SMP, bind the client to whatever tsi the current thread * represents */ #if LWS_MAX_SMP > 1 lws_context_lock(i->context, "client find tsi"); for (n = 0; n < i->context->count_threads; n++) if (i->context->pt[n].service_tid == tid) { lwsl_info("%s: client binds to caller tsi %d\n", __func__, n); wsi->tsi = n; #if defined(LWS_WITH_DETAILED_LATENCY) wsi->detlat.tsi = n; #endif break; } /* * this binding is sort of provisional, since when we try to insert * into the pt fds, there may be no space and it will fail */ lws_context_unlock(i->context); #endif /* * PHASE 3: Choose an initial role for the wsi and do role-specific init * * Note the initial role may not reflect the final role, eg, * we may want ws, but first we have to go through h1 to get that */ if (lws_role_call_client_bind(wsi, i) < 0) { lwsl_err("%s: unable to bind to role\n", __func__); goto bail; } lwsl_info("%s: role binding to %s\n", __func__, wsi->role_ops->name); /* * PHASE 4: fill up the wsi with stuff from the connect_info as far as * it can go. It's uncertain because not only is our connection * going to complete asynchronously, we might have bound to h1 and not * even be able to get ahold of an ah immediately. */ wsi->user_space = NULL; wsi->pending_timeout = NO_PENDING_TIMEOUT; wsi->position_in_fds_table = LWS_NO_FDS_POS; wsi->ocport = wsi->c_port = i->port; wsi->sys_tls_client_cert = i->sys_tls_client_cert; #if defined(LWS_ROLE_H2) wsi->txc.manual_initial_tx_credit = (int32_t)i->manual_initial_tx_credit; #endif wsi->a.protocol = &wsi->a.vhost->protocols[0]; wsi->client_pipeline = !!(i->ssl_connection & LCCSCF_PIPELINE); wsi->client_no_follow_redirect = !!(i->ssl_connection & LCCSCF_HTTP_NO_FOLLOW_REDIRECT); /* * PHASE 5: handle external user_space now, generic alloc is done in * role finalization */ if (i->userdata) { wsi->user_space_externally_allocated = 1; wsi->user_space = i->userdata; } if (local) { lwsl_info("%s: vh %s protocol binding to %s\n", __func__, wsi->a.vhost->name, local); p = lws_vhost_name_to_protocol(wsi->a.vhost, local); if (p) lws_bind_protocol(wsi, p, __func__); else lwsl_info("%s: unknown protocol %s\n", __func__, local); lwsl_info("%s: wsi %p: %s %s entry\n", __func__, wsi, wsi->role_ops->name, wsi->a.protocol ? wsi->a.protocol->name : "none"); } /* * PHASE 5: handle external user_space now, generic alloc is done in * role finalization */ if (!wsi->user_space && i->userdata) { wsi->user_space_externally_allocated = 1; wsi->user_space = i->userdata; } #if defined(LWS_WITH_TLS) wsi->tls.use_ssl = i->ssl_connection; #else if (i->ssl_connection & LCCSCF_USE_SSL) { lwsl_err("%s: lws not configured for tls\n", __func__); goto bail; } #endif /* * PHASE 6: stash the things from connect_info that we can't process * right now, eg, if http binding, without an ah. If h1 and no ah, we * will go on the ah waiting list and process those things later (after * the connect_info and maybe the things pointed to have gone out of * scope) * * However these things are stashed in a generic way at this point, * with no relationship to http or ah */ cisin[CIS_ADDRESS] = i->address; cisin[CIS_PATH] = i->path; cisin[CIS_HOST] = i->host; cisin[CIS_ORIGIN] = i->origin; cisin[CIS_PROTOCOL] = i->protocol; cisin[CIS_METHOD] = i->method; cisin[CIS_IFACE] = i->iface; cisin[CIS_ALPN] = i->alpn; size = sizeof(*wsi->stash); /* * Let's overallocate the stash object with space for all the args * in one hit. */ for (n = 0; n < CIS_COUNT; n++) if (cisin[n]) size += strlen(cisin[n]) + 1; wsi->stash = lws_malloc(size, "client stash"); if (!wsi->stash) { lwsl_err("%s: OOM\n", __func__); goto bail1; } /* all the pointers default to NULL, but no need to zero the args */ memset(wsi->stash, 0, sizeof(*wsi->stash)); wsi->a.opaque_user_data = wsi->stash->opaque_user_data = i->opaque_user_data; pc = (char *)&wsi->stash[1]; for (n = 0; n < CIS_COUNT; n++) if (cisin[n]) { wsi->stash->cis[n] = pc; m = (int)strlen(cisin[n]) + 1; memcpy(pc, cisin[n], m); pc += m; } /* * at this point user callbacks like * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER will be interested to * know the parent... eg for proxying we can grab extra headers from * the parent's incoming ah and add them to the child client handshake */ if (i->parent_wsi) { lwsl_info("%s: created child %p of parent %p\n", __func__, wsi, i->parent_wsi); wsi->parent = i->parent_wsi; safe = wsi->sibling_list = i->parent_wsi->child_list; i->parent_wsi->child_list = wsi; } /* * PHASE 7: Do any role-specific finalization processing. We can still * see important info things via wsi->stash */ if (wsi->role_ops->client_bind) { int n = wsi->role_ops->client_bind(wsi, NULL); if (n && i->parent_wsi) { /* unpick from parent */ i->parent_wsi->child_list = safe; } if (n < 0) /* we didn't survive, wsi is freed */ goto bail2; if (n) /* something else failed, wsi needs freeing */ goto bail; } /* let the caller's optional wsi storage have the wsi we created */ if (i->pwsi) *i->pwsi = wsi; /* PHASE 8: notify protocol with role-specific connected callback */ /* raw socket per se doesn't want this... raw socket proxy wants it... */ if (wsi->role_ops != &role_ops_raw_skt || (i->local_protocol_name && !strcmp(i->local_protocol_name, "raw-proxy"))) { lwsl_debug("%s: wsi %p: adoption cb %d to %s %s\n", __func__, wsi, wsi->role_ops->adoption_cb[0], wsi->role_ops->name, wsi->a.protocol->name); wsi->a.protocol->callback(wsi, wsi->role_ops->adoption_cb[0], wsi->user_space, NULL, 0); } #if defined(LWS_WITH_HUBBUB) if (i->uri_replace_to) wsi->http.rw = lws_rewrite_create(wsi, html_parser_cb, i->uri_replace_from, i->uri_replace_to); #endif if (i->method && (!strcmp(i->method, "RAW") // || // !strcmp(i->method, "MQTT") )) { /* * Not for MQTT here, since we don't know if we will * pipeline it or not... */ #if defined(LWS_WITH_TLS) wsi->tls.ssl = NULL; if (wsi->tls.use_ssl & LCCSCF_USE_SSL) { const char *cce = NULL; switch ( #if !defined(LWS_WITH_SYS_ASYNC_DNS) lws_client_create_tls(wsi, &cce, 1) #else lws_client_create_tls(wsi, &cce, 0) #endif ) { case 1: return wsi; case 0: break; default: goto bail3; } } #endif /* fallthru */ wsi = lws_http_client_connect_via_info2(wsi); } if (wsi) /* * If it subsequently fails, report CONNECTION_ERROR, * because we're going to return a non-error return now. */ wsi->client_suppress_CONNECTION_ERROR = 0; return wsi; #if defined(LWS_WITH_TLS) bail3: lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "tls start fail"); return NULL; #endif bail1: lws_free_set_NULL(wsi->stash); bail: lws_free(wsi); #if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2) bail2: #endif #if defined(LWS_WITH_TLS) if (i->ssl_connection & LCCSCF_USE_SSL) lws_tls_restrict_return(i->context); #endif if (i->pwsi) *i->pwsi = NULL; lws_stats_bump(&i->context->pt[tid], LWSSTATS_C_CONNS_CLIENT_FAILED, 1); return NULL; }