mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-09 00:00:04 +01:00
async dns: recursion
Handle the situation that we are told to use a CNAME, but the CNAME is not resolved by the remote server... adapt the query to resolve the CNAME and restart it, while retaining the original query name for the cache entry generation. "Recursion" doesn't mean function-calling-a-function type recursion, it remains completely asynchronous on the event loop.
This commit is contained in:
parent
1c09e6e822
commit
efc35fe1e1
5 changed files with 179 additions and 48 deletions
10
README.md
10
README.md
|
@ -91,11 +91,11 @@ lws will generate PINGs and take PONGs as the indication of validity.
|
|||
|
||||
## `lws_system`: Async DNS support
|
||||
|
||||
Master now provides optional Asynchronous (ie, nonblocking) DNS resolving. Enable
|
||||
with `-DLWS_WITH_SYS_ASYNC_DNS=1` at cmake. This provides a quite sophisticated
|
||||
ipv4 + ipv6 capable resolver that autodetects the dns server on several platforms
|
||||
and operates a UDP socket to its port 53 to produce and parse DNS packets
|
||||
from the event loop. And of course, it's extremely compact.
|
||||
Master now provides optional Asynchronous (ie, nonblocking) recursive DNS resolving.
|
||||
Enable with `-DLWS_WITH_SYS_ASYNC_DNS=1` at cmake. This provides a quite
|
||||
sophisticated ipv4 + ipv6 capable resolver that autodetects the dns server on
|
||||
several platforms and operates a UDP socket to its port 53 to produce and parse DNS
|
||||
packets from the event loop. And of course, it's extremely compact.
|
||||
|
||||
It broadly follows the getaddrinfo style api, but instead of creating the results
|
||||
on the heap for each caller, it caches a single result according to the TTL and
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## Introduction
|
||||
|
||||
Lws now features optional asynchronous, ie, nonblocking DNS
|
||||
Lws now features optional asynchronous, ie, nonblocking recursive DNS
|
||||
resolution done on the event loop, enable `-DLWS_WITH_SYS_ASYNC_DNS=1`
|
||||
at cmake to build it in.
|
||||
|
||||
|
@ -35,6 +35,8 @@ Other features
|
|||
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
|
||||
- Uses CNAME resolution inside the same response if present, otherwise
|
||||
recurses to resolve the CNAME (up to 3 deep)
|
||||
- ipv6 pieces only built if cmake `LWS_IPV6` enabled
|
||||
|
||||
## Api
|
||||
|
@ -74,3 +76,25 @@ first cache entry is extended into the second one. At the time the
|
|||
second result arrives, the query is destroyed and the cached results
|
||||
provided on the result callback.
|
||||
|
||||
## Recursion
|
||||
|
||||
Where CNAMEs are returned, DNS servers may take two approaches... if the
|
||||
CNAME is also resolved by the same server and so it knows what it should
|
||||
resolve to, it may provide the CNAME resolution in the same response
|
||||
packet.
|
||||
|
||||
In the case the CNAME is actually resolved by a different name server,
|
||||
the server with the CNAME does not have the information to hand to also
|
||||
resolve the CNAME in the same response. So it just leaves it for the
|
||||
client to sort out.
|
||||
|
||||
The lws implementation can deal with both of these, first it "recurses"
|
||||
(it does not recurse on the process stack but uses its own manual stack)
|
||||
to look for results in the same packet that told it about the CNAME. If
|
||||
there are no results, it resets the query to look instead for the CNAME,
|
||||
and restarts it. It allows this to happen for 3 CNAME deep.
|
||||
|
||||
At the end, either way, the cached result is set using the original
|
||||
query name and the results from the last CNAME in the chain.
|
||||
|
||||
|
||||
|
|
|
@ -71,12 +71,18 @@ again1:
|
|||
pointer = 1;
|
||||
}
|
||||
|
||||
again:
|
||||
if (ls >= e)
|
||||
return -1;
|
||||
|
||||
ll = *ls++;
|
||||
if (ls + ll + 4 > e || ll > budget) {
|
||||
lwsl_notice("%s: label len invalid\n", __func__);
|
||||
if (ls + ll + 1 > e) {
|
||||
lwsl_notice("%s: label len invalid, %ld vs %ld\n", __func__,
|
||||
(ls + ll + 1) - pkt, e - pkt);
|
||||
|
||||
return -1;
|
||||
}
|
||||
if (ll > budget) {
|
||||
lwsl_notice("%s: label too long %d vs %d\n", __func__, ll, budget);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
@ -97,7 +103,7 @@ again:
|
|||
|
||||
if (pointer) {
|
||||
if (*ls)
|
||||
goto again;
|
||||
goto again1;
|
||||
|
||||
/*
|
||||
* special fun rule... if whole qname was a pointer label,
|
||||
|
@ -126,7 +132,7 @@ typedef int (*lws_async_dns_find_t)(const char *name, void *opaque,
|
|||
/* locally query the response packet */
|
||||
|
||||
struct label_stack {
|
||||
char name[64];
|
||||
char name[DNS_MAX];
|
||||
int enl;
|
||||
const uint8_t *p;
|
||||
};
|
||||
|
@ -143,18 +149,18 @@ struct label_stack {
|
|||
*/
|
||||
|
||||
static int
|
||||
lws_adns_iterate(const uint8_t *pkt, int len, const char *expname,
|
||||
lws_async_dns_find_t cb, void *opaque)
|
||||
lws_adns_iterate(lws_adns_q_t *q, const uint8_t *pkt, int len,
|
||||
const char *expname, lws_async_dns_find_t cb, void *opaque)
|
||||
{
|
||||
const uint8_t *e = pkt + len, *p, *pay;
|
||||
struct label_stack stack[4];
|
||||
int n = 0, stp = 0, ansc, m;
|
||||
uint16_t rrtype, rrpaylen;
|
||||
int n = 0, stp = 0, ansc;
|
||||
char *sp, inq;
|
||||
uint32_t ttl;
|
||||
|
||||
lws_strncpy(stack[stp].name, expname, sizeof(stack[stp].name));
|
||||
stack[stp].enl = strlen(expname);
|
||||
lws_strncpy(stack[0].name, expname, sizeof(stack[0].name));
|
||||
stack[0].enl = strlen(expname);
|
||||
|
||||
start:
|
||||
ansc = lws_ser_ru16be(pkt + DHO_NANSWERS);
|
||||
|
@ -168,7 +174,7 @@ start:
|
|||
* query class
|
||||
*/
|
||||
|
||||
resume:
|
||||
|
||||
while (p + 14 < e && (inq || ansc)) {
|
||||
|
||||
if (!inq && !stp)
|
||||
|
@ -215,7 +221,7 @@ resume:
|
|||
*/
|
||||
|
||||
if (lws_ser_ru16be(&p[2]) != 1) {
|
||||
lwsl_debug("%s: non-IN response 0x%x\n", __func__,
|
||||
lwsl_err("%s: non-IN response 0x%x\n", __func__,
|
||||
lws_ser_ru16be(&p[2]));
|
||||
|
||||
return -1;
|
||||
|
@ -251,9 +257,13 @@ resume:
|
|||
if (stack[0].name[n - 1] == '.')
|
||||
n--;
|
||||
|
||||
if (n < 1 || n != stack[stp].enl ||
|
||||
strcmp(stack[0].name, stack[stp].name)) {
|
||||
lwsl_debug("%s: skipping %s vs %s\n", __func__,
|
||||
m = stack[stp].enl;
|
||||
if (stack[stp].name[m - 1] == '.')
|
||||
m--;
|
||||
|
||||
if (n < 1 || n != m ||
|
||||
strncmp(stack[0].name, stack[stp].name, n)) {
|
||||
lwsl_notice("%s: skipping %s vs %s\n", __func__,
|
||||
stack[0].name, stack[stp].name);
|
||||
goto skip;
|
||||
}
|
||||
|
@ -275,14 +285,18 @@ resume:
|
|||
*/
|
||||
|
||||
switch (rrtype) {
|
||||
#if defined(LWS_WITH_IPV6)
|
||||
|
||||
case LWS_ADNS_RECORD_AAAA:
|
||||
if (rrpaylen != 16) {
|
||||
lwsl_err("%s: unexpected rrpaylen\n", __func__);
|
||||
return -1;
|
||||
}
|
||||
#if defined(LWS_WITH_IPV6)
|
||||
goto do_cb;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
|
||||
case LWS_ADNS_RECORD_A:
|
||||
if (rrpaylen != 4) {
|
||||
lwsl_err("%s: unexpected rrpaylen4\n", __func__);
|
||||
|
@ -297,7 +311,7 @@ do_cb:
|
|||
|
||||
case LWS_ADNS_RECORD_CNAME:
|
||||
/*
|
||||
* The name the CNAME refers to should itself be
|
||||
* The name the CNAME refers to MAY itself be
|
||||
* included elsewhere in the response packet.
|
||||
*
|
||||
* So switch tack, stack where to resume from and
|
||||
|
@ -314,6 +328,7 @@ do_cb:
|
|||
return -1;
|
||||
}
|
||||
sp = stack[stp].name;
|
||||
/* get the cname alias */
|
||||
n = lws_adns_parse_label(pkt, len, p, rrpaylen, &sp,
|
||||
sizeof(stack[stp].name) -
|
||||
lws_ptr_diff(sp, stack[stp].name));
|
||||
|
@ -329,11 +344,14 @@ do_cb:
|
|||
/* it should have exactly reached rrpaylen */
|
||||
|
||||
if (p != pay + rrpaylen) {
|
||||
lwsl_err("%s: cname name bad len\n", __func__);
|
||||
lwsl_err("%s: cname name bad len %d\n", __func__, rrpaylen);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
lwsl_info("%s: recursing looking for %s\n", __func__,
|
||||
stack[stp].name);
|
||||
|
||||
stack[stp].enl = lws_ptr_diff(sp, stack[stp].name);
|
||||
/* when we unstack, resume from here */
|
||||
stack[stp].p = pay + rrpaylen;
|
||||
|
@ -350,16 +368,61 @@ skip:
|
|||
if (!stp)
|
||||
return 1; /* we didn't find anything, but we didn't error */
|
||||
|
||||
lwsl_info("%s: '%s' -> CNAME '%s' resolution not provided, recursing\n",
|
||||
__func__, ((const char *)&q[1]) + DNS_MAX,
|
||||
stack[stp].name);
|
||||
|
||||
/*
|
||||
* This implies there wasn't any usable definition for the
|
||||
* CNAME in the end, eg, only AAAA when we needed and A.
|
||||
* CNAME in the end, eg, only AAAA when we needed an A.
|
||||
*
|
||||
* Short-circuit the whole stack and resume from after the
|
||||
* original CNAME reference.
|
||||
* It's also legit if the DNS just returns the CNAME, and that server
|
||||
* did not directly know the next step in resolution of the CNAME, so
|
||||
* instead of putting the resolution elsewhere in the response, has
|
||||
* told us just the CNAME and left it to us to find out its resolution
|
||||
* separately.
|
||||
*
|
||||
* Reset this request to be for the CNAME, and restart the request
|
||||
* action with a new tid.
|
||||
*/
|
||||
p = stack[1].p;
|
||||
stp = 0;
|
||||
goto resume;
|
||||
|
||||
if (lws_async_dns_get_new_tid(q->context, q))
|
||||
return -1;
|
||||
|
||||
q->tid &= 0xfffe;
|
||||
q->asked = q->responded = 0;
|
||||
#if defined(LWS_WITH_IPV6)
|
||||
q->sent[1] = 0;
|
||||
#endif
|
||||
q->sent[0] = 0;
|
||||
q->recursion++;
|
||||
if (q->recursion == DNS_RECURSION_LIMIT) {
|
||||
lwsl_err("%s: recursion overflow\n", __func__);
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (q->firstcache)
|
||||
lws_adns_cache_destroy(q->firstcache);
|
||||
q->firstcache = NULL;
|
||||
|
||||
/* overwrite the query name with the CNAME */
|
||||
|
||||
n = 0;
|
||||
{
|
||||
char *cp = (char *)&q[1];
|
||||
|
||||
while (stack[stp].name[n])
|
||||
*cp++ = tolower(stack[stp].name[n++]);
|
||||
/* trim the following . if any */
|
||||
if (n && cp[-1] == '.')
|
||||
cp--;
|
||||
*cp = '\0';
|
||||
}
|
||||
|
||||
lws_callback_on_writable(q->dns->wsi);
|
||||
|
||||
return 2;
|
||||
}
|
||||
|
||||
int
|
||||
|
@ -464,12 +527,12 @@ lws_async_dns_store(const char *name, void *opaque, uint32_t ttl,
|
|||
void
|
||||
lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len)
|
||||
{
|
||||
const char *nm, *nmcname;
|
||||
lws_adns_cache_t *c;
|
||||
struct adstore adst;
|
||||
lws_adns_q_t *q;
|
||||
const char *nm;
|
||||
int n, ncname;
|
||||
size_t est;
|
||||
int n;
|
||||
|
||||
// lwsl_hexdump_notice(pkt, len);
|
||||
|
||||
|
@ -503,7 +566,10 @@ lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len)
|
|||
}
|
||||
|
||||
q->responded |= n;
|
||||
nm = (const char *)&q[1];
|
||||
|
||||
/* we want to confirm the results against what we last requested... */
|
||||
|
||||
nmcname = ((const char *)&q[1]);
|
||||
|
||||
/*
|
||||
* First walk the packet figuring out the allocation needed for all
|
||||
|
@ -514,13 +580,26 @@ lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len)
|
|||
* char []: copy of resolved name
|
||||
*/
|
||||
|
||||
n = strlen(nm) + 1;
|
||||
ncname = strlen(nmcname) + 1;
|
||||
|
||||
est = sizeof(lws_adns_cache_t) + n;
|
||||
if (lws_ser_ru16be(pkt + DHO_NANSWERS) &&
|
||||
lws_adns_iterate(pkt, len, nm, lws_async_dns_estimate, &est) < 0)
|
||||
est = sizeof(lws_adns_cache_t) + ncname;
|
||||
if (lws_ser_ru16be(pkt + DHO_NANSWERS)) {
|
||||
int ir = lws_adns_iterate(q, pkt, len, nmcname,
|
||||
lws_async_dns_estimate, &est);
|
||||
if (ir < 0)
|
||||
goto fail_out;
|
||||
|
||||
if (ir == 2) /* CNAME recursive resolution */
|
||||
return;
|
||||
}
|
||||
|
||||
/* but we want to create the cache entry against the original request */
|
||||
|
||||
nm = ((const char *)&q[1]) + DNS_MAX;
|
||||
n = strlen(nm) + 1;
|
||||
|
||||
lwsl_info("%s: create cache entry for %s, %zu\n", __func__, nm,
|
||||
est - sizeof(lws_adns_cache_t));
|
||||
c = lws_malloc(est, "async-dns-entry");
|
||||
if (!c) {
|
||||
lwsl_err("%s: OOM %zu\n", __func__, est);
|
||||
|
@ -550,7 +629,7 @@ lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len)
|
|||
*/
|
||||
|
||||
if (lws_ser_ru16be(pkt + DHO_NANSWERS) &&
|
||||
lws_adns_iterate(pkt, len, nm, lws_async_dns_store, &adst) < 0) {
|
||||
lws_adns_iterate(q, pkt, len, nmcname, lws_async_dns_store, &adst) < 0) {
|
||||
lws_free(c);
|
||||
goto fail_out;
|
||||
}
|
||||
|
|
|
@ -482,7 +482,7 @@ check_tid(struct lws_dll2 *d, void *user)
|
|||
return q->tid == (uint16_t)(long)user;
|
||||
}
|
||||
|
||||
static int
|
||||
int
|
||||
lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q)
|
||||
{
|
||||
lws_async_dns_t *dns = &context->async_dns;
|
||||
|
@ -492,13 +492,17 @@ lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q)
|
|||
* Make the TID unpredictable, but must be unique amongst ongoing ones
|
||||
*/
|
||||
do {
|
||||
if (lws_get_random(context, &q->tid, 2) != 2)
|
||||
uint16_t tid;
|
||||
|
||||
if (lws_get_random(context, &tid, 2) != 2)
|
||||
return -1;
|
||||
|
||||
if (lws_dll2_foreach_safe(&dns->waiting,
|
||||
(void *)(long)q->tid, check_tid))
|
||||
(void *)(long)tid, check_tid))
|
||||
continue;
|
||||
|
||||
q->tid = tid;
|
||||
|
||||
return 0;
|
||||
|
||||
} while (budget--);
|
||||
|
@ -536,6 +540,9 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name,
|
|||
}
|
||||
#endif
|
||||
|
||||
if (nlen >= DNS_MAX - 1)
|
||||
goto failed;
|
||||
|
||||
/*
|
||||
* we magically know 'localhost' and 'localhost6' if IPv6, this is a
|
||||
* sort of canned /etc/hosts
|
||||
|
@ -674,12 +681,18 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name,
|
|||
* 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
|
||||
* Allocate for DNS_MAX, because we may recurse and alter what we're
|
||||
* looking for.
|
||||
*
|
||||
* 0 sizeof(*q) sizeof(*q) + DNS_MAX
|
||||
* [lws_adns_q_t][ name (DNS_MAX reserved) ] [ name \0 ]
|
||||
*/
|
||||
|
||||
q = (lws_adns_q_t *)lws_zalloc(sizeof(*q) + nlen + 1, __func__);
|
||||
q = (lws_adns_q_t *)lws_malloc(sizeof(*q) + DNS_MAX + nlen + 1,
|
||||
__func__);
|
||||
if (!q)
|
||||
goto failed;
|
||||
memset(q, 0, sizeof(*q));
|
||||
|
||||
if (wsi)
|
||||
lws_dll2_add_head(&wsi->adns, &q->wsi_adns);
|
||||
|
@ -702,10 +715,19 @@ lws_async_dns_query(struct lws_context *context, int tsi, const char *name,
|
|||
lws_retry_sul_schedule_retry_wsi(dns->wsi, &q->sul,
|
||||
lws_async_dns_sul_cb_retry, &q->retry);
|
||||
|
||||
/*
|
||||
* 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--)
|
||||
while (nlen--) {
|
||||
*p++ = tolower(*name++);
|
||||
p[DNS_MAX - 1] = p[-1];
|
||||
}
|
||||
*p = '\0';
|
||||
p[DNS_MAX] = '\0';
|
||||
|
||||
lws_callback_on_writable(dns->wsi);
|
||||
|
||||
|
|
|
@ -23,10 +23,11 @@
|
|||
*/
|
||||
|
||||
|
||||
#define DNS_MAX 128 /* Maximum host name */
|
||||
#define DNS_PACKET_LEN 1400 /* Buffer size for DNS packet */
|
||||
#define MAX_CACHE_ENTRIES 10 /* Dont cache more than that */
|
||||
#define DNS_QUERY_TIMEOUT 30 /* Query timeout, seconds */
|
||||
#define DNS_MAX 96 /* Maximum host name */
|
||||
#define DNS_RECURSION_LIMIT 3
|
||||
#define DNS_PACKET_LEN 1400 /* Buffer size for DNS packet */
|
||||
#define MAX_CACHE_ENTRIES 10 /* Dont cache more than that */
|
||||
#define DNS_QUERY_TIMEOUT 30 /* Query timeout, seconds */
|
||||
|
||||
/*
|
||||
* ... when we completed a query then the query object is destroyed and a
|
||||
|
@ -78,6 +79,8 @@ typedef struct {
|
|||
uint8_t asked;
|
||||
uint8_t responded;
|
||||
|
||||
uint8_t recursion;
|
||||
|
||||
/* name overallocated here */
|
||||
} lws_adns_q_t;
|
||||
|
||||
|
@ -116,3 +119,6 @@ lws_adns_get_query(lws_async_dns_t *dns, adns_query_type_t qtype,
|
|||
|
||||
void
|
||||
lws_async_dns_trim_cache(lws_async_dns_t *dns);
|
||||
|
||||
int
|
||||
lws_async_dns_get_new_tid(struct lws_context *context, lws_adns_q_t *q);
|
||||
|
|
Loading…
Add table
Reference in a new issue