/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 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"
#include "private-lib-async-dns.h"

static const uint32_t botable[] = { 300, 500, 700, 1250, 5000
				/* in case everything just dog slow */ };
static const lws_retry_bo_t retry_policy = {
	botable, LWS_ARRAY_SIZE(botable), LWS_RETRY_CONCEAL_ALWAYS,
	/* don't conceal after the last table entry */ 0, 0, 20 };

void
lws_adns_q_destroy(lws_adns_q_t *q)
{
	lws_metrics_caliper_report(q->metcal, (char)q->go_nogo);

	lws_sul_cancel(&q->sul);
	lws_sul_cancel(&q->write_sul);
	lws_dll2_remove(&q->list);

	if (q->wsi_tcp) {
		q->wsi_tcp->a.opaque_user_data = NULL;
		lws_set_timeout(q->wsi_tcp, 1, LWS_TO_KILL_ASYNC);
		q->wsi_tcp = NULL;
	}

	if (q->tcp_rx_buf) {
		lws_free(q->tcp_rx_buf);
		q->tcp_rx_buf = NULL;
	}

	if (q->firstcache) {
		q->firstcache->refcount--;
		q->firstcache = NULL;
	}

	lws_free(q);
}

lws_adns_q_t *
lws_adns_get_query(lws_async_dns_t *dns, adns_query_type_t qtype,
		   uint16_t tid, const char *name)
{
	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&dns->waiting)) {
		lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);
		int n = 0, nmax = q->tids >= LWS_ARRAY_SIZE(q->tid) ?
				  LWS_ARRAY_SIZE(q->tid) : q->tids;

		if (!name) {
			for (n = 0; n < nmax; n++) {
				if ((tid & 0xfffe) == (q->tid[n] & 0xfffe))
					return q;
			}
		}

		if (name) {
			int type_match = 0;
			if (q->qtype == LWS_ADNS_RECORD_A || q->qtype == LWS_ADNS_RECORD_AAAA)
				type_match = (q->qtype == ((tid & 1) ? LWS_ADNS_RECORD_AAAA : LWS_ADNS_RECORD_A));
			else
				type_match = (q->qtype == qtype) || (qtype == 0);

			if (type_match && !strcasecmp(name, (const char *)&q[1]))
				return q;
		}

	} lws_end_foreach_dll_safe(d, d1);

	return NULL;
}

void
lws_async_dns_drop_server(lws_async_dns_server_t *dsrv)
{
	if (!dsrv->wsi)
		return;

	dsrv->dns_server_set = 0;
	lws_set_timeout(dsrv->wsi, 1, LWS_TO_KILL_ASYNC);
	dsrv->wsi->a.opaque_user_data = NULL;
	dsrv->wsi = NULL;
	dsrv->dns_server_connected = 0;
}

lws_async_dns_retcode_t
lws_async_dns_complete(lws_adns_q_t *q, lws_adns_cache_t *c)
{
	lws_async_dns_retcode_t ret = LADNS_RET_FOUND;

	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&q->wsi_adns)) {
		struct lws *w = lws_container_of(d, struct lws, adns);

		lws_dll2_remove(d);
		if (c && c->results) {
			lwsl_wsi_debug(w, "q: %p, c: %p, refcount %d -> %d",
				    q, c, c->refcount, c->refcount + 1);
			c->refcount++;
		}
		lws_set_timeout(w, NO_PENDING_TIMEOUT, 0);
		if (w->adns_cb(w, (const char *)&q[1], c ? c->results : NULL,
#if defined(LWS_WITH_SYS_ASYNC_DNS_DNSSEC)
				(c ? 0 : LADNS_RET_FAILED) | (q->dnssec_valid ? LWS_ADNS_DNSSEC_VALID : 0),
#else
				(c ? 0 : LADNS_RET_FAILED),
#endif
				q->opaque) == NULL) {
			lwsl_info("%s: failed\n", __func__);
			ret = LADNS_RET_FAILED_WSI_CLOSED;
		}

	} lws_end_foreach_dll_safe(d, d1);

	if (q->standalone_cb) {
		if (c && c->results)
			c->refcount++;

		if (q->standalone_cb(NULL, (const char *)&q[1],
				 c ? c->results : NULL,
#if defined(LWS_WITH_SYS_ASYNC_DNS_DNSSEC)
				 (c ? 0 : LADNS_RET_FAILED) | (q->dnssec_valid ? LWS_ADNS_DNSSEC_VALID : 0),
#else
				 (c ? 0 : LADNS_RET_FAILED),
#endif
				 q->opaque) == NULL)
			ret = LADNS_RET_FAILED_WSI_CLOSED;
	}

	lws_adns_dump(q->dns);

	return ret;
}

static void
lws_async_dns_sul_cb_retry(struct lws_sorted_usec_list *sul)
{
	lws_adns_q_t *q = lws_container_of(sul, lws_adns_q_t, sul);

	lwsl_wsi_info(q->dsrv ? q->dsrv->wsi : NULL, "in");
	lws_adns_dump(q->dns);

	if (q->dsrv && q->dsrv->wsi) {
		q->is_retry = 1;
		lws_callback_on_writable(q->dsrv->wsi);
	}
}

