mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-23 00:00:06 +01:00

Add some exports so the api test can inject results into the parser for live queries, suppressing asking the server but otherwise following the flow. Provide two new suspect responses for injection and parsing in ctest. Add a --cos option to minimal-http-client to force a close after the connection has started the async dns.
932 lines
24 KiB
C
932 lines
24 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 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 int
|
|
lws_get_idlest_tsi(struct lws_context *context)
|
|
{
|
|
unsigned int lowest = ~0u;
|
|
int n = 0, hit = -1;
|
|
|
|
for (; n < context->count_threads; n++) {
|
|
lwsl_cx_debug(context, "%d %d\n", context->pt[n].fds_count,
|
|
context->fd_limit_per_thread - 1);
|
|
if ((unsigned int)context->pt[n].fds_count !=
|
|
context->fd_limit_per_thread - 1 &&
|
|
(unsigned int)context->pt[n].fds_count < lowest) {
|
|
lowest = context->pt[n].fds_count;
|
|
hit = n;
|
|
}
|
|
}
|
|
|
|
return hit;
|
|
}
|
|
|
|
struct lws *
|
|
lws_create_new_server_wsi(struct lws_vhost *vhost, int fixed_tsi, const char *desc)
|
|
{
|
|
struct lws *new_wsi;
|
|
int n = fixed_tsi;
|
|
|
|
if (n < 0)
|
|
n = lws_get_idlest_tsi(vhost->context);
|
|
|
|
if (n < 0) {
|
|
lwsl_vhost_err(vhost, "no space for new conn");
|
|
return NULL;
|
|
}
|
|
|
|
lws_context_lock(vhost->context, __func__);
|
|
new_wsi = __lws_wsi_create_with_role(vhost->context, n, NULL,
|
|
vhost->lc.log_cx);
|
|
lws_context_unlock(vhost->context);
|
|
if (new_wsi == NULL) {
|
|
lwsl_vhost_err(vhost, "OOM");
|
|
return NULL;
|
|
}
|
|
|
|
lws_wsi_fault_timedclose(new_wsi);
|
|
|
|
__lws_lc_tag(vhost->context, &vhost->context->lcg[
|
|
#if defined(LWS_ROLE_H2) || defined(LWS_ROLE_MQTT)
|
|
strcmp(desc, "adopted") ? LWSLCG_WSI_MUX :
|
|
#endif
|
|
LWSLCG_WSI_SERVER], &new_wsi->lc, desc);
|
|
|
|
new_wsi->wsistate |= LWSIFR_SERVER;
|
|
new_wsi->tsi = (char)n;
|
|
lwsl_wsi_debug(new_wsi, "joining vh %s, tsi %d",
|
|
vhost->name, new_wsi->tsi);
|
|
|
|
lws_vhost_bind_wsi(vhost, new_wsi);
|
|
new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;
|
|
new_wsi->retry_policy = vhost->retry_policy;
|
|
|
|
/* initialize the instance struct */
|
|
|
|
lwsi_set_state(new_wsi, LRS_UNCONNECTED);
|
|
new_wsi->hdr_parsing_completed = 0;
|
|
|
|
#ifdef LWS_WITH_TLS
|
|
new_wsi->tls.use_ssl = LWS_SSL_ENABLED(vhost);
|
|
#endif
|
|
|
|
/*
|
|
* these can only be set once the protocol is known
|
|
* we set an un-established connection's protocol pointer
|
|
* to the start of the supported list, so it can look
|
|
* for matching ones during the handshake
|
|
*/
|
|
new_wsi->a.protocol = vhost->protocols;
|
|
new_wsi->user_space = NULL;
|
|
|
|
/*
|
|
* outermost create notification for wsi
|
|
* no user_space because no protocol selection
|
|
*/
|
|
vhost->protocols[0].callback(new_wsi, LWS_CALLBACK_WSI_CREATE, NULL,
|
|
NULL, 0);
|
|
|
|
return new_wsi;
|
|
}
|
|
|
|
|
|
/* if not a socket, it's a raw, non-ssl file descriptor
|
|
* req cx lock, acq pt lock, acq vh lock
|
|
*/
|
|
|
|
static struct lws *
|
|
__lws_adopt_descriptor_vhost1(struct lws_vhost *vh, lws_adoption_type type,
|
|
const char *vh_prot_name, struct lws *parent,
|
|
void *opaque, const char *fi_wsi_name)
|
|
{
|
|
struct lws_context *context = vh->context;
|
|
struct lws_context_per_thread *pt;
|
|
struct lws *new_wsi;
|
|
int n;
|
|
|
|
/*
|
|
* Notice that in SMP case, the wsi may be being created on an
|
|
* entirely different pt / tsi for load balancing. In that case as
|
|
* we initialize it, it may become "live" concurrently unexpectedly...
|
|
*/
|
|
|
|
lws_context_assert_lock_held(vh->context);
|
|
|
|
n = -1;
|
|
if (parent)
|
|
n = parent->tsi;
|
|
new_wsi = lws_create_new_server_wsi(vh, n, "adopted");
|
|
if (!new_wsi)
|
|
return NULL;
|
|
|
|
/* bring in specific fault injection rules early */
|
|
lws_fi_inherit_copy(&new_wsi->fic, &context->fic, "wsi", fi_wsi_name);
|
|
|
|
if (lws_fi(&new_wsi->fic, "createfail")) {
|
|
lws_fi_destroy(&new_wsi->fic);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
new_wsi->a.opaque_user_data = opaque;
|
|
|
|
pt = &context->pt[(int)new_wsi->tsi];
|
|
lws_pt_lock(pt, __func__);
|
|
|
|
if (parent) {
|
|
new_wsi->parent = parent;
|
|
new_wsi->sibling_list = parent->child_list;
|
|
parent->child_list = new_wsi;
|
|
}
|
|
|
|
if (vh_prot_name) {
|
|
new_wsi->a.protocol = lws_vhost_name_to_protocol(new_wsi->a.vhost,
|
|
vh_prot_name);
|
|
if (!new_wsi->a.protocol) {
|
|
lwsl_vhost_err(new_wsi->a.vhost, "Protocol %s not enabled",
|
|
vh_prot_name);
|
|
goto bail;
|
|
}
|
|
if (lws_ensure_user_space(new_wsi)) {
|
|
lwsl_wsi_notice(new_wsi, "OOM");
|
|
goto bail;
|
|
}
|
|
}
|
|
|
|
if (!LWS_SSL_ENABLED(new_wsi->a.vhost) ||
|
|
!(type & LWS_ADOPT_SOCKET))
|
|
type &= (unsigned int)~LWS_ADOPT_ALLOW_SSL;
|
|
|
|
if (lws_role_call_adoption_bind(new_wsi, (int)type, vh_prot_name)) {
|
|
lwsl_wsi_err(new_wsi, "no role for desc type 0x%x", type);
|
|
goto bail;
|
|
}
|
|
|
|
#if defined(LWS_WITH_SERVER)
|
|
if (new_wsi->role_ops) {
|
|
lws_metrics_tag_wsi_add(new_wsi, "role", new_wsi->role_ops->name);
|
|
}
|
|
#endif
|
|
|
|
lws_pt_unlock(pt);
|
|
|
|
/*
|
|
* he's an allocated wsi, but he's not on any fds list or child list,
|
|
* join him to the vhost's list of these kinds of incomplete wsi until
|
|
* he gets another identity (he may do async dns now...)
|
|
*/
|
|
lws_vhost_lock(new_wsi->a.vhost);
|
|
lws_dll2_add_head(&new_wsi->vh_awaiting_socket,
|
|
&new_wsi->a.vhost->vh_awaiting_socket_owner);
|
|
lws_vhost_unlock(new_wsi->a.vhost);
|
|
|
|
return new_wsi;
|
|
|
|
bail:
|
|
lwsl_wsi_notice(new_wsi, "exiting on bail");
|
|
if (parent)
|
|
parent->child_list = new_wsi->sibling_list;
|
|
if (new_wsi->user_space)
|
|
lws_free(new_wsi->user_space);
|
|
|
|
lws_fi_destroy(&new_wsi->fic);
|
|
|
|
lws_pt_unlock(pt);
|
|
__lws_vhost_unbind_wsi(new_wsi); /* req cx, acq vh lock */
|
|
|
|
lws_free(new_wsi);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS)
|
|
|
|
/*
|
|
* If the incoming wsi is bound to a vhost that is a ss server, this creates
|
|
* an accepted ss bound to the wsi.
|
|
*
|
|
* For h1 or raw, we can do the binding here, but for muxed protocols like h2
|
|
* or mqtt we have to do it not on the nwsi but on the stream. And for h2 we
|
|
* start off bound to h1 role, since we don't know if we will upgrade to h2
|
|
* until we meet the server.
|
|
*
|
|
* 1) No tls is assumed to mean no muxed protocol so can do it at adopt.
|
|
*
|
|
* 2) After alpn if not muxed we can do it.
|
|
*
|
|
* 3) For muxed, do it at the nwsi migration and on new stream
|
|
*/
|
|
|
|
int
|
|
lws_adopt_ss_server_accept(struct lws *new_wsi)
|
|
{
|
|
struct lws_context_per_thread *pt =
|
|
&new_wsi->a.context->pt[(int)new_wsi->tsi];
|
|
lws_ss_handle_t *h;
|
|
void *pv, **ppv;
|
|
|
|
if (!new_wsi->a.vhost->ss_handle)
|
|
return 0;
|
|
|
|
pv = (char *)&new_wsi->a.vhost->ss_handle[1];
|
|
|
|
/*
|
|
* Yes... the vhost is pointing to its secure stream representing the
|
|
* server... we want to create an accepted SS and bind it to new_wsi,
|
|
* the info/ssi from the server SS (so the SS callbacks defined there),
|
|
* the opaque_user_data of the server object and the policy of it.
|
|
*/
|
|
|
|
ppv = (void **)((char *)pv +
|
|
new_wsi->a.vhost->ss_handle->info.opaque_user_data_offset);
|
|
|
|
/*
|
|
* indicate we are an accepted connection referencing the
|
|
* server object
|
|
*/
|
|
|
|
new_wsi->a.vhost->ss_handle->info.flags |= LWSSSINFLAGS_SERVER;
|
|
|
|
if (lws_ss_create(new_wsi->a.context, new_wsi->tsi,
|
|
&new_wsi->a.vhost->ss_handle->info,
|
|
*ppv, &h, NULL, NULL)) {
|
|
lwsl_wsi_err(new_wsi, "accept ss creation failed");
|
|
goto fail1;
|
|
}
|
|
|
|
/*
|
|
* We made a fresh accepted SS conn from the server pieces,
|
|
* now bind the wsi... the problem is, this is the nwsi if it's
|
|
* h2.
|
|
*/
|
|
|
|
h->wsi = new_wsi;
|
|
new_wsi->a.opaque_user_data = h;
|
|
h->info.flags |= LWSSSINFLAGS_ACCEPTED;
|
|
/* indicate wsi should invalidate any ss link to it on close */
|
|
new_wsi->for_ss = 1;
|
|
|
|
// lwsl_wsi_notice(new_wsi, "%s: opaq %p, role %s",
|
|
// new_wsi->a.opaque_user_data,
|
|
// new_wsi->role_ops->name);
|
|
|
|
h->policy = new_wsi->a.vhost->ss_handle->policy;
|
|
|
|
/* apply requested socket options */
|
|
if (lws_plat_set_socket_options_ip(new_wsi->desc.sockfd,
|
|
h->policy->priority,
|
|
(LCCSCF_IP_LOW_LATENCY *
|
|
!!(h->policy->flags & LWSSSPOLF_ATTR_LOW_LATENCY)) |
|
|
(LCCSCF_IP_HIGH_THROUGHPUT *
|
|
!!(h->policy->flags & LWSSSPOLF_ATTR_HIGH_THROUGHPUT)) |
|
|
(LCCSCF_IP_HIGH_RELIABILITY *
|
|
!!(h->policy->flags & LWSSSPOLF_ATTR_HIGH_RELIABILITY)) |
|
|
(LCCSCF_IP_LOW_COST *
|
|
!!(h->policy->flags & LWSSSPOLF_ATTR_LOW_COST))))
|
|
lwsl_wsi_warn(new_wsi, "unable to set ip options");
|
|
|
|
/*
|
|
* add us to the list of clients that came in from the server
|
|
*/
|
|
|
|
lws_pt_lock(pt, __func__);
|
|
lws_dll2_add_tail(&h->cli_list, &new_wsi->a.vhost->ss_handle->src_list);
|
|
lws_pt_unlock(pt);
|
|
|
|
/*
|
|
* Let's give it appropriate state notifications
|
|
*/
|
|
|
|
if (lws_ss_event_helper(h, LWSSSCS_CREATING))
|
|
goto fail;
|
|
if (lws_ss_event_helper(h, LWSSSCS_CONNECTING))
|
|
goto fail;
|
|
|
|
/* defer CONNECTED until we see if he is upgrading */
|
|
|
|
// if (lws_ss_event_helper(h, LWSSSCS_CONNECTED))
|
|
// goto fail;
|
|
|
|
// lwsl_notice("%s: accepted ss complete, pcol %s\n", __func__,
|
|
// new_wsi->a.protocol->name);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
lws_ss_destroy(&h);
|
|
fail1:
|
|
return 1;
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
static struct lws *
|
|
lws_adopt_descriptor_vhost2(struct lws *new_wsi, lws_adoption_type type,
|
|
lws_sock_file_fd_type fd)
|
|
{
|
|
struct lws_context_per_thread *pt =
|
|
&new_wsi->a.context->pt[(int)new_wsi->tsi];
|
|
int n;
|
|
|
|
/* enforce that every fd is nonblocking */
|
|
|
|
if (type & LWS_ADOPT_SOCKET) {
|
|
if (lws_plat_set_nonblocking(fd.sockfd)) {
|
|
lwsl_wsi_err(new_wsi, "unable to set sockfd %d nonblocking",
|
|
fd.sockfd);
|
|
goto fail;
|
|
}
|
|
}
|
|
#if !defined(WIN32)
|
|
else
|
|
if (lws_plat_set_nonblocking(fd.filefd)) {
|
|
lwsl_wsi_err(new_wsi, "unable to set filefd nonblocking");
|
|
goto fail;
|
|
}
|
|
#endif
|
|
|
|
new_wsi->desc = fd;
|
|
|
|
if (!LWS_SSL_ENABLED(new_wsi->a.vhost) ||
|
|
!(type & LWS_ADOPT_SOCKET))
|
|
type &= (unsigned int)~LWS_ADOPT_ALLOW_SSL;
|
|
|
|
/*
|
|
* A new connection was accepted. Give the user a chance to
|
|
* set properties of the newly created wsi. There's no protocol
|
|
* selected yet so we issue this to the vhosts's default protocol,
|
|
* itself by default protocols[0]
|
|
*/
|
|
new_wsi->wsistate |= LWSIFR_SERVER;
|
|
n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED;
|
|
if (new_wsi->role_ops->adoption_cb[lwsi_role_server(new_wsi)])
|
|
n = new_wsi->role_ops->adoption_cb[lwsi_role_server(new_wsi)];
|
|
|
|
if (new_wsi->a.context->event_loop_ops->sock_accept)
|
|
if (new_wsi->a.context->event_loop_ops->sock_accept(new_wsi))
|
|
goto fail;
|
|
|
|
#if LWS_MAX_SMP > 1
|
|
/*
|
|
* Caution: after this point the wsi is live on its service thread
|
|
* which may be concurrent to this. We mark the wsi as still undergoing
|
|
* init in another pt so the assigned pt leaves it alone.
|
|
*/
|
|
new_wsi->undergoing_init_from_other_pt = 1;
|
|
#endif
|
|
|
|
if (!(type & LWS_ADOPT_ALLOW_SSL)) {
|
|
lws_pt_lock(pt, __func__);
|
|
if (__insert_wsi_socket_into_fds(new_wsi->a.context, new_wsi)) {
|
|
lws_pt_unlock(pt);
|
|
lwsl_wsi_err(new_wsi, "fail inserting socket");
|
|
goto fail;
|
|
}
|
|
lws_pt_unlock(pt);
|
|
}
|
|
#if defined(LWS_WITH_SERVER)
|
|
else
|
|
if (lws_server_socket_service_ssl(new_wsi, fd.sockfd, 0)) {
|
|
lwsl_wsi_info(new_wsi, "fail ssl negotiation");
|
|
|
|
goto fail;
|
|
}
|
|
#endif
|
|
|
|
lws_vhost_lock(new_wsi->a.vhost);
|
|
/* he has fds visibility now, remove from vhost orphan list */
|
|
lws_dll2_remove(&new_wsi->vh_awaiting_socket);
|
|
lws_vhost_unlock(new_wsi->a.vhost);
|
|
|
|
/*
|
|
* by deferring callback to this point, after insertion to fds,
|
|
* lws_callback_on_writable() can work from the callback
|
|
*/
|
|
if ((new_wsi->a.protocol->callback)(new_wsi, (enum lws_callback_reasons)n, new_wsi->user_space,
|
|
NULL, 0))
|
|
goto fail;
|
|
|
|
/* role may need to do something after all adoption completed */
|
|
|
|
lws_role_call_adoption_bind(new_wsi, (int)type | _LWS_ADOPT_FINISH,
|
|
new_wsi->a.protocol->name);
|
|
|
|
#if defined(LWS_WITH_SERVER) && defined(LWS_WITH_SECURE_STREAMS)
|
|
/*
|
|
* Did we come from an accepted client connection to a ss server?
|
|
*
|
|
* !!! For mux protocols, this will cause an additional inactive ss
|
|
* representing the nwsi. Doing that allows us to support both h1
|
|
* (here) and h2 (at __lws_wsi_server_new())
|
|
*/
|
|
|
|
lwsl_wsi_info(new_wsi, "vhost %s", new_wsi->a.vhost->lc.gutag);
|
|
|
|
if (lws_adopt_ss_server_accept(new_wsi))
|
|
goto fail;
|
|
#endif
|
|
|
|
#if LWS_MAX_SMP > 1
|
|
/* its actual pt can service it now */
|
|
|
|
new_wsi->undergoing_init_from_other_pt = 0;
|
|
#endif
|
|
|
|
lws_cancel_service_pt(new_wsi);
|
|
|
|
return new_wsi;
|
|
|
|
fail:
|
|
if (type & LWS_ADOPT_SOCKET)
|
|
lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS,
|
|
"adopt skt fail");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/* if not a socket, it's a raw, non-ssl file descriptor */
|
|
|
|
struct lws *
|
|
lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type,
|
|
lws_sock_file_fd_type fd, const char *vh_prot_name,
|
|
struct lws *parent)
|
|
{
|
|
lws_adopt_desc_t info;
|
|
|
|
memset(&info, 0, sizeof(info));
|
|
|
|
info.vh = vh;
|
|
info.type = type;
|
|
info.fd = fd;
|
|
info.vh_prot_name = vh_prot_name;
|
|
info.parent = parent;
|
|
|
|
return lws_adopt_descriptor_vhost_via_info(&info);
|
|
}
|
|
|
|
struct lws *
|
|
lws_adopt_descriptor_vhost_via_info(const lws_adopt_desc_t *info)
|
|
{
|
|
socklen_t slen = sizeof(lws_sockaddr46);
|
|
struct lws *new_wsi;
|
|
|
|
#if defined(LWS_WITH_PEER_LIMITS)
|
|
struct lws_peer *peer = NULL;
|
|
|
|
if (info->type & LWS_ADOPT_SOCKET) {
|
|
peer = lws_get_or_create_peer(info->vh, info->fd.sockfd);
|
|
|
|
if (peer && info->vh->context->ip_limit_wsi &&
|
|
peer->count_wsi >= info->vh->context->ip_limit_wsi) {
|
|
lwsl_info("Peer reached wsi limit %d\n",
|
|
info->vh->context->ip_limit_wsi);
|
|
if (info->vh->context->pl_notify_cb)
|
|
info->vh->context->pl_notify_cb(
|
|
info->vh->context,
|
|
info->fd.sockfd,
|
|
&peer->sa46);
|
|
compatible_close(info->fd.sockfd);
|
|
return NULL;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
lws_context_lock(info->vh->context, __func__);
|
|
|
|
new_wsi = __lws_adopt_descriptor_vhost1(info->vh, info->type,
|
|
info->vh_prot_name, info->parent,
|
|
info->opaque, info->fi_wsi_name);
|
|
if (!new_wsi) {
|
|
if (info->type & LWS_ADOPT_SOCKET)
|
|
compatible_close(info->fd.sockfd);
|
|
goto bail;
|
|
}
|
|
|
|
if (info->type & LWS_ADOPT_SOCKET &&
|
|
getpeername(info->fd.sockfd, (struct sockaddr *)&new_wsi->sa46_peer,
|
|
&slen) < 0)
|
|
lwsl_info("%s: getpeername failed\n", __func__);
|
|
|
|
#if defined(LWS_WITH_PEER_LIMITS)
|
|
if (peer)
|
|
lws_peer_add_wsi(info->vh->context, peer, new_wsi);
|
|
#endif
|
|
|
|
new_wsi = lws_adopt_descriptor_vhost2(new_wsi, info->type, info->fd);
|
|
|
|
bail:
|
|
lws_context_unlock(info->vh->context);
|
|
|
|
return new_wsi;
|
|
}
|
|
|
|
struct lws *
|
|
lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd)
|
|
{
|
|
lws_sock_file_fd_type fd;
|
|
|
|
fd.sockfd = accept_fd;
|
|
return lws_adopt_descriptor_vhost(vh, LWS_ADOPT_SOCKET |
|
|
LWS_ADOPT_HTTP | LWS_ADOPT_ALLOW_SSL, fd, NULL, NULL);
|
|
}
|
|
|
|
struct lws *
|
|
lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd)
|
|
{
|
|
return lws_adopt_socket_vhost(context->vhost_list, accept_fd);
|
|
}
|
|
|
|
/* Common read-buffer adoption for lws_adopt_*_readbuf */
|
|
static struct lws*
|
|
adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len)
|
|
{
|
|
struct lws_context_per_thread *pt;
|
|
struct lws_pollfd *pfd;
|
|
int n;
|
|
|
|
if (!wsi)
|
|
return NULL;
|
|
|
|
if (!readbuf || len == 0)
|
|
return wsi;
|
|
|
|
if (wsi->position_in_fds_table == LWS_NO_FDS_POS)
|
|
return wsi;
|
|
|
|
pt = &wsi->a.context->pt[(int)wsi->tsi];
|
|
|
|
n = lws_buflist_append_segment(&wsi->buflist, (const uint8_t *)readbuf,
|
|
len);
|
|
if (n < 0)
|
|
goto bail;
|
|
if (n)
|
|
lws_dll2_add_head(&wsi->dll_buflist, &pt->dll_buflist_owner);
|
|
|
|
/*
|
|
* we can't process the initial read data until we can attach an ah.
|
|
*
|
|
* if one is available, get it and place the data in his ah rxbuf...
|
|
* wsi with ah that have pending rxbuf get auto-POLLIN service.
|
|
*
|
|
* no autoservice because we didn't get a chance to attach the
|
|
* readbuf data to wsi or ah yet, and we will do it next if we get
|
|
* the ah.
|
|
*/
|
|
if (wsi->http.ah || !lws_header_table_attach(wsi, 0)) {
|
|
|
|
lwsl_notice("%s: calling service on readbuf ah\n", __func__);
|
|
|
|
/*
|
|
* unlike a normal connect, we have the headers already
|
|
* (or the first part of them anyway).
|
|
* libuv won't come back and service us without a network
|
|
* event, so we need to do the header service right here.
|
|
*/
|
|
pfd = &pt->fds[wsi->position_in_fds_table];
|
|
pfd->revents |= LWS_POLLIN;
|
|
lwsl_err("%s: calling service\n", __func__);
|
|
if (lws_service_fd_tsi(wsi->a.context, pfd, wsi->tsi))
|
|
/* service closed us */
|
|
return NULL;
|
|
|
|
return wsi;
|
|
}
|
|
lwsl_err("%s: deferring handling ah\n", __func__);
|
|
|
|
return wsi;
|
|
|
|
bail:
|
|
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS,
|
|
"adopt skt readbuf fail");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#if defined(LWS_WITH_UDP)
|
|
#if defined(LWS_WITH_CLIENT)
|
|
|
|
/*
|
|
* This is the ASYNC_DNS callback target for udp client, it's analogous to
|
|
* connect3()
|
|
*/
|
|
|
|
static struct lws *
|
|
lws_create_adopt_udp2(struct lws *wsi, const char *ads,
|
|
const struct addrinfo *r, int n, void *opaque)
|
|
{
|
|
lws_sock_file_fd_type sock;
|
|
int bc = 1, m;
|
|
|
|
assert(wsi);
|
|
|
|
if (ads && (n < 0 || !r)) {
|
|
/*
|
|
* DNS lookup failed: there are no usable results. Fail the
|
|
* overall connection request.
|
|
*/
|
|
lwsl_notice("%s: bad: n %d, r %p\n", __func__, n, r);
|
|
|
|
goto bail;
|
|
}
|
|
|
|
m = lws_sort_dns(wsi, r);
|
|
#if defined(LWS_WITH_SYS_ASYNC_DNS)
|
|
lws_async_dns_freeaddrinfo(&r);
|
|
#else
|
|
freeaddrinfo((struct addrinfo *)r);
|
|
#endif
|
|
if (m)
|
|
goto bail;
|
|
|
|
while (lws_dll2_get_head(&wsi->dns_sorted_list)) {
|
|
lws_dns_sort_t *s = lws_container_of(
|
|
lws_dll2_get_head(&wsi->dns_sorted_list),
|
|
lws_dns_sort_t, list);
|
|
|
|
/*
|
|
* Remove it from the head, but don't free it yet... we are
|
|
* taking responsibility to free it
|
|
*/
|
|
lws_dll2_remove(&s->list);
|
|
|
|
/*
|
|
* We have done the dns lookup, identify the result we want
|
|
* if any, and then complete the adoption by binding wsi to
|
|
* socket opened on it.
|
|
*
|
|
* Ignore the weak assumptions about protocol driven by port
|
|
* number and force to DGRAM / UDP since that's what this
|
|
* function is for.
|
|
*/
|
|
|
|
#if !defined(__linux__)
|
|
sock.sockfd = socket(s->dest.sa4.sin_family,
|
|
SOCK_DGRAM, IPPROTO_UDP);
|
|
#else
|
|
/* PF_PACKET is linux-only */
|
|
sock.sockfd = socket(wsi->pf_packet ? PF_PACKET :
|
|
s->dest.sa4.sin_family,
|
|
SOCK_DGRAM, wsi->pf_packet ?
|
|
htons(0x800) : IPPROTO_UDP);
|
|
#endif
|
|
if (sock.sockfd == LWS_SOCK_INVALID)
|
|
goto resume;
|
|
|
|
/* ipv6 udp!!! */
|
|
|
|
if (s->af == AF_INET)
|
|
s->dest.sa4.sin_port = htons(wsi->c_port);
|
|
#if defined(LWS_WITH_IPV6)
|
|
else
|
|
s->dest.sa6.sin6_port = htons(wsi->c_port);
|
|
#endif
|
|
|
|
if (setsockopt(sock.sockfd, SOL_SOCKET, SO_REUSEADDR,
|
|
(const char *)&bc, sizeof(bc)) < 0)
|
|
lwsl_err("%s: failed to set reuse\n", __func__);
|
|
|
|
if (wsi->do_broadcast &&
|
|
setsockopt(sock.sockfd, SOL_SOCKET, SO_BROADCAST,
|
|
(const char *)&bc, sizeof(bc)) < 0)
|
|
lwsl_err("%s: failed to set broadcast\n", __func__);
|
|
|
|
/* Bind the udp socket to a particular network interface */
|
|
|
|
if (opaque &&
|
|
lws_plat_BINDTODEVICE(sock.sockfd, (const char *)opaque))
|
|
goto resume;
|
|
|
|
if (wsi->do_bind &&
|
|
bind(sock.sockfd, sa46_sockaddr(&s->dest),
|
|
#if defined(_WIN32)
|
|
(int)sa46_socklen(&s->dest)
|
|
#else
|
|
sizeof(struct sockaddr)
|
|
#endif
|
|
) == -1) {
|
|
lwsl_err("%s: bind failed\n", __func__);
|
|
goto resume;
|
|
}
|
|
|
|
if (!wsi->do_bind && !wsi->pf_packet) {
|
|
#if !defined(__APPLE__)
|
|
if (connect(sock.sockfd, sa46_sockaddr(&s->dest),
|
|
sa46_socklen(&s->dest)) == -1 &&
|
|
errno != EADDRNOTAVAIL /* openbsd */ ) {
|
|
lwsl_err("%s: conn fd %d fam %d %s:%u failed "
|
|
"errno %d\n", __func__, sock.sockfd,
|
|
s->dest.sa4.sin_family,
|
|
ads ? ads : "null", wsi->c_port,
|
|
LWS_ERRNO);
|
|
compatible_close(sock.sockfd);
|
|
goto resume;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if (wsi->udp)
|
|
wsi->udp->sa46 = s->dest;
|
|
wsi->sa46_peer = s->dest;
|
|
|
|
/* we connected: complete the udp socket adoption flow */
|
|
|
|
#if defined(LWS_WITH_SYS_ASYNC_DNS)
|
|
if (wsi->a.context->async_dns.wsi == wsi)
|
|
wsi->a.context->async_dns.dns_server_connected = 1;
|
|
#endif
|
|
|
|
lws_free(s);
|
|
lws_addrinfo_clean(wsi);
|
|
return lws_adopt_descriptor_vhost2(wsi,
|
|
LWS_ADOPT_RAW_SOCKET_UDP, sock);
|
|
|
|
resume:
|
|
lws_free(s);
|
|
}
|
|
|
|
lwsl_err("%s: unable to create INET socket %d\n", __func__, LWS_ERRNO);
|
|
lws_addrinfo_clean(wsi);
|
|
|
|
#if defined(LWS_WITH_SYS_ASYNC_DNS)
|
|
if (wsi->a.context->async_dns.wsi == wsi)
|
|
lws_async_dns_drop_server(wsi->a.context);
|
|
#endif
|
|
|
|
bail:
|
|
|
|
/* caller must close */
|
|
|
|
return NULL;
|
|
}
|
|
|
|
struct lws *
|
|
lws_create_adopt_udp(struct lws_vhost *vhost, const char *ads, int port,
|
|
int flags, const char *protocol_name, const char *ifname,
|
|
struct lws *parent_wsi, void *opaque,
|
|
const lws_retry_bo_t *retry_policy, const char *fi_wsi_name)
|
|
{
|
|
#if !defined(LWS_PLAT_OPTEE)
|
|
struct lws *wsi;
|
|
int n;
|
|
|
|
lwsl_info("%s: %s:%u\n", __func__, ads ? ads : "null", port);
|
|
|
|
/* create the logical wsi without any valid fd */
|
|
|
|
lws_context_lock(vhost->context, __func__);
|
|
|
|
wsi = __lws_adopt_descriptor_vhost1(vhost, LWS_ADOPT_SOCKET |
|
|
LWS_ADOPT_RAW_SOCKET_UDP,
|
|
protocol_name, parent_wsi, opaque,
|
|
fi_wsi_name);
|
|
|
|
lws_context_unlock(vhost->context);
|
|
if (!wsi) {
|
|
lwsl_err("%s: udp wsi creation failed\n", __func__);
|
|
goto bail;
|
|
}
|
|
|
|
// lwsl_notice("%s: role %s\n", __func__, wsi->role_ops->name);
|
|
|
|
wsi->do_bind = !!(flags & LWS_CAUDP_BIND);
|
|
wsi->do_broadcast = !!(flags & LWS_CAUDP_BROADCAST);
|
|
wsi->pf_packet = !!(flags & LWS_CAUDP_PF_PACKET);
|
|
wsi->c_port = (uint16_t)(unsigned int)port;
|
|
if (retry_policy)
|
|
wsi->retry_policy = retry_policy;
|
|
else
|
|
wsi->retry_policy = vhost->retry_policy;
|
|
|
|
#if !defined(LWS_WITH_SYS_ASYNC_DNS)
|
|
{
|
|
struct addrinfo *r, h;
|
|
char buf[16];
|
|
|
|
memset(&h, 0, sizeof(h));
|
|
h.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */
|
|
h.ai_socktype = SOCK_DGRAM;
|
|
h.ai_protocol = IPPROTO_UDP;
|
|
#if defined(AI_PASSIVE)
|
|
h.ai_flags = AI_PASSIVE;
|
|
#endif
|
|
#ifdef AI_ADDRCONFIG
|
|
h.ai_flags |= AI_ADDRCONFIG;
|
|
#endif
|
|
|
|
/* if the dns lookup is synchronous, do the whole thing now */
|
|
lws_snprintf(buf, sizeof(buf), "%u", port);
|
|
n = getaddrinfo(ads, buf, &h, &r);
|
|
if (n) {
|
|
#if !defined(LWS_PLAT_FREERTOS)
|
|
lwsl_info("%s: getaddrinfo error: %s\n", __func__,
|
|
gai_strerror(n));
|
|
#else
|
|
lwsl_info("%s: getaddrinfo error: %s\n", __func__,
|
|
strerror(n));
|
|
#endif
|
|
//freeaddrinfo(r);
|
|
goto bail1;
|
|
}
|
|
/*
|
|
* With synchronous dns, complete it immediately after the
|
|
* blocking dns lookup finished... free r when connect either
|
|
* completed or failed
|
|
*/
|
|
wsi = lws_create_adopt_udp2(wsi, ads, r, 0, NULL);
|
|
|
|
return wsi;
|
|
}
|
|
#else
|
|
if (ads) {
|
|
/*
|
|
* with async dns, use the wsi as the point about which to do
|
|
* the dns lookup and have it call the second part when it's
|
|
* done.
|
|
*
|
|
* Keep a refcount on the results and free it when we connected
|
|
* or definitively failed.
|
|
*
|
|
* Notice wsi has no socket at this point (we don't know what
|
|
* kind to ask for until we get the dns back). But it is bound
|
|
* to a vhost and can be cleaned up from that at vhost destroy.
|
|
*/
|
|
n = lws_async_dns_query(vhost->context, 0, ads,
|
|
LWS_ADNS_RECORD_A,
|
|
lws_create_adopt_udp2, wsi,
|
|
(void *)ifname, NULL);
|
|
// lwsl_notice("%s: dns query returned %d\n", __func__, n);
|
|
if (n == LADNS_RET_FAILED) {
|
|
lwsl_err("%s: async dns failed\n", __func__);
|
|
wsi = NULL;
|
|
/*
|
|
* It was already closed by calling callback with error
|
|
* from lws_async_dns_query()
|
|
*/
|
|
goto bail;
|
|
}
|
|
} else {
|
|
lwsl_debug("%s: udp adopt has no ads\n", __func__);
|
|
wsi = lws_create_adopt_udp2(wsi, ads, NULL, 0, (void *)ifname);
|
|
}
|
|
|
|
/* dns lookup is happening asynchronously */
|
|
|
|
// lwsl_notice("%s: returning wsi %p\n", __func__, wsi);
|
|
|
|
return wsi;
|
|
#endif
|
|
#if !defined(LWS_WITH_SYS_ASYNC_DNS)
|
|
bail1:
|
|
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "adopt udp2 fail");
|
|
wsi = NULL;
|
|
#endif
|
|
bail:
|
|
return wsi;
|
|
#else
|
|
return NULL;
|
|
#endif
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
struct lws *
|
|
lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd,
|
|
const char *readbuf, size_t len)
|
|
{
|
|
return adopt_socket_readbuf(lws_adopt_socket(context, accept_fd),
|
|
readbuf, len);
|
|
}
|
|
|
|
struct lws *
|
|
lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost,
|
|
lws_sockfd_type accept_fd,
|
|
const char *readbuf, size_t len)
|
|
{
|
|
return adopt_socket_readbuf(lws_adopt_socket_vhost(vhost, accept_fd),
|
|
readbuf, len);
|
|
}
|