libwebsockets/lib/core-net/client/connect4.c
payne.ye ec76f8178d smp: extend client to work with tsi
and take care of zombie sul previously left on connect failure path
2024-04-12 06:15:12 +01:00

339 lines
9.2 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 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"
struct lws *
lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback,
ssize_t plen)
{
#if defined(LWS_CLIENT_HTTP_PROXYING)
struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];
#endif
const char *meth;
struct lws_pollfd pfd;
const char *cce = "";
int n, m, rawish = 0;
meth = lws_wsi_client_stash_item(wsi, CIS_METHOD,
_WSI_TOKEN_CLIENT_METHOD);
if (meth && (!strcmp(meth, "RAW")
#if defined(LWS_ROLE_MQTT)
|| !strcmp(meth, "MQTT")
#endif
))
rawish = 1;
if (wsi_piggyback)
goto send_hs;
#if defined(LWS_CLIENT_HTTP_PROXYING)
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
/* we are connected to server, or proxy */
/* http proxy */
if (wsi->a.vhost->http.http_proxy_port) {
const char *cpa;
cpa = lws_wsi_client_stash_item(wsi, CIS_ADDRESS,
_WSI_TOKEN_CLIENT_PEER_ADDRESS);
if (!cpa)
goto failed;
lwsl_wsi_info(wsi, "going via proxy");
plen = lws_snprintf((char *)pt->serv_buf, 256,
"CONNECT %s:%u HTTP/1.1\x0d\x0a"
"Host: %s:%u\x0d\x0a"
"User-agent: lws\x0d\x0a", cpa, wsi->ocport,
cpa, wsi->ocport);
#if defined(LWS_WITH_HTTP_BASIC_AUTH)
if (wsi->a.vhost->proxy_basic_auth_token[0])
plen += lws_snprintf((char *)pt->serv_buf + plen, 256,
"Proxy-authorization: basic %s\x0d\x0a",
wsi->a.vhost->proxy_basic_auth_token);
#endif
plen += lws_snprintf((char *)pt->serv_buf + plen, 5,
"\x0d\x0a");
/* lwsl_hexdump_notice(pt->serv_buf, plen); */
/*
* OK from now on we talk via the proxy, so connect to that
*/
if (wsi->stash)
wsi->stash->cis[CIS_ADDRESS] =
wsi->a.vhost->http.http_proxy_address;
else
if (lws_hdr_simple_create(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS,
wsi->a.vhost->http.http_proxy_address))
goto failed;
wsi->c_port = (uint16_t)wsi->a.vhost->http.http_proxy_port;
n = (int)send(wsi->desc.sockfd, (char *)pt->serv_buf,
(unsigned int)plen,
MSG_NOSIGNAL);
if (n < 0) {
lwsl_wsi_debug(wsi, "ERROR writing to proxy socket");
cce = "proxy write failed";
goto failed;
}
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
(int)wsi->a.context->timeout_secs);
wsi->conn_port = wsi->c_port;
lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY);
return wsi;
}
#endif
#endif
/* coverity */
if (!wsi->a.protocol)
return NULL;
#if defined(LWS_WITH_SOCKS5)
if (lwsi_state(wsi) != LRS_ESTABLISHED)
switch (lws_socks5c_greet(wsi, &cce)) {
case -1:
goto failed;
case 1:
return wsi;
default:
break;
}
#endif
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
send_hs:
if (wsi_piggyback &&
!lws_dll2_is_detached(&wsi->dll2_cli_txn_queue)) {
/*
* We are pipelining on an already-established connection...
* we can skip tls establishment.
*
* Set these queued guys to a state where they won't actually
* send their headers until we decide later.
*/
lwsi_set_state(wsi, LRS_H2_WAITING_TO_SEND_HEADERS);
/*
* we can't send our headers directly, because they have to
* be sent when the parent is writeable. The parent will check
* for anybody on his client transaction queue that is in
* LRS_H1C_ISSUE_HANDSHAKE2, and let them write.
*
* If we are trying to do this too early, before the network
* connection has written his own headers, then it will just
* wait in the queue until it's possible to send them.
*/
lws_callback_on_writable(wsi_piggyback);
lwsl_wsi_info(wsi, "waiting to send hdrs (par state 0x%x)",
lwsi_state(wsi_piggyback));
} else {
lwsl_wsi_info(wsi, "%s %s client created own conn "
"(raw %d) vh %s st 0x%x",
wsi->role_ops->name, wsi->a.protocol->name, rawish,
wsi->a.vhost->name, lwsi_state(wsi));
/* we are making our own connection */
if (!rawish
#if defined(LWS_WITH_TLS)
// && (!(wsi->tls.use_ssl & LCCSCF_USE_SSL) || wsi->tls.ssl)
#endif
) {
if (lwsi_state(wsi) != LRS_H1C_ISSUE_HANDSHAKE2)
lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE);
} else {
/* for a method = "RAW" connection, this makes us
* established */
#if defined(LWS_WITH_TLS)// && !defined(LWS_WITH_MBEDTLS)
/* we have connected if we got here */
if (lwsi_state(wsi) == LRS_WAITING_CONNECT &&
(wsi->tls.use_ssl & LCCSCF_USE_SSL)) {
int result;
//lwsi_set_state(wsi, LRS_WAITING_SSL);
/*
* We can retry this... just cook the SSL BIO
* the first time
*/
result = lws_client_create_tls(wsi, &cce, 1);
switch (result) {
case CCTLS_RETURN_DONE:
break;
case CCTLS_RETURN_RETRY:
lwsl_wsi_debug(wsi, "create_tls RETRY");
return wsi;
default:
lwsl_wsi_debug(wsi, "create_tls FAIL");
goto failed;
}
/*
* We succeeded to negotiate a new client tls
* tunnel. If it's h2 alpn, we have arranged
* to send the h2 prefix and set our state to
* LRS_H2_WAITING_TO_SEND_HEADERS already.
*/
lwsl_wsi_notice(wsi, "tls established st 0x%x, "
"client_h2_alpn %d", lwsi_state(wsi),
wsi->client_h2_alpn);
if (lwsi_state(wsi) !=
LRS_H2_WAITING_TO_SEND_HEADERS)
lwsi_set_state(wsi,
LRS_H1C_ISSUE_HANDSHAKE2);
lws_set_timeout(wsi,
PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND,
(int)wsi->a.context->timeout_secs);
#if 0
/* ensure pollin enabled */
if (lws_change_pollfd(wsi, 0, LWS_POLLIN))
lwsl_wsi_notice(wsi,
"unable to set POLLIN");
#endif
goto provoke_service;
}
#endif
/* clear his established timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
m = wsi->role_ops->adoption_cb[0];
if (m) {
n = user_callback_handle_rxflow(
wsi->a.protocol->callback, wsi,
(enum lws_callback_reasons)m,
wsi->user_space, NULL, 0);
if (n < 0) {
lwsl_wsi_info(wsi, "RAW_PROXY_CLI_ADOPT err");
goto failed;
}
}
/* service.c pollout processing wants this */
wsi->hdr_parsing_completed = 1;
#if defined(LWS_ROLE_MQTT)
if (meth && !strcmp(meth, "MQTT")) {
#if defined(LWS_WITH_TLS)
if (wsi->tls.use_ssl & LCCSCF_USE_SSL) {
lwsi_set_state(wsi, LRS_WAITING_SSL);
return wsi;
}
#endif
lwsl_wsi_info(wsi, "settings LRS_MQTTC_IDLE");
lwsi_set_state(wsi, LRS_MQTTC_IDLE);
/*
* provoke service to issue the CONNECT
* directly.
*/
lws_set_timeout(wsi,
PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE,
(int)wsi->a.context->timeout_secs);
assert(lws_socket_is_valid(wsi->desc.sockfd));
pfd.fd = wsi->desc.sockfd;
pfd.events = LWS_POLLIN;
pfd.revents = LWS_POLLOUT;
lwsl_wsi_info(wsi, "going to service fd");
n = lws_service_fd_tsi(wsi->a.context, &pfd, wsi->tsi);
if (n < 0) {
cce = "first service failed";
goto failed;
}
if (n)
/* returns 1 on fail after close wsi */
return NULL;
return wsi;
}
#endif
lwsl_wsi_info(wsi, "setting ESTABLISHED");
lwsi_set_state(wsi, LRS_ESTABLISHED);
return wsi;
}
/*
* provoke service to issue the handshake directly.
*
* we need to do it this way because in the proxy case, this is
* the next state and executed only if and when we get a good
* proxy response inside the state machine... but notice in
* SSL case this may not have sent anything yet with 0 return,
* and won't until many retries from main loop. To stop that
* becoming endless, cover with a timeout.
*/
#if defined(LWS_WITH_TLS) //&& !defined(LWS_WITH_MBEDTLS)
provoke_service:
#endif
lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE,
(int)wsi->a.context->timeout_secs);
assert(lws_socket_is_valid(wsi->desc.sockfd));
pfd.fd = wsi->desc.sockfd;
pfd.events = LWS_POLLIN;
pfd.revents = LWS_POLLIN;
n = lws_service_fd_tsi(wsi->a.context, &pfd, wsi->tsi);
if (n < 0) {
cce = "first service failed";
goto failed;
}
if (n) /* returns 1 on failure after closing wsi */
return NULL;
}
#endif
return wsi;
failed:
lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect4");
return NULL;
}