static void
lws_async_dns_writeable(struct lws *wsi, lws_adns_q_t *q)
{
	uint8_t pkt[LWS_PRE + DNS_PACKET_LEN], *e = &pkt[sizeof(pkt)], *p, *pl;
	int m, n, which;
	const char *name;

	/*
	 * We managed to get to the point of being WRITEABLE, which is not a
	 * given if no routes.  So call off the write_sul timeout for that.
	 */
	lws_sul_cancel(&q->write_sul);

	if (!q->is_retry && q->sent[0]
#if defined(LWS_WITH_IPV6)
	         && q->sent[0] == q->sent[1]
#endif
	)
		return;

	q->is_retry = 0;

	/*
	 * UDP is not reliable, it can be locally dropped, or dropped
	 * by any intermediary or the remote peer.  So even though we
	 * will do the write in a moment, we schedule another request
	 * for rewrite according to the wsi retry policy.
	 *
	 * If the result came before, we'll cancel it as part of the
	 * wsi close.
	 *
	 * If we have already reached the end of our concealed retries
	 * in the policy, just close without another write.
	 */
	if (lws_dll2_is_detached(&q->sul.list) &&
	    lws_retry_sul_schedule_retry_wsi(wsi, &q->sul,
				       lws_async_dns_sul_cb_retry, &q->retry)) {
		/* we have reached the end of our concealed retries */
		lwsl_wsi_info(wsi, "failing query");
		/*
		 * our policy is to force reloading the dns server info
		 * if our connection ever timed out, in case it or the
		 * routing state changed
		 */

		lws_async_dns_drop_server(q->dsrv);
		goto qfail;
	}

	name = (const char *)&q[1];

	p = &pkt[LWS_PRE];
	memset(p, 0, DHO_SIZEOF);

#if !defined(LWS_WITH_IPV4)
	which = 1;
	q->sent[0]++;
	q->sent[1]++;
	q->asked = 2; /* AAAA response bit is 2 (1 << 1) */
#elif defined(LWS_WITH_IPV6)
	if (q->qtype != LWS_ADNS_RECORD_A && q->qtype != LWS_ADNS_RECORD_AAAA) {
		which = 0;
		q->sent[0]++;
		q->sent[1]++; /* match states to avoid ipv6 duplicate writeable loop */
		q->asked = 1;
	} else if (q->ipv4_only && q->qtype == LWS_ADNS_RECORD_A) {
		which = 0;
		q->sent[0]++;
		q->sent[1]++; /* also mark ipv6 state sent to avoid AAAA query */
		q->asked = 1;
	} else if (!q->responded) {
		/* must pick between ipv6 and ipv4 */
		which = q->sent[0] > q->sent[1];
		q->sent[which]++;
		q->asked = 3; /* want results for 4 & 6 before done */
	} else
		which = q->responded & 1;
#else
	which = 0;
	q->asked = 1;
#endif

	lwsl_wsi_info(wsi, "%s, which %d", name, which);

	/* we hack b0 of the tid to be 0 = A, 1 = AAAA */

	lws_ser_wu16be(&p[DHO_TID],
#if defined(LWS_WITH_IPV6)
			which ? (LADNS_MOST_RECENT_TID(q) | 1) :
#endif
					LADNS_MOST_RECENT_TID(q));

#if defined(LWS_WITH_SYS_ASYNC_DNS_DNSSEC)
	lws_ser_wu16be(&p[DHO_FLAGS], (1 << 8) | (1 << 4)); /* RD + CD */
#else
	lws_ser_wu16be(&p[DHO_FLAGS], (1 << 8));
#endif

	lws_ser_wu16be(&p[DHO_NQUERIES], 1);
#if defined(LWS_WITH_SYS_ASYNC_DNS_DNSSEC)
	if (q->dns->dnssec_mode || q->want_dnssec)
		lws_ser_wu16be(&p[DHO_NOTHER], 1); /* 1 additional record (EDNS0 OPT) */
#endif

	p += DHO_SIZEOF;

	/* start of label-formatted qname */

	pl = p++;

	do {
		if (*name == '.' || !*name) {
			int l = (int)lws_ptr_diff(p, pl + 1);
			if (l == 0) {
				/* skip empty label (e.g. trailing dot) */
				if (!*name++)
					break;
				continue;
			}
			*pl = (uint8_t)l;
			pl = p;
			*p++ = 0; /* also serves as terminal length */
			if (!*name++)
				break;
		} else
			*p++ = (uint8_t)*name++;
	} while (p + 6 < e);

	if (p + 6 >= e) {
		assert(0);
		lwsl_wsi_err(wsi, "name too big");
		goto qfail;
	}

	if (q->qtype == LWS_ADNS_RECORD_A || q->qtype == LWS_ADNS_RECORD_AAAA)
		lws_ser_wu16be(p, which ? LWS_ADNS_RECORD_AAAA : LWS_ADNS_RECORD_A);
	else
		lws_ser_wu16be(p, q->qtype);
	p += 2;

	lws_ser_wu16be(p, 1); /* IN class */
	p += 2;

#if defined(LWS_WITH_SYS_ASYNC_DNS_DNSSEC)
	if (q->dns->dnssec_mode || q->want_dnssec) {
		/* Append EDNS0 OPT record with DO (DNSSEC-OK) bit */
		*p++ = 0; /* Name: root */
		lws_ser_wu16be(p, 41); /* Type: OPT (41) */
		p += 2;
		lws_ser_wu16be(p, LWS_PRE + DNS_PACKET_LEN); /* UDP payload size */
		p += 2;
		*p++ = 0; /* Extended RCODE */
		*p++ = 0; /* Version */
		lws_ser_wu16be(p, 0x8000); /* Flags: DO bit (bit 0 of 16-bit flags) */
		p += 2;
		lws_ser_wu16be(p, 0); /* RDLEN: 0 */
		p += 2;
	}
#endif

	assert(p < pkt + sizeof(pkt) - LWS_PRE);
	n = lws_ptr_diff(p, pkt + LWS_PRE);

	if (q->broadsiding) {
		m = wsi->udp ? n : n + 2; /* assume success unless primary fails */
		lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&q->dns->nameservers)) {
			lws_async_dns_server_t *s = lws_container_of(d, lws_async_dns_server_t, list);
			if (s->wsi) {
				int m2;
				if (!s->wsi->udp) {
					lws_ser_wu16be(pkt + LWS_PRE - 2, (uint16_t)n);
					m2 = lws_write(s->wsi, pkt + LWS_PRE - 2, (unsigned int)n + 2, 0);
				} else
					m2 = lws_write(s->wsi, pkt + LWS_PRE, (unsigned int)n, 0);

				if (s->wsi == wsi)
					m = m2;
			}
		} lws_end_foreach_dll_safe(d, d1);
	} else {
		if (!wsi->udp) {
			lws_ser_wu16be(pkt + LWS_PRE - 2, (uint16_t)n);
			m = lws_write(wsi, pkt + LWS_PRE - 2, (unsigned int)n + 2, 0);
		} else
			m = lws_write(wsi, pkt + LWS_PRE, (unsigned int)n, 0);
	}

	if (m != (wsi->udp ? n : n + 2)) {
		lwsl_wsi_notice(wsi, "dns write failed %d %d errno %d",
			    m, n, errno);
		goto qfail;
	}

#if defined(LWS_WITH_IPV6)
	if (!q->responded && q->sent[0] != q->sent[1]) {
		lwsl_wsi_debug(wsi, "request writeable for ipv6");
		lws_callback_on_writable(wsi);
	}
#endif

	return;

qfail:
	lwsl_wsi_warn(wsi, "failing query doing NULL completion");
	/*
	 * in ipv6 case, we made a cache entry for the first response but
	 * evidently the second response didn't come in time, purge the
	 * incomplete cache entry
	 */
	if (q->firstcache) {
		lwsl_wsi_debug(wsi, "destroy firstcache");
		lws_adns_cache_destroy(q->firstcache);
		q->firstcache = NULL;
	}
	if (q->dsrv) {
		lws_adapt_report_val(q->dsrv->adapt, 20000000, lws_now_usecs());
	}
	lws_async_dns_complete(q, NULL);
	lws_adns_q_destroy(q);
}

