1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-16 00:00:07 +01:00
libwebsockets/lib/system/async-dns/async-dns.c
Andy Green c591e1adfc asynchronous dns for ipv4 and ipv6
This adds the option to have lws do its own dns resolution on
the event loop, without blocking.  Existing implementations get
the name resolution done by the libc, which is blocking.  In
the case you are opening client connections but need to carefully
manage latency, another connection opening and doing the name
resolution becomes a big problem.

Currently it supports

 - ipv4 / A records
 - ipv6 / AAAA records
 - ipv4-over-ipv6 ::ffff:1.2.3.4 A record promotion for ipv6
 - only one server supported over UDP :53
 - nameserver discovery on linux, windows, freertos

It also has some nice advantages

 - lws-style paranoid response parsing
 - random unique tid generation to increase difficulty of poisoning
 - it's really integrated with the lws event loop, it does not spawn
   threads or use the libc resolver, and of course no blocking at all
 - platform-specific server address capturing (from /etc/resolv.conf
   on linux, windows apis on windows)
 - it has LRU caching
 - piggybacking (multiple requests before the first completes go on
   a list on the first request, not spawn multiple requests)
 - observes TTL in cache
 - TTL and timeout use lws_sul timers on the event loop
 - ipv6 pieces only built if cmake LWS_IPV6 enabled
2019-09-19 06:54:53 +01:00

