1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-23 00:00:06 +01:00
libwebsockets/lib/system/async-dns/async-dns.c

672 lines
15 KiB
C
Raw Normal View History

/*
* 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;
}