static int
callback_async_dns(struct lws *wsi, enum lws_callback_reasons reason,
		   void *user, void *in, size_t len)
{
	struct lws_async_dns *dns = &(lws_get_context(wsi)->async_dns);

	if (!wsi->udp) {
		lws_adns_q_t *q = (lws_adns_q_t *)wsi->a.opaque_user_data;

		if (!q)
			return 0;

		switch (reason) {
		case LWS_CALLBACK_RAW_CONNECTED:
			lws_callback_on_writable(wsi);
			break;
		case LWS_CALLBACK_RAW_CLOSE:
			q->wsi_tcp = NULL;
			if (q->go_nogo != METRES_GO) {
				lws_async_dns_complete(q, NULL);
				lws_adns_q_destroy(q);
			}
			break;
		case LWS_CALLBACK_RAW_RX:
			/* accumulate bytes */
			if (!q->has_tcp_len) {
				const uint8_t *in8 = (const uint8_t *)in;

				while (len && !q->has_tcp_len) {
					q->tcp_rx_len = (uint16_t)((q->tcp_rx_len << 8) | *in8++);
					q->tcp_rx_pos++;
					len--;
					if (q->tcp_rx_pos == 2) {
						if (q->tcp_rx_len < DHO_SIZEOF ||
						    q->tcp_rx_len > LWS_ADNS_MAX_PAYLOAD) {
							lwsl_wsi_notice(wsi, "adns tcp len bad");
							return -1;
						}
						q->has_tcp_len = 1;
						q->tcp_rx_pos = 0;
						q->tcp_rx_buf = lws_malloc((size_t)q->tcp_rx_len + 1, "adns tcp");
						if (!q->tcp_rx_buf) {
							lwsl_wsi_err(wsi, "OOM");
							return -1;
						}
					}
				}
				in = (void *)in8;
			}

			if (q->has_tcp_len && len) {
				size_t chunk = len;
				if (q->tcp_rx_pos + chunk > (size_t)q->tcp_rx_len)
					chunk = (size_t)(q->tcp_rx_len - q->tcp_rx_pos);
				memcpy(q->tcp_rx_buf + q->tcp_rx_pos, in, chunk);
				q->tcp_rx_pos = (uint16_t)(q->tcp_rx_pos + chunk);

				if (q->tcp_rx_pos == q->tcp_rx_len) {
					/* we have the whole message */
					lws_adns_parse_udp(dns, q->tcp_rx_buf, q->tcp_rx_len, q->dsrv);
					/* TCP connection is done */
					return -1;
				}
			}
			break;
		case LWS_CALLBACK_RAW_WRITEABLE:
			if (!q->is_retry && q->sent[0]
#if defined(LWS_WITH_IPV6)
	         && q->sent[0] == q->sent[1]
#endif
			)
				break;
			lws_async_dns_writeable(wsi, q);
			break;
		default:
			break;
		}
		return 0;
	}

	lws_async_dns_server_t *dsrv =
			(lws_async_dns_server_t *)wsi->a.opaque_user_data;

	if (!dsrv)
		return 0;

	switch (reason) {

	/* callbacks related to raw socket descriptor */

        case LWS_CALLBACK_RAW_ADOPT:
		//lwsl_wsi_user(wsi, "LWS_CALLBACK_RAW_ADOPT");
                break;

	case LWS_CALLBACK_RAW_CLOSE:
		//lwsl_wsi_user(wsi, "LWS_CALLBACK_RAW_CLOSE");
		break;

	case LWS_CALLBACK_RAW_RX:
		//lwsl_wsi_user(wsi, "LWS_CALLBACK_RAW_RX (%d)", (int)len);
		// lwsl_hexdump_wsi_notice(wsi, in, len);
		lws_adns_parse_udp(dns, in, len, dsrv);
		break;

	case LWS_CALLBACK_RAW_WRITEABLE:
		//lwsl_wsi_user(wsi, "LWS_CALLBACK_RAW_WRITEABLE");
		lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
					   dns->waiting.head) {
			lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t,
							   list);

			if (//lws_dll2_is_detached(&q->sul.list) &&
			    !q->is_synthetic && q->dsrv == dsrv &&
			    (!q->asked || q->responded != q->asked) && !q->is_tcp) {
				lws_async_dns_writeable(wsi, q);
			}
		} lws_end_foreach_dll_safe(d, d1);
		break;

	default:
		break;
	}

	return 0;
}

/* require: context lock */

lws_async_dns_server_t *
__lws_async_dns_server_find_wsi(lws_async_dns_t *dns, struct lws *wsi)
{
	lws_start_foreach_dll(struct lws_dll2 *, d, dns->nameservers.head) {
		lws_async_dns_server_t *s = lws_container_of(d,
						lws_async_dns_server_t, list);

		if (s->wsi == wsi)
			return s;
	} lws_end_foreach_dll(d);

	return NULL;
}

lws_async_dns_server_t *
__lws_async_dns_server_find(lws_async_dns_t *dns, const lws_sockaddr46 *sa46)
{
	lws_start_foreach_dll(struct lws_dll2 *, d, dns->nameservers.head) {
		lws_async_dns_server_t *s = lws_container_of(d,
						lws_async_dns_server_t, list);

		if (!lws_sa46_compare_ads(sa46, &s->sa46))
			return s;
	} lws_end_foreach_dll(d);

	return NULL;
}

/* require: context lock */

lws_async_dns_server_t *
__lws_async_dns_server_add(lws_async_dns_t *dns, const lws_sockaddr46 *sa46)
{
	lws_async_dns_server_t *s;

	s = __lws_async_dns_server_find(dns, sa46);
	if (s) {
		s->refcount++;
		return s;
	}

	s = lws_zalloc(sizeof(*s), __func__);

	if (s) {
		s->sa46 = *sa46;
		lws_dll2_add_tail(&s->list, &dns->nameservers);
		s->refcount++;
		/* 1 level (just tracking values), 5s short decay, 60s long decay */
		s->adapt = lws_adapt_create(1, 5 * LWS_US_PER_SEC, 60 * LWS_US_PER_SEC);
	}

	return s;
}

/* require: context lock */

static void
__lws_async_dns_server_destroy(lws_async_dns_server_t *dsrv)
{
	lws_async_dns_t *dns;

	if (!dsrv)
		return;

	dsrv->refcount--;
	if (dsrv->refcount)
		return;

	dns = (lws_async_dns_t *)dsrv->list.owner;

	if (dns) {
		lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				lws_dll2_get_head(&dns->waiting)) {
			lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);

			if (q->dsrv == dsrv) {
				/* this query will never be answered, clean it up cleanly */
				lwsl_cx_notice(dns->cx, "failing query attached to removed DNS server");
				lws_async_dns_complete(q, NULL);
				lws_adns_q_destroy(q);
			}
		} lws_end_foreach_dll_safe(d, d1);
	}

	lws_dll2_remove(&dsrv->list);

	if (dsrv->adapt)
		lws_adapt_destroy(&dsrv->adapt);

	lws_async_dns_drop_server(dsrv);
	lws_free(dsrv);
}

void
__lws_async_dns_server_remove(lws_async_dns_t *dns, const lws_sockaddr46 *sa46)
{
	lws_async_dns_server_t *dsrv = __lws_async_dns_server_find(dns, sa46);

	if (!dsrv)
		return;

	__lws_async_dns_server_destroy(dsrv);
}

int
lws_async_dns_server_add(struct lws_context *cx, const lws_sockaddr46 *sa46)
{
	lws_async_dns_server_t *dsrv;
	int r;

	lws_context_lock(cx, __func__);
	dsrv = __lws_async_dns_server_add(&cx->async_dns, sa46);
	if (dsrv)
		dsrv->pinned = 1;
	r = !!dsrv;
	lws_context_unlock(cx);

	return r;
}

void
lws_async_dns_server_remove(struct lws_context *cx, const lws_sockaddr46 *sa46)
{
	lws_context_lock(cx, __func__);
	__lws_async_dns_server_remove(&cx->async_dns, sa46);
	lws_context_unlock(cx);
}

struct lws_protocols lws_async_dns_protocol = {
	"lws-async-dns", callback_async_dns, 0, 0, 0, NULL, 0
};

/*
 * Ensure every logical DNS server has a wsi ready
 */

int
lws_async_dns_create_server_wsi(struct lws_context *context)
{
	lws_async_dns_t *dns = &context->async_dns;
	char ads[48];

	if (!context->vhost_list) { /* coverity... system vhost always present */
		lwsl_cx_err(context, "no system vhost");
		return 1;
	}

	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&dns->nameservers)) {
		lws_async_dns_server_t *dsrv = lws_container_of(d,
						lws_async_dns_server_t, list);

		if (!dsrv->wsi) {
			uint16_t port = 53;
			const char *env_port = getenv("LWS_ASYNCDNS_PORT");
			if (env_port)
				port = (uint16_t)atoi(env_port);

			dsrv->sa46.sa4.sin_port = htons(port);
			lws_sa46_write_numeric_address(&dsrv->sa46,
						       ads, sizeof(ads));

			/* wsi opaque is the dsrv */
			dsrv->wsi = lws_create_adopt_udp(
					context->vhost_list, ads, port, 0,
					lws_async_dns_protocol.name, NULL,
					NULL, dsrv, &retry_policy, "asyncdns");
			if (!dsrv->wsi) {
				lwsl_cx_err(context, "adns wsi adopt fail");
				return 1;
			}

			/*
			 * lws_create_adopt_udp() only allocates wsi->udp once
			 * the raw-skt adoption actually completes.  If adoption
			 * did not complete (eg, the resolved nameserver address
			 * produced no usable route and the wsi is pending close),
			 * wsi->udp may still be NULL -- don't deref it blindly.
			 */
			if (!dsrv->wsi->udp) {
				lwsl_cx_err(context, "adns wsi has no udp");
				return 1;
			}

			dsrv->wsi->udp->sa46 = dsrv->sa46;
		}

	} lws_end_foreach_dll_safe(d, d1);

	return 0;
}

lws_async_dns_server_check_t
lws_plat_asyncdns_init(struct lws_context *context, lws_async_dns_t *dns)
{
	lws_async_dns_server_check_t s = LADNS_CONF_SERVER_SAME;
	lws_async_dns_server_t *dsrv;
	lws_sockaddr46 sa46t;
	int n = 0, any_pinned = 0;

	lws_start_foreach_dll(struct lws_dll2 *, d,
				   lws_dll2_get_head(&dns->nameservers)) {
		dsrv = lws_container_of(d, lws_async_dns_server_t, list);
		if (dsrv->pinned)
			any_pinned = 1;
		dsrv->seen = 0;
	} lws_end_foreach_dll(d);

	if (!any_pinned) {
		while (lws_plat_asyncdns_get_server(context, n++, &sa46t) == 0) {
			dsrv = __lws_async_dns_server_find(dns, &sa46t);
			if (!dsrv) {
				dsrv = __lws_async_dns_server_add(dns, &sa46t);
				s = LADNS_CONF_SERVER_CHANGED;
			}
			if (dsrv)
				dsrv->seen = 1;
		}
	}

	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&dns->nameservers)) {
		dsrv = lws_container_of(d, lws_async_dns_server_t, list);
		if (!dsrv->seen && !dsrv->pinned) {
			__lws_async_dns_server_remove(dns, &dsrv->sa46);
			s = LADNS_CONF_SERVER_CHANGED;
		}
	} lws_end_foreach_dll_safe(d, d1);

	return s;
}

int
lws_async_dns_init(struct lws_context *context)
{
	lws_async_dns_t *dns = &context->async_dns;
	int n;

	dns->cx = context;
	const lws_system_ops_t *ops = lws_system_get_ops(context);
	if (ops) {
		dns->dnssec_mode = ops->async_dns_dnssec_mode;
	} else {
		dns->dnssec_mode = 0;
	}

	n = lws_plat_asyncdns_init(context, dns);
	if (!dns->nameservers.count) {
		lwsl_cx_warn(context, "no valid dns server, retry");

		return 1;
	}

	if (n != LADNS_CONF_SERVER_CHANGED)
		return 0;

	lws_async_dns_create_server_wsi(context);

	return 0;
}

LWS_VISIBLE int
lws_async_dns_server_reload(struct lws_context *context)
{
	return lws_async_dns_init(context);
}

lws_adns_cache_t *
lws_adns_get_cache(lws_async_dns_t *dns, const char *name)
{
	lws_adns_cache_t *c;

	if (!name) {
		assert(0);
		return NULL;
	}

	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&dns->cached)) {
		c = lws_container_of(d, lws_adns_cache_t, list);

		// lwsl_wsi_notice(dns->wsi, "%s vs %s (inc %d)", name, c->name, c->incomplete);

		if (!c->incomplete && !strcasecmp(name, c->name)) {
			/* Keep sorted by LRU: move to the head */
			lws_dll2_remove(&c->list);
			lws_dll2_add_head(&c->list, &dns->cached);

			return c;
		}
	} lws_end_foreach_dll_safe(d, d1);

	return NULL;
}

#if defined(_DEBUG)

void
lws_adns_server_dump(lws_async_dns_server_t *dsrv)
{
#if (_LWS_ENABLED_LOGS & LLL_INFO)
	lws_async_dns_t *dns = (lws_async_dns_t *)dsrv->list.owner;
	char ads[64];

	lws_sa46_write_numeric_address(&dsrv->sa46, ads, sizeof(ads));
	lwsl_cx_info(dns->cx, "nameserver: '%s'", ads);

	lws_start_foreach_dll(struct lws_dll2 *, d,
				   lws_dll2_get_head(&dns->waiting)) {
		lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);
		if (q->dsrv == dsrv || q->broadsiding)
			lwsl_wsi_info(dsrv->wsi, "q: '%s', sent %d, resp %d%s",
				      (const char *)&q[1], q->sent[0], q->responded,
				      q->broadsiding ? " (broadsiding)" : "");
	} lws_end_foreach_dll(d);
#endif
}

void
lws_adns_dump(lws_async_dns_t *dns)
{
#if (_LWS_ENABLED_LOGS & LLL_INFO)
	lws_async_dns_server_t *dsrv;
	lws_adns_cache_t *c;

	if (!dns)
		return;

	lwsl_cx_info(dns->cx, "ADNS cache %u entries",
			(unsigned int)dns->cached.count);

	lws_start_foreach_dll(struct lws_dll2 *, d,
			      lws_dll2_get_head(&dns->cached)) {
		c = lws_container_of(d, lws_adns_cache_t, list);

		lwsl_cx_info(dns->cx, "cache: '%s', exp: %lldus, incomp %d, "
			  "fl 0x%x, refc %d, res %p\n", c->name,
			  (long long)(c->sul.us - lws_now_usecs()),
			  c->incomplete, c->flags, c->refcount, c->results);
	} lws_end_foreach_dll(d);

	lws_start_foreach_dll(struct lws_dll2 *, d,
			      lws_dll2_get_head(&dns->nameservers)) {
		dsrv = lws_container_of(d, lws_async_dns_server_t, list);
		lws_adns_server_dump(dsrv);
	} lws_end_foreach_dll(d);
#endif
}
#endif