671 lines
15 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"
#include "private-lib-async-dns.h"
void
lws_adns_q_destroy(lws_adns_q_t *q)
{
lws_dll2_remove(&q->sul.list);
lws_dll2_remove(&q->list);
lws_free(q);
}
lws_adns_q_t *
lws_adns_get_query(lws_async_dns_t *dns, adns_query_type_t qtype,
lws_dll2_owner_t *owner, uint16_t tid, const char *name)
{
lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
lws_dll2_get_head(owner)) {
lws_adns_q_t *q = lws_container_of(d, lws_adns_q_t, list);
if (!name && (tid & 0xfffe) == (q->tid & 0xfffe))
return q;
if (name && q->qtype == ((tid & 1) ? LWS_ADNS_RECORD_AAAA :
LWS_ADNS_RECORD_A) &&
!strcasecmp(name, (const char *)&q[1])) {
if (owner == &dns->cached) {
/* Keep sorted by LRU: move to the head */
lws_dll2_remove(&q->list);
lws_dll2_add_head(&q->list, &dns->cached);
}
return q;
}
} lws_end_foreach_dll_safe(d, d1);
return NULL;
}
void
lws_async_dns_drop_server(struct lws_context *context)
{
context->async_dns.dns_server_set = 0;
lws_set_timeout(context->async_dns.wsi, 1, LWS_TO_KILL_ASYNC);
context->async_dns.wsi = NULL;
}
int
lws_async_dns_complete(lws_adns_q_t *q, lws_adns_cache_t *c)
{
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_debug("%s: q: %p, c: %p, refcount %d -> %d\n",
__func__, q, c, c->refcount, c->refcount + 1);
c->refcount++;
}
w->adns_cb(w, (const char *)&q[1], c ? c->results : NULL, 0,
q->opaque);
} lws_end_foreach_dll_safe(d, d1);
if (q->standalone_cb) {
if (c && c->results) {
lwsl_debug("%s: q: %p, c: %p, refcount %d -> %d\n",
__func__, q, c, c->refcount, c->refcount + 1);
c->refcount++;
}
q->standalone_cb(NULL, (const char *)&q[1],
c ? c->results : NULL, 0, q->opaque);
}
return 0;
}
static void
sul_cb_timeout(struct lws_sorted_usec_list *sul)
{
lws_adns_q_t *q = lws_container_of(sul, lws_adns_q_t, sul);
lws_async_dns_complete(q, NULL);
lws_adns_q_destroy(q);
/*
* 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->context);
}
static int
callback_async_dns(struct lws *wsi, enum lws_callback_reasons reason,
void *user, void *in, size_t len)
{
uint8_t pkt[LWS_PRE + DNS_PACKET_LEN], *e = &pkt[sizeof(pkt)], *p, *pl;
struct lws_async_dns *dns = &(lws_get_context(wsi)->async_dns);
struct lws_dll2 *d;
const char *name;
lws_adns_q_t *q;
int fd, m, n;
switch (reason) {
/* callbacks related to raw socket descriptor */
case LWS_CALLBACK_RAW_ADOPT:
// lwsl_user("LWS_CALLBACK_RAW_ADOPT\n");
break;
case LWS_CALLBACK_RAW_CLOSE:
// lwsl_user("LWS_CALLBACK_RAW_CLOSE\n");
break;
case LWS_CALLBACK_RAW_RX:
// lwsl_user("LWS_CALLBACK_RAW_RX (%d)\n", (int)len);
//lwsl_hexdump_level(LLL_NOTICE, in, len);
lws_adns_parse_udp(dns, in, len);
return 0;
case LWS_CALLBACK_RAW_WRITEABLE:
d = lws_dll2_get_head(&dns->waiting_send);
if (!d)
return 0;
q = lws_container_of(d, lws_adns_q_t, list);
name = (const char *)&q[1];
p = &pkt[LWS_PRE];
memset(p, 0, DHO_SIZEOF);
/* we hack b0 of the tid to be 0 = A, 1 = AAAA */
lws_ser_wu16be(&p[DHO_TID], q->asked ? q->tid | 1 :
q->tid);
lws_ser_wu16be(&p[DHO_FLAGS], (1 << 8));
lws_ser_wu16be(&p[DHO_NQUERIES], 1);
p += DHO_SIZEOF;
/* start of label-formatted qname */
pl = p++;
do {
if (*name == '.' || !*name) {
*pl = lws_ptr_diff(p, pl + 1);
pl = p;
*p++ = 0; /* also serves as terminal length */
if (!*name++)
break;
} else
*p++ = *name++;
} while (p + 6 < e);
if (p + 6 >= e) {
assert(0);
lwsl_err("%s: name too big\n", __func__);
goto qfail;
}
lws_ser_wu16be(p, q->asked ? LWS_ADNS_RECORD_AAAA :
LWS_ADNS_RECORD_A);
p += 2;
lws_ser_wu16be(p, 1); /* IN class */
p += 2;
assert(p < pkt + sizeof(pkt) - LWS_PRE);
n = lws_ptr_diff(p, pkt + LWS_PRE);
fd = lws_get_socket_fd(wsi);
if (fd < 0)
break;
m = send(fd, pkt + LWS_PRE, n, 0);
if (m != n) {
lwsl_notice("%s: dns write failed %d %d\n", __func__,
m, n);
goto qfail;
}
/* move us to the "waiting for response" list */
#if defined(LWS_WITH_IPV6)
/* don't move to waiting resp until we sent both */
if (q->asked) {
q->asked |= 2;
#endif
lws_dll2_remove(&q->list);
lws_dll2_add_head(&q->list, &dns->waiting_resp);
#if defined(LWS_WITH_IPV6)
} else
lws_callback_on_writable(wsi);
#endif
q->asked |= 1;
if (lws_dll2_get_head(&dns->waiting_send))
/* more to do */
lws_callback_on_writable(wsi);
break;
default:
break;
}
return 0;
qfail:
lws_async_dns_complete(q, NULL);
lws_adns_q_destroy(q);
return 0;
}
struct lws_protocols lws_async_dns_protocol = {
"lws-async-dns", callback_async_dns, 0, 0
};
int
lws_async_dns_init(struct lws_context *context)
{
lws_async_dns_t *dns = &context->async_dns;
char ads[48];
int n;
memset(&dns->sa46, 0, sizeof(dns->sa46));
n = lws_plat_asyncdns_init(context, &dns->sa46);
if (n < 0) {
lwsl_warn("%s: no valid dns server, retry\n", __func__);
return 1;
}
dns->sa46.sa4.sin_port = htons(53);
lws_write_numeric_address((uint8_t *)&dns->sa46.sa4.sin_addr.s_addr, 4,
ads, sizeof(ads));
context->async_dns.wsi = lws_create_adopt_udp(context->vhost_list, ads,
53, 0, lws_async_dns_protocol.name, NULL);
if (!dns->wsi) {
lwsl_err("%s: foreign socket adoption failed\n", __func__);
return 1;
}
dns->dns_server_set = 1;
return 0;
}
lws_adns_cache_t *
lws_adns_get_cache(lws_async_dns_t *dns, const char *name)
{
lws_adns_cache_t *c;
const char *cn;
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);
cn = (const char *)&c[1];
if (name && !strcasecmp(name, cn)) {
/* 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;
}
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
lws_async_dns_freeaddrinfo(const struct addrinfo *ai)
{
lws_adns_cache_t *c;
if (!ai)
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 *)ai)[-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--;
}
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;
c1 = lws_container_of(lws_dll2_get_tail(&dns->cached),
lws_adns_cache_t, list);
if (c1->refcount)
lwsl_notice("%s: wsi %p: refcount %d on purge\n",
__func__, 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;
}
void
lws_async_dns_deinit(lws_async_dns_t *dns)
{
lws_dll2_foreach_safe(&dns->waiting_send, NULL, clean);
lws_dll2_foreach_safe(&dns->waiting_resp, NULL, clean);
lws_dll2_foreach_safe(&dns->cached, NULL, cache_clean);
}
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) {
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;
}
void
lws_async_dns_cancel(struct lws *wsi)
{
lws_async_dns_t *dns = &wsi->context->async_dns;
if (!lws_dll2_foreach_safe(&dns->waiting_send, wsi, cancel))
lws_dll2_foreach_safe(&dns->waiting_resp, 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);
return q->tid == (uint16_t)(long)user;
}
static int
lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q)
{
lws_async_dns_t *dns = &context->async_dns;
int budget = 10;
/*
* Make the TID unpredictable, but must be unique amongst ongoing ones
*/
do {
if (lws_get_random(context, &q->tid, 2) != 2)
return -1;
if (lws_dll2_foreach_safe(&dns->waiting_send,
(void *)(long)q->tid, check_tid))
continue;
if (lws_dll2_foreach_safe(&dns->waiting_resp,
(void *)(long)q->tid, check_tid))
continue;
return 0;
} while (budget--);
lwsl_err("%s: unable to get unique tid\n", __func__);
return -1;
}
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)
{
lws_async_dns_t *dns = &context->async_dns;
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;
#if !defined(LWS_WITH_IPV6)
if (qtype == LWS_ADNS_RECORD_AAAA) {
lwsl_err("%s: ipv6 not enabled\n", __func__);
goto failed;
}
#endif
/*
* we magically know 'localhost' and 'localhost6' if IPv6, this is a
* sort of canned /etc/hosts
*/
if (!strcmp(name, "localhost"))
name = "127.0.0.1";
#if defined(LWS_WITH_IPV6)
if (!strcmp(name, "localhost6"))
name = "::1";
#endif
if (wsi) {
if (!lws_dll2_is_detached(&wsi->adns)) {
lwsl_err("%s: wsi %p already bound to query %p\n",
__func__, wsi, wsi->adns.owner);
goto failed;
}
wsi->adns_cb = cb;
}
/* there's a done, cached query we can just reuse? */
c = lws_adns_get_cache(dns, name);
if (c) {
cb(wsi, name, c->results, 0, opaque);
return LADNS_RET_FOUND;
}
/*
* It's a 1.2.3.4 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
*/
m = lws_parse_numeric_address(name, ads, sizeof(ads));
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;
memcpy(&sa46[1], 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,
lws_now_usecs() + (3600ll * LWS_US_PER_SEC));
}
if (m == 4) {
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, m);
lws_async_dns_complete(&tmq.tq, c);
return LADNS_RET_FOUND;
}
#if defined(LWS_WITH_IPV6)
if (m == 16) {
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, m);
lws_async_dns_complete(&tmq.tq, c);
return LADNS_RET_FOUND;
}
#endif
/*
* to try anything else we need a remote server configured...
*/
if (!context->async_dns.dns_server_set &&
lws_async_dns_init(context)) {
lwsl_notice("%s: init failed\n", __func__);
goto failed;
}
/* there's an ongoing query we can share the result of */
q = lws_adns_get_query(dns, qtype, &dns->waiting_send, 0, name);
if (!q)
q = lws_adns_get_query(dns, qtype, &dns->waiting_resp, 0, name);
if (q) {
lwsl_debug("%s: dns piggybacking: %d:%s\n", __func__,
qtype, name);
if (wsi)
lws_dll2_add_head(&wsi->adns, &q->wsi_adns);
return LADNS_RET_CONTINUING;
}
/*
* Allocate new query / queries... this is a bit complicated because
* multiple queries in one packet are not supported peoperly in DNS
* itself, and there's no reliable other way to get both ipv6 and ipv4
* (AAAA and A) responses in one hit.
*
* If we don't support ipv6, it's simple, we just ask for A and that's
* it. But if we do support ipv6, we need to ask twice, once for A
* and in a separate query, again for AAAA.
*
* For ipv6, A / ipv4 is routable over ipv6. So we always ask for A
* first and then if ipv6, AAAA separately.
*
* The results need binding into
*/
q = (lws_adns_q_t *)lws_zalloc(sizeof(*q) + nlen + 1, __func__);
if (!q)
goto failed;
if (wsi)
lws_dll2_add_head(&wsi->adns, &q->wsi_adns);
q->qtype = (uint16_t)qtype;
if (lws_async_dns_get_new_tid(context, q))
goto failed;
q->tid &= 0xfffe;
q->context = context;
q->tsi = tsi;
q->opaque = opaque;
if (!wsi)
q->standalone_cb = cb;
lws_sul_schedule(context, tsi, &q->sul, sul_cb_timeout,
lws_now_usecs() +
(DNS_QUERY_TIMEOUT * LWS_US_PER_SEC));
p = (char *)&q[1];
while (nlen--)
*p++ = tolower(*name++);
*p = '\0';
lws_callback_on_writable(dns->wsi);
lws_dll2_add_head(&q->list, &dns->waiting_send);
lwsl_debug("%s: created new query\n", __func__);
return LADNS_RET_CONTINUING;
failed:
cb(wsi, NULL, NULL, LADNS_RET_FAILED, opaque);
return LADNS_RET_FAILED;
}