void
lws_adns_cache_destroy(lws_adns_cache_t *c)
{
	lws_dll2_remove(&c->sul.list);
	lws_dll2_remove(&c->list);
	if (c->chain)
		lws_free(c->chain);
	lws_free(c);
}

static int
cache_clean(struct lws_dll2 *d, void *user)
{
	lws_adns_cache_destroy(lws_container_of(d, lws_adns_cache_t, list));

	return 0;
}

void
sul_cb_expire(struct lws_sorted_usec_list *sul)
{
	lws_adns_cache_t *c = lws_container_of(sul, lws_adns_cache_t, sul);

	lws_adns_cache_destroy(c);
}

void
sul_cb_write(struct lws_sorted_usec_list *sul)
{
	lws_adns_q_t *q = lws_container_of(sul, lws_adns_q_t, write_sul);

	/*
	 * Something's up, we couldn't even get from write request to
	 * WRITEABLE within the timeout, let alone the result... fail
	 * the query and everyone riding on it...
	 */

	lwsl_wsi_info(q->dsrv ? q->dsrv->wsi : NULL, "failing");
	lws_adns_dump(q->dns);

	if (q->dsrv) {
		lws_adapt_report_val(q->dsrv->adapt, 20000000, lws_now_usecs());
	}

	lws_async_dns_complete(q, NULL); /* no cache to relate to */
	lws_adns_q_destroy(q);
}

void
lws_async_dns_freeaddrinfo(const struct addrinfo **pai)
{
	lws_adns_cache_t *c;

	if (!*pai)
		return;

	/*
	 * First query may have been empty... if second has something, we
	 * fixed up the first result to point to second... but it means
	 * looking backwards from ai, which is c->result, which is the second
	 * packet's results, doesn't get us to the firstcache pointer.
	 *
	 * Adjust c to the firstcache in this case.
	 */

	c = &((lws_adns_cache_t *)(*pai))[-1];
	if (c->firstcache)
		c = c->firstcache;

	lwsl_debug("%s: c %p, %s, refcount %d -> %d\n", __func__, c,
		   (c->results && c->results->ai_canonname) ?
				c->results->ai_canonname : "none",
						c->refcount, c->refcount - 1);

	assert(c->refcount > 0);
	c->refcount--;
	*pai = NULL;
}

void
lws_async_dns_trim_cache(lws_async_dns_t *dns)
{
	lws_adns_cache_t *c1;

	if (dns->cached.count + 1< MAX_CACHE_ENTRIES)
		return;

	/* we want to make space for one new one */

	if (!dns->cached.count)
		return;

	c1 = lws_container_of(lws_dll2_get_tail(&dns->cached),
						lws_adns_cache_t, list);
	if (c1->refcount)
		lwsl_cx_info(dns->cx, "acache %p: refcount %d on purge",
				c1, c1->refcount);
	else
		lws_adns_cache_destroy(c1);
}


static int
clean(struct lws_dll2 *d, void *user)
{
	lws_adns_q_destroy(lws_container_of(d, lws_adns_q_t, list));

	return 0;
}

static int
ns_clean(struct lws_dll2 *d, void *user)
{
	lws_async_dns_server_t *dsrv = lws_container_of(d,
					lws_async_dns_server_t, list);
	lws_async_dns_t *dns = (lws_async_dns_t *)dsrv->list.owner;

	/* Since waiting queue is now on dns, we can't iterate dsrv->waiting. We should just let dns deinit handle the global queue. But we can clean up any queries specifically bound to this server if not broadsiding. */
	lws_start_foreach_dll_safe(struct lws_dll2 *, dwait, dwait1,
				   lws_dll2_get_head(&dns->waiting)) {
		lws_adns_q_t *q = lws_container_of(dwait, lws_adns_q_t, list);
		if (q->dsrv == dsrv && !q->broadsiding) {
			clean(dwait, user);
		}
	} lws_end_foreach_dll_safe(dwait, dwait1);

	__lws_async_dns_server_destroy(dsrv);

	return 0;
}

void
lws_async_dns_deinit(lws_async_dns_t *dns)
{
	lws_dll2_foreach_safe(&dns->waiting, NULL, clean);
	lws_dll2_foreach_safe(&dns->nameservers, NULL, ns_clean);
	lws_dll2_foreach_safe(&dns->cached, NULL, cache_clean);
}

void
lws_async_dns_detach_query_wsi(struct lws *wsi)
{

}

static int
cancel(struct lws_dll2 *d, void *user)
{
	lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);

	lws_start_foreach_dll_safe(struct lws_dll2 *, d3, d4,
				   lws_dll2_get_head(&q->wsi_adns)) {
		struct lws *w = lws_container_of(d3, struct lws, adns);

		if (user == w) {
			/*
			 * This is an ongoing query that our wsi was involved
			 * in... detach it from the query, and if nothing left
			 * interested in the query, destroy it.
			 *
			 * Since the wsi can only be running one query at a time
			 * currently, we can bail if we got a hit.
			 */
			lws_dll2_remove(d3);
			if (!q->wsi_adns.count)
				lws_adns_q_destroy(q);
			return 1;
		}
	} lws_end_foreach_dll_safe(d3, d4);

	return 0;
}

/*
 * Let's go through all ongoing async dns queries, detaching wsi from any it
 * is involved in, and destroying the query if that was the only consumer.
 */

void
lws_async_dns_cancel(struct lws *wsi)
{
	lws_dll2_foreach_safe(&wsi->a.context->async_dns.waiting, wsi, cancel);
}


static int
check_tid(struct lws_dll2 *d, void *user)
{
	lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);
	int n = 0, nmax = q->tids >= LWS_ARRAY_SIZE(q->tid) ?
			  LWS_ARRAY_SIZE(q->tid) : q->tids;
	uint16_t check = (uint16_t)(intptr_t)user;

	for (n = 0; n < nmax; n++)
		if (check == q->tid[n])
			return 1;

	return 0;
}

int
lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q)
{
	int budget = 10;

	assert(q->dsrv);

	/*
	 * Make the TID unpredictable, but must be unique amongst ongoing ones
	 */
	do {
		uint16_t tid;

		if (lws_get_random(context, &tid, 2) != 2)
			return -1;

		if (lws_dll2_foreach_safe(&q->dns->waiting,
					  (void *)(intptr_t)tid, check_tid))
			continue;

		q->tids++;
		LADNS_MOST_RECENT_TID(q) = tid;

		return 0;

	} while (budget--);

	lwsl_cx_err(context, "unable to get unique tid");

	return -1;
}

#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_FREERTOS)

/*
 * Search /etc/hosts or similar for the DNS name
 */

int
lws_adns_scan_hostsfile(const char *name, uint8_t *ads, size_t adslen, int qtype)
{
	char had_ads = 0, buf[128], finalized = 0;
	int fd, ret = 0, l = 0, ol = -1;
	size_t nl = strlen(name);
	struct lws_tokenize ts;

#if defined(WIN32)
	int s;

	fd = _open("C:\\Windows\\System32\\Drivers\\etc\\hosts", LWS_O_RDONLY);
#else
	ssize_t s;

	fd = open("/etc/hosts", LWS_O_RDONLY);
#endif

	if (fd < 0)
		return 0;

	memset(&ts, 0, sizeof(ts));
	ts.flags = LWS_TOKENIZE_F_EXPECT_MORE | LWS_TOKENIZE_F_MINUS_NONTERM |
		   LWS_TOKENIZE_F_DOT_NONTERM | LWS_TOKENIZE_F_NO_FLOATS |
		   LWS_TOKENIZE_F_NO_INTEGERS | LWS_TOKENIZE_F_HASH_COMMENT |
		   LWS_TOKENIZE_F_COLON_NONTERM;

	do {

#if defined(WIN32)
		s = _read(fd, buf, sizeof(buf) - 1);
#else
		s = read(fd, buf, sizeof(buf) - 1);
#endif
		if (s <= 0) {
			finalized = 1;
			ts.flags = (uint16_t)(ts.flags & (~LWS_TOKENIZE_F_EXPECT_MORE));
			s = 0;
		}

		ts.start = (char *)buf;
		ts.len = (size_t)s;

		do {
			ts.e = (int8_t)lws_tokenize(&ts);
			if (ts.e == LWS_TOKZE_WANT_READ)
				break;

			if (ts.effline != ol) {
				had_ads = 0;
				ol = ts.effline;
			}

			if (ts.e == LWS_TOKZE_TOKEN && had_ads && l &&
			    nl == ts.token_len &&
			    !memcmp(name, ts.token, ts.token_len)) {
				/* it's a hit */
				if ((qtype == LWS_ADNS_RECORD_A && l == 4) ||
#if defined(LWS_WITH_IPV6)
				    (qtype == LWS_ADNS_RECORD_A && l == 16) ||
#endif
				    (qtype == LWS_ADNS_RECORD_AAAA && l == 16) ||
				    (qtype != LWS_ADNS_RECORD_A && qtype != LWS_ADNS_RECORD_AAAA)) {
					ret = (int)l;
					break;
				}
			}
			if (ts.e == LWS_TOKZE_TOKEN && !had_ads && ts.token_len < 50) {
				/* we're getting an ipv4 or ipv6 numads */
				char temp_ads[64];
				memcpy(temp_ads, ts.token, ts.token_len);
				temp_ads[ts.token_len] = '\0';
				l = lws_parse_numeric_address(temp_ads, ads, adslen);
				had_ads = 1;
			}

		} while (ts.e > 0); /* no error yet */

	} while (!ret && !finalized); /* no match yet */

#if defined(WIN32)
	_close(fd);
#else
	close(fd);
#endif

	return ret;
}
#endif

uint16_t
lws_adns_get_tid(struct lws_adns_q *q)
{
	return LADNS_MOST_RECENT_TID(q);
}

struct lws_async_dns *
lws_adns_get_async_dns(struct lws_adns_q *q)
{
	return q->dns;
}

struct lws_async_dns_server *
lws_adns_get_server(struct lws_adns_q *q)
{
	return q->dsrv;
}

struct temp_q {
	lws_adns_q_t tq;
	char name[48];
};

lws_async_dns_retcode_t
lws_async_dns_query(struct lws_context *context, int tsi, const char *name,
		    adns_query_type_t qtype, lws_async_dns_cb_t cb,
		    struct lws *wsi, void *opaque, struct lws_adns_q **pq)
{
	lws_async_dns_t *dns = &context->async_dns;
	lws_async_dns_server_t *dsrv;
	size_t nlen = strlen(name);
	lws_sockaddr46 *sa46;
	lws_adns_cache_t *c;
	struct addrinfo *ai;
	struct temp_q tmq;
	lws_adns_q_t *q;
	uint8_t ads[16];
	char *p;
	int m = 0;
	uint8_t want_dnssec = (qtype & LWS_ADNS_WANT_DNSSEC) ? 1 : 0;
	
	qtype = (adns_query_type_t)(qtype & ~(uint32_t)LWS_ADNS_WANT_DNSSEC);

	lwsl_cx_info(context, "entry %s", name);
	lws_adns_dump(dns);

#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_FREERTOS) && !defined(WIN32)
	{
		struct stat st;
		if (!stat("/etc/resolv.conf", &st)) {
			if (dns->time_resolv_check && st.st_mtime > dns->time_resolv_check) {
				lwsl_cx_notice(context, "/etc/resolv.conf updated, reloading");
				lws_async_dns_server_reload(context);
			}
			dns->time_resolv_check = st.st_mtime;
		}
	}
#endif

#if !defined(LWS_WITH_IPV6)
	if (qtype == LWS_ADNS_RECORD_AAAA) {
		lwsl_cx_err(context, "ipv6 not enabled");
		goto failed;
	}
#endif

	if (nlen >= DNS_MAX - 1)
		goto failed;

	if (wsi) {
		if (!lws_dll2_is_detached(&wsi->adns)) {
			lwsl_cx_err(context, "%s already bound to query %p",
					lws_wsi_tag(wsi), wsi->adns.owner);
			goto failed;
		}
		wsi->adns_cb = cb;
	}

	/* there's a done, cached query we can just reuse? */

	c = (qtype & LWS_ADNS_NOCACHE) ? NULL : lws_adns_get_cache(dns, name);
	qtype = (adns_query_type_t)(qtype & ~(uint32_t)LWS_ADNS_NOCACHE);
	if (c) {
		int found = 0;
		if (qtype == LWS_ADNS_RECORD_A || qtype == LWS_ADNS_RECORD_AAAA) {
			if (c->results)
				found = 1;
		} else {
			lws_adns_rr_t *rr = c->rr_results;
			while (rr) {
				if (rr->type == qtype) {
					found = 1;
					break;
				}
				rr = rr->next;
			}
		}

		if (!found) {
			lwsl_cx_info(context, "%s: cached but missing 0x%x, bypassing", name, qtype);
			lws_dll2_remove(&c->list); /* Remove from cache list, let sul expire it */
			c = NULL;
		}
	}

	if (c) {
		lwsl_cx_info(context, "%s: using cached, c->results %p, c->rr_results %p",
			  name, c->results, c->rr_results);
		m = (c->results || c->rr_results) ? LADNS_RET_FOUND : LADNS_RET_FAILED;
		if (c->results)
			c->refcount++;
		/* Note: c->rr_results relies on the same cache refcount, but to be
		   consistent, we'll bump the refcount if rr_results exists too! */
		else if (c->rr_results)
			c->refcount++;

#if defined(LWS_WITH_SYS_METRICS)
		lws_metric_event(context->mt_adns_cache,  METRES_GO, 0);
#endif

		if (cb(wsi, name, c->results, m, opaque) == NULL)
			return LADNS_RET_FAILED_WSI_CLOSED;

		return m;
	} else
		lwsl_cx_info(context, "%s uncached", name);

#if defined(LWS_WITH_SYS_METRICS)
	lws_metric_event(context->mt_adns_cache, METRES_NOGO, 0);
#endif

	/*
	 * It's a 1.2.3.4 or ::1 type IP address already?  We don't need a dns
	 * server set up to be able to create an addrinfo result for that.
	 *
	 * Create it as a cached object so it follows the refcount lifecycle
	 * of any other result
	 */

	if ((qtype & 0xff) == LWS_ADNS_RECORD_A || (qtype & 0xff) == LWS_ADNS_RECORD_AAAA) {
		m = lws_parse_numeric_address(name, ads, sizeof(ads));

#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_FREERTOS)
		if (m < 4 && !(qtype & LWS_ADNS_IGNORE_HOSTS_FILE))
			/*
			 * Not directly numeric and not explicitly bypassing...
			 * look through /etc/hosts
			 */
			m = lws_adns_scan_hostsfile(name, ads, sizeof(ads), qtype & 0xff);

		if (m < 4 && !strcmp(name, "localhost")) {
#if defined(LWS_WITH_IPV4)
			if ((qtype & 0xff) == LWS_ADNS_RECORD_A) {
				ads[0] = 127; ads[1] = 0; ads[2] = 0; ads[3] = 1;
				m = 4;
			}
#endif
#if defined(LWS_WITH_IPV6)
			if (
#if defined(LWS_WITH_IPV4)
				(qtype & 0xff) == LWS_ADNS_RECORD_AAAA
#else
				1
#endif
			) {
				memset(ads, 0, 15); ads[15] = 1;
				m = 16;
			}
#endif
		}
#endif
	}

	if (m == 4
#if defined(LWS_WITH_IPV6)
		|| m == 16
#endif
	) {
		lws_async_dns_trim_cache(dns);

		c = lws_zalloc(sizeof(lws_adns_cache_t) +
			       sizeof(struct addrinfo) +
			       sizeof(lws_sockaddr46) + nlen + 1, "adns-numip");
		if (!c)
			goto failed;

		ai = (struct addrinfo *)&c[1];
		sa46 = (lws_sockaddr46 *)&ai[1];

		ai->ai_socktype = SOCK_STREAM;
		c->name = (const char *)&sa46[1];
		memcpy((char *)c->name, name, nlen + 1);
		ai->ai_canonname = (char *)&sa46[1];

		c->results = ai;
		memset(&tmq.tq, 0, sizeof(tmq.tq));
		tmq.tq.opaque = opaque;
		if (wsi) {
			wsi->adns_cb = cb;
			lws_dll2_add_head(&wsi->adns, &tmq.tq.wsi_adns);
		} else
			tmq.tq.standalone_cb = cb;
		lws_strncpy(tmq.name, name, sizeof(tmq.name));

		lws_dll2_add_head(&c->list, &dns->cached);
		lws_sul_schedule(context, 0, &c->sul, sul_cb_expire,
				 3600ll * LWS_US_PER_SEC);

		lws_adns_dump(dns);
	}

	if (m == 4) {
		ai = (struct addrinfo *)&c[1];
		sa46 = (lws_sockaddr46 *)&ai[1];
		ai->ai_family = sa46->sa4.sin_family = AF_INET;
		ai->ai_addrlen = sizeof(sa46->sa4);
		ai->ai_addr = (struct sockaddr *)&sa46->sa4;
		memcpy(&sa46->sa4.sin_addr, ads, (unsigned int)m);

		return lws_async_dns_complete(&tmq.tq, c);
	}

#if defined(LWS_WITH_IPV6)
	if (m == 16) {
		ai = (struct addrinfo *)&c[1];
		sa46 = (lws_sockaddr46 *)&ai[1];
		ai->ai_family = sa46->sa6.sin6_family = AF_INET6;
		ai->ai_addrlen = sizeof(sa46->sa6);
		ai->ai_addr = (struct sockaddr *)&sa46->sa6;
		memcpy(&sa46->sa6.sin6_addr, ads, (unsigned int)m);

		return lws_async_dns_complete(&tmq.tq, c);
	}
#endif

	/*
	 * to try anything else we need a remote server configured...
	 */

	if (!context->async_dns.nameservers.head &&
	    lws_async_dns_init(context)) {
		lwsl_cx_notice(context, "init failed");
		goto failed;
	}

	/* make sure we have the remote server UDP enabled, it's cheap if we
	 * already do.  This is for the case we came up before the network and
	 * couldn't succeed to open the socket / wsi initially */

	lws_async_dns_create_server_wsi(context);

	/* there's an ongoing query we can share the result of? */

	q = lws_adns_get_query(dns, qtype, 0, name);
	if (q) {
		lwsl_cx_debug(context, "dns piggybacking: %d:%s",
				qtype, name);
		if (wsi)
			lws_dll2_add_head(&wsi->adns, &q->wsi_adns);

		return LADNS_RET_CONTINUING;
	}

	q = lws_zalloc(sizeof(*q) + nlen + 1 + DNS_MAX + 1, "adns-q");
	if (!q) {
		lwsl_cx_err(context, "OOM");
		goto failed;
	}

	if (wsi)
		lws_dll2_add_head(&wsi->adns, &q->wsi_adns);

	q->qtype = (uint16_t)qtype;
	q->want_dnssec = want_dnssec != 0;
	if (wsi && wsi->a.vhost && lws_check_opt(wsi->a.vhost->options, LWS_SERVER_OPTION_DISABLE_IPV6))
		q->ipv4_only = 1;
	if (qtype & LWS_ADNS_SYNTHETIC)
		q->is_synthetic = 1;
#if defined(LWS_WITH_SYS_ASYNC_DNS_DNSSEC)
	if (qtype & LWS_ADNS_INDICATE_LACKS_DNSSEC)
		q->lacks_dnssec = 1;
#endif

	q->context = context;
	q->tsi = (uint8_t)tsi;
	q->opaque = opaque;
	q->dns = dns;

	/* Evaluate the optimal server */
	{
		lws_async_dns_server_t *best_dsrv = NULL;
		uint64_t best_rtt = ~(uint64_t)0;
		int all_failed = 1;

		lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&dns->nameservers)) {
			lws_async_dns_server_t *s = lws_container_of(d, lws_async_dns_server_t, list);
			uint64_t v = lws_adapt_get_val(s->adapt, 0, 1);
			/* 10 seconds means virtually down or unknown */
			if (!v || v > 10000000) {
				q->broadsiding = 1;
			} else {
				all_failed = 0;
			}
			if (v && v < best_rtt) {
				best_rtt = v;
				best_dsrv = s;
			}
		} lws_end_foreach_dll_safe(d, d1);

		if (dns->nameservers.count && all_failed) {
			if (lws_now_usecs() - dns->time_last_reload > 5000000) {
				lwsl_cx_notice(context, "Async DNS fallback reload triggered");
				lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
						   lws_dll2_get_head(&dns->nameservers)) {
					lws_async_dns_server_t *s = lws_container_of(d, lws_async_dns_server_t, list);
					lws_async_dns_drop_server(s);
				} lws_end_foreach_dll_safe(d, d1);
				lws_async_dns_server_reload(context);
				/* also ensure any new servers have a wsi */
				lws_async_dns_create_server_wsi(context);
				dns->time_last_reload = lws_now_usecs();
			}
		}

		if (q->broadsiding)
			/* Just peg it to the first for tracking purposes, but it will be skipped visually */
			dsrv = lws_container_of(context->async_dns.nameservers.head, lws_async_dns_server_t, list);
		else if (best_dsrv)
			/* Pick servers near the 'best' round-robin style maybe, or just pick the best. For simplicity: best one */
			dsrv = best_dsrv;
		else
			dsrv = lws_container_of(context->async_dns.nameservers.head, lws_async_dns_server_t, list);


		q->dsrv = dsrv;
	}

	/*
	 * If the chosen DNS server has no wsi (eg, its UDP listener could not
	 * be created / bound, as happens in an IPv6-only build where the only
	 * nameserver was discovered as an IPv4 literal that maps to an
	 * unreachable ::ffff: address), we cannot actually issue the query.
	 * Fail it cleanly rather than dereferencing the NULL wsi in the retry
	 * scheduler / writable callback below.
	 */
	if (!dsrv || !dsrv->wsi) {
		lwsl_cx_notice(context, "no usable async dns server wsi");
		/*
		 * q is allocated but has not yet been published on dns->waiting
		 * and has no scheduled sul / metrics caliper yet, so the failed:
		 * path below (which expects a published q) would leak it.  Detach
		 * any wsi bound to it and free q directly first.
		 */
		if (wsi)
			lws_dll2_remove(&wsi->adns);
		lws_free(q);
		goto failed;
	}

	q->issue_time = lws_now_usecs();

	if (lws_async_dns_get_new_tid(context, q)) {
		lwsl_cx_err(context, "tid fail");
		goto failed;
	}

	LADNS_MOST_RECENT_TID(q) &= 0xfffe;

	if (pq)
		*pq = q;

	if (!wsi)
		q->standalone_cb = cb;

	/* schedule a retry according to the retry policy on the wsi */
	if (lws_retry_sul_schedule_retry_wsi(dsrv->wsi, &q->sul,
					 lws_async_dns_sul_cb_retry, &q->retry))
		goto failed;

	/* fail us if we can't write by this timeout */
	lws_sul_schedule(context, 0, &q->write_sul, sul_cb_write, LWS_US_PER_SEC);

	/*
	 * We may rewrite the copy at +sizeof(*q) for CNAME recursion.  Keep
	 * a second copy at + sizeof(*q) + DNS_MAX so we can create the cache
	 * entry for the original name, not the last CNAME we met.
	 */

	p = (char *)&q[1];
	while (nlen--) {
		*p++ = (char)tolower((uint8_t)*name++);
		p[DNS_MAX - 1] = p[-1];
	}
	*p = '\0';
	p[DNS_MAX] = '\0';

	lws_callback_on_writable(dsrv->wsi);


	lws_dll2_add_head(&q->list, &dns->waiting);

	lws_metrics_caliper_bind(q->metcal, context->mt_conn_dns);
	q->go_nogo = METRES_NOGO;
	/* caliper is reported in lws_adns_q_destroy */

	lwsl_cx_info(context, "created new query: %s", name);
	lws_adns_dump(dns);

	return LADNS_RET_CONTINUING;

failed:
	lwsl_cx_notice(context, "failed");
	if (!cb(wsi, NULL, NULL, LADNS_RET_FAILED, opaque))
		return LADNS_RET_FAILED_WSI_CLOSED;

	return LADNS_RET_FAILED;
}

int
lws_async_dns_create_tcp_wsi(lws_adns_q_t *q)
{
#if defined(LWS_WITH_CLIENT)
	struct lws_client_connect_info i;
	char ads[48];

	if (q->wsi_tcp)
		return 0;

	memset(&i, 0, sizeof(i));
	i.context = q->context;
	i.vhost = q->context->vhost_system;

	uint16_t port = 53;
	const char *env_port = getenv("LWS_ASYNCDNS_PORT");
	if (env_port)
		port = (uint16_t)atoi(env_port);

	lws_sa46_write_numeric_address(&q->dsrv->sa46, ads, sizeof(ads));
	i.address = ads;
	i.port = port;
	i.ssl_connection = 0;
	i.method = "RAW";
	i.local_protocol_name = lws_async_dns_protocol.name;
	i.opaque_user_data = q;

	q->is_tcp = 1;
	q->has_tcp_len = 0;
	q->tcp_rx_pos = 0;

	q->wsi_tcp = lws_client_connect_via_info(&i);
	if (!q->wsi_tcp) {
		lwsl_cx_err(q->context, "adns tcp connect fail");
		return 1;
	}

	return 0;
#else
	return 1;
#endif
}

void
lws_async_dns_dnssec_set_mode(struct lws_context *context,
			      lws_async_dns_dnssec_mode_t mode)
{
	context->async_dns.dnssec_mode = (uint8_t)mode;
}

const uint8_t *
lws_async_dns_get_rr_cache(struct lws_context *context, const char *name,
			   adns_query_type_t qtype, uint16_t *paylen)
{
	lws_adns_cache_t *c;
	lws_adns_rr_t *rr;

	if (!context || !name)
		return NULL;

	lws_start_foreach_dll(struct lws_dll2 *, d,
			      lws_dll2_get_head(&context->async_dns.cached)) {
		c = lws_container_of(d, lws_adns_cache_t, list);

		if (!strcmp(c->name, name) && c->rr_results) {
			rr = c->rr_results;
			while (rr) {
				if (rr->type == qtype) {
					if (paylen)
						*paylen = rr->paylen;
					return (const uint8_t *)&rr[1];
				}
				rr = rr->next;
			}
		}
	} lws_end_foreach_dll(d);

	return NULL;
}

int
lws_async_dns_get_alpn(struct lws_context *context, const char *name, const char *alpn)
{
	uint16_t paylen;
	const uint8_t *rr = lws_async_dns_get_rr_cache(context, name, LWS_ADNS_RECORD_HTTPS, &paylen);
	int pos = 2;
	int limit;

	if (!rr || paylen < 3)
		return 0;

	/* TargetName: sequence of labels ending in 0 */
	limit = 128;
	while (limit-- && pos < paylen && rr[pos]) {
		if (pos + rr[pos] + 1 > paylen)
			return 0;
		pos += rr[pos] + 1;
	}
	if (pos >= paylen)
		return 0;
	pos++; /* Skip the 0 length label */

	/* SvcParams */
	limit = 128;
	while (limit-- && pos + 4 <= paylen) {
		uint16_t key = (uint16_t)((rr[pos] << 8) | rr[pos + 1]);
		uint16_t len = (uint16_t)((rr[pos + 2] << 8) | rr[pos + 3]);
		pos += 4;

		if (pos + len > paylen)
			break;

		if (key == 1) { /* alpn */
			uint16_t i = 0;
			int limit2 = 128;
			while (limit2-- && i < len) {
				uint8_t slen = rr[pos + i];
				if (i + 1 + slen > len)
					break;
				if (slen == strlen(alpn) && !strncmp((const char *)&rr[pos + i + 1], alpn, strlen(alpn)))
					return 1;
				i = (uint16_t)(i + slen + 1);
			}
		}
		pos += len;
	}

	return 0;
}
