• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Copyright (C) 2010 - 2021 Andy Green <andy@warmcat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  * IN THE SOFTWARE.
23  */
24 
25 #include "private-lib-core.h"
26 #include "private-lib-async-dns.h"
27 
28 
29 /* updates *dest, returns chars used from ls directly, else -1 for fail */
30 
31 static int
lws_adns_parse_label(const uint8_t * pkt,int len,const uint8_t * ls,int budget,char ** dest,size_t dl)32 lws_adns_parse_label(const uint8_t *pkt, int len, const uint8_t *ls, int budget,
33 		     char **dest, size_t dl)
34 {
35 	const uint8_t *e = pkt + len, *ols = ls;
36 	char pointer = 0, first = 1;
37 	uint8_t ll;
38 	int n;
39 
40 	if (budget < 1)
41 		return 0;
42 
43 	/* caller must catch end of labels */
44 	assert(*ls);
45 
46 again1:
47 	if (ls >= e)
48 		return -1;
49 
50 	if (((*ls) & 0xc0) == 0xc0) {
51 		if (budget < 2)
52 			return -1;
53 		/* pointer into message pkt to name to actually use */
54 		n = lws_ser_ru16be(ls) & 0x3fff;
55 		if (n >= len) {
56 			lwsl_notice("%s: illegal name pointer\n", __func__);
57 
58 			return -1;
59 		}
60 
61 		/* dereference the label pointer */
62 		ls = pkt + n;
63 
64 		/* are we being fuzzed or messed with? */
65 		if (((*ls) & 0xc0) == 0xc0) {
66 			/* ... pointer to pointer is unreasonable */
67 			lwsl_notice("%s: label ptr to ptr invalid\n", __func__);
68 
69 			return -1;
70 		}
71 		pointer = 1;
72 	}
73 
74 	if (ls >= e)
75 		return -1;
76 
77 	ll = *ls++;
78 	if (ls + ll + 1 > e) {
79 		lwsl_notice("%s: label len invalid, %d vs %d\n", __func__,
80 			    lws_ptr_diff((ls + ll + 1), pkt), lws_ptr_diff(e, pkt));
81 
82 		return -1;
83 	}
84 
85 	if (ls + ll > ols + budget) {
86 		lwsl_notice("%s: label too long %d vs %d\n", __func__, ll, budget);
87 
88 		return -1;
89 	}
90 
91 	if ((unsigned int)ll + 2 > dl) {
92 		lwsl_notice("%s: qname too large\n", __func__);
93 
94 		return -1;
95 	}
96 
97 	/* copy the label content into place */
98 
99 	memcpy(*dest, ls, ll);
100 	(*dest)[ll] = '.';
101 	(*dest)[ll + 1] = '\0';
102 	*dest += ll + 1;
103 	ls += ll;
104 
105 	if (pointer) {
106 		if (*ls)
107 			goto again1;
108 
109 		/*
110 		 * special fun rule... if whole qname was a pointer label,
111 		 * it has no 00 terminator afterwards
112 		 */
113 		if (first)
114 			return 2; /* we just took the 16-bit pointer */
115 
116 		return 3;
117 	}
118 
119 	first = 0;
120 
121 	if (*ls)
122 		goto again1;
123 
124 	ls++;
125 
126 	return lws_ptr_diff(ls, ols);
127 }
128 
129 typedef int (*lws_async_dns_find_t)(const char *name, void *opaque,
130 				    uint32_t ttl, adns_query_type_t type,
131 				    const uint8_t *payload);
132 
133 /* locally query the response packet */
134 
135 struct label_stack {
136 	char name[DNS_MAX];
137 	int enl;
138 	const uint8_t *p;
139 };
140 
141 /*
142  * Walk the response packet, calling back to the user-provided callback for each
143  * A (and AAAA if LWS_IPV6=1) record with a matching name found in there.
144  *
145  * Able to recurse using an explicit non-CPU stack to resolve CNAME usages
146  *
147  * Return -1: unexpectedly failed
148  *         0: found
149  *         1: didn't find anything matching
150  */
151 
152 static int
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)153 lws_adns_iterate(lws_adns_q_t *q, const uint8_t *pkt, int len,
154 		 const char *expname, lws_async_dns_find_t cb, void *opaque)
155 {
156 	const uint8_t *e = pkt + len, *p, *pay;
157 	struct label_stack stack[4];
158 	int n = 0, stp = 0, ansc, m;
159 	uint16_t rrtype, rrpaylen;
160 	char *sp, inq;
161 	uint32_t ttl;
162 
163 	lws_strncpy(stack[0].name, expname, sizeof(stack[0].name));
164 	stack[0].enl = (int)strlen(expname);
165 
166 start:
167 	ansc = lws_ser_ru16be(pkt + DHO_NANSWERS);
168 	p = pkt + DHO_SIZEOF;
169 	inq = 1;
170 
171 	/*
172 	 * The response also includes the query... and we have to parse it
173 	 * so we can understand we reached the response... there's a QNAME
174 	 * made up of labels and then 2 x 16-bit fields, for query type and
175 	 * query class
176 	 */
177 
178 
179 	while (p + 14 < e && (inq || ansc)) {
180 
181 		if (!inq && !stp)
182 			ansc--;
183 
184 		/*
185 		 * First is the name the query applies to... two main
186 		 * formats can appear here, one is a pointer to
187 		 * elsewhere in the message, the other separately
188 		 * provides len / data for each dotted "label", so for
189 		 * "warmcat.com" warmcat and com are given each with a
190 		 * prepended length byte.  Any of those may be a pointer
191 		 * to somewhere else in the packet :-/
192 		 *
193 		 * Paranoia is appropriate since the name length must be
194 		 * parsed out before the rest of the RR can be used and
195 		 * we can be attacked with absolutely any crafted
196 		 * content easily via UDP.
197 		 *
198 		 * So parse the name and additionally confirm it matches
199 		 * what the query the TID belongs to actually asked for.
200 		 */
201 
202 		sp = stack[0].name;
203 
204 		/* while we have more labels */
205 
206 		n = lws_adns_parse_label(pkt, len, p, len, &sp,
207 					 sizeof(stack[0].name) -
208 					 lws_ptr_diff_size_t(sp, stack[0].name));
209 		/* includes case name won't fit */
210 		if (n < 0)
211 			return -1;
212 
213 		p += n;
214 
215 		if (p + (inq ? 5 : 14) > e)
216 			return -1;
217 
218 		/*
219 		 * p is now just after the decoded RR name, pointing at: type
220 		 *
221 		 * We sent class = 1 = IN query... response must match
222 		 */
223 
224 		if (lws_ser_ru16be(&p[2]) != 1) {
225 			lwsl_err("%s: non-IN response 0x%x\n", __func__,
226 						lws_ser_ru16be(&p[2]));
227 
228 			return -1;
229 		}
230 
231 		if (inq) {
232 			lwsl_debug("%s: reached end of inq\n", __func__);
233 			inq = 0;
234 			p += 4;
235 			continue;
236 		}
237 
238 		/* carefully validate the claimed RR payload length */
239 
240 		rrpaylen = lws_ser_ru16be(&p[8]);
241 		if (p + 10 + rrpaylen > e) { /* it may be == e */
242 			lwsl_notice("%s: invalid RR data length\n", __func__);
243 
244 			return -1;
245 		}
246 
247 		ttl = lws_ser_ru32be(&p[4]);
248 		rrtype = lws_ser_ru16be(&p[0]);
249 		p += 10; /* point to the payload */
250 		pay = p;
251 
252 		/*
253 		 * Compare the RR names, allowing for the decoded labelname
254 		 * to have an extra '.' at the end.
255 		 */
256 
257 		n = lws_ptr_diff(sp, stack[0].name);
258 		if (stack[0].name[n - 1] == '.')
259 			n--;
260 
261 		m = stack[stp].enl;
262 		if (stack[stp].name[m - 1] == '.')
263 			m--;
264 
265 		if (n < 1 || n != m ||
266 		    strncmp(stack[0].name, stack[stp].name, (unsigned int)n)) {
267 			//lwsl_notice("%s: skipping %s vs %s\n", __func__,
268 			//		stack[0].name, stack[stp].name);
269 			goto skip;
270 		}
271 
272 		/*
273 		 * It's something we could be interested in...
274 		 *
275 		 * We can skip RRs we don't understand.  But we need to deal
276 		 * with at least these and their payloads:
277 		 *
278 		 *    A:      4: ipv4 address
279 		 *    AAAA:  16: ipv6 address (if asked for AAAA)
280 		 *    CNAME:  ?: labelized name
281 		 *
282 		 * If we hit a CNAME we need to try to dereference it with
283 		 * stuff that is in the same response packet and judge it
284 		 * from that, without losing our place here.  CNAMEs may
285 		 * point to CNAMEs to whatever depth we're willing to handle.
286 		 */
287 
288 		switch (rrtype) {
289 
290 		case LWS_ADNS_RECORD_AAAA:
291 			if (rrpaylen != 16) {
292 				lwsl_err("%s: unexpected rrpaylen\n", __func__);
293 				return -1;
294 			}
295 #if defined(LWS_WITH_IPV6)
296 			goto do_cb;
297 #else
298 			break;
299 #endif
300 
301 		case LWS_ADNS_RECORD_A:
302 			if (rrpaylen != 4) {
303 				lwsl_err("%s: unexpected rrpaylen4\n", __func__);
304 
305 				return -1;
306 			}
307 #if defined(LWS_WITH_IPV6)
308 do_cb:
309 #endif
310 			cb(stack[0].name, opaque, ttl, rrtype, p);
311 			break;
312 
313 		case LWS_ADNS_RECORD_CNAME:
314 			/*
315 			 * The name the CNAME refers to MAY itself be
316 			 * included elsewhere in the response packet.
317 			 *
318 			 * So switch tack, stack where to resume from and
319 			 * search for the decoded CNAME label name definition
320 			 * instead.
321 			 *
322 			 * First decode the CNAME label payload into the next
323 			 * stack level buffer for it.
324 			 */
325 
326 			if (++stp == (int)LWS_ARRAY_SIZE(stack)) {
327 				lwsl_notice("%s: CNAMEs too deep\n", __func__);
328 
329 				return -1;
330 			}
331 			sp = stack[stp].name;
332 			/* get the cname alias */
333 			n = lws_adns_parse_label(pkt, len, p, rrpaylen, &sp,
334 						 sizeof(stack[stp].name) -
335 						 lws_ptr_diff_size_t(sp, stack[stp].name));
336 			/* includes case name won't fit */
337 			if (n < 0)
338 				return -1;
339 
340 			p += n;
341 
342 			if (p + 14 > e)
343 				return -1;
344 #if 0
345 			/* it should have exactly reached rrpaylen if only one
346 			 * CNAME, else somewhere in the middle */
347 
348 			if (p != pay + rrpaylen) {
349 				lwsl_err("%s: cname name bad len %d\n", __func__, rrpaylen);
350 
351 				return -1;
352 			}
353 #endif
354 			lwsl_notice("%s: recursing looking for %s\n", __func__, stack[stp].name);
355 
356 			lwsl_info("%s: recursing looking for %s\n", __func__,
357 					stack[stp].name);
358 
359 			stack[stp].enl = lws_ptr_diff(sp, stack[stp].name);
360 			/* when we unstack, resume from here */
361 			stack[stp].p = pay + rrpaylen;
362 			goto start;
363 
364 		default:
365 			break;
366 		}
367 
368 skip:
369 		p += rrpaylen;
370 	}
371 
372 	if (!stp)
373 		return 1; /* we didn't find anything, but we didn't error */
374 
375 	lwsl_info("%s: '%s' -> CNAME '%s' resolution not provided, recursing\n",
376 			__func__, ((const char *)&q[1]) + DNS_MAX,
377 			stack[stp].name);
378 
379 	/*
380 	 * This implies there wasn't any usable definition for the
381 	 * CNAME in the end, eg, only AAAA when we needed an A.
382 	 *
383 	 * It's also legit if the DNS just returns the CNAME, and that server
384 	 * did not directly know the next step in resolution of the CNAME, so
385 	 * instead of putting the resolution elsewhere in the response, has
386 	 * told us just the CNAME and left it to us to find out its resolution
387 	 * separately.
388 	 *
389 	 * Reset this request to be for the CNAME, and restart the request
390 	 * action with a new tid.
391 	 */
392 
393 	if (lws_async_dns_get_new_tid(q->context, q))
394 		return -1;
395 
396 	LADNS_MOST_RECENT_TID(q) &= 0xfffe;
397 	q->asked = q->responded = 0;
398 #if defined(LWS_WITH_IPV6)
399 	q->sent[1] = 0;
400 #endif
401 	q->sent[0] = 0;
402 	q->recursion++;
403 	if (q->recursion == DNS_RECURSION_LIMIT) {
404 		lwsl_err("%s: recursion overflow\n", __func__);
405 
406 		return -1;
407 	}
408 
409 	if (q->firstcache)
410 		lws_adns_cache_destroy(q->firstcache);
411 	q->firstcache = NULL;
412 
413 	/* overwrite the query name with the CNAME */
414 
415 	n = 0;
416 	{
417 		char *cp = (char *)&q[1];
418 
419 		while (stack[stp].name[n])
420 			*cp++ = (char)tolower(stack[stp].name[n++]);
421 		/* trim the following . if any */
422 		if (n && cp[-1] == '.')
423 			cp--;
424 		*cp = '\0';
425 	}
426 
427 	lws_callback_on_writable(q->dns->wsi);
428 
429 	return 2;
430 }
431 
432 int
lws_async_dns_estimate(const char * name,void * opaque,uint32_t ttl,adns_query_type_t type,const uint8_t * payload)433 lws_async_dns_estimate(const char *name, void *opaque, uint32_t ttl,
434 			adns_query_type_t type, const uint8_t *payload)
435 {
436 	size_t *est = (size_t *)opaque, my;
437 
438 	my = sizeof(struct addrinfo);
439 	if (type == LWS_ADNS_RECORD_AAAA)
440 		my += sizeof(struct sockaddr_in6);
441 	else
442 		my += sizeof(struct sockaddr_in);
443 
444 	*est += my;
445 
446 	return 0;
447 }
448 
449 struct adstore {
450 	const char *name;
451 	struct addrinfo *pos;
452 	struct addrinfo *prev;
453 	int ctr;
454 	uint32_t smallest_ttl;
455 	uint8_t flags;
456 };
457 
458 /*
459  * Callback for each A or AAAA record, creating getaddrinfo-compatible results
460  * into the preallocated exact-sized storage.
461  */
462 int
lws_async_dns_store(const char * name,void * opaque,uint32_t ttl,adns_query_type_t type,const uint8_t * payload)463 lws_async_dns_store(const char *name, void *opaque, uint32_t ttl,
464 		    adns_query_type_t type, const uint8_t *payload)
465 {
466 	struct adstore *adst = (struct adstore *)opaque;
467 #if defined(_DEBUG)
468 	char buf[48];
469 #endif
470 	size_t i;
471 
472 	if (ttl < adst->smallest_ttl || !adst->ctr)
473 		adst->smallest_ttl = ttl;
474 
475 	if (adst->prev)
476 		adst->prev->ai_next = adst->pos;
477 	adst->prev = adst->pos;
478 
479 	adst->pos->ai_flags = 0;
480 	adst->pos->ai_family = type == LWS_ADNS_RECORD_AAAA ?
481 						AF_INET6 : AF_INET;
482 	adst->pos->ai_socktype = SOCK_STREAM;
483 	adst->pos->ai_protocol = IPPROTO_UDP; /* no meaning */
484 	adst->pos->ai_addrlen = type == LWS_ADNS_RECORD_AAAA ?
485 						sizeof(struct sockaddr_in6) :
486 						sizeof(struct sockaddr_in);
487 	adst->pos->ai_canonname = (char *)adst->name;
488 	adst->pos->ai_addr = (struct sockaddr *)&adst->pos[1];
489 	adst->pos->ai_next = NULL;
490 
491 #if defined(LWS_WITH_IPV6)
492 	if (type == LWS_ADNS_RECORD_AAAA) {
493 		struct sockaddr_in6 *in6 = (struct sockaddr_in6 *)&adst->pos[1];
494 
495 		i = sizeof(*in6);
496 		memset(in6, 0, i);
497 		in6->sin6_family = (sa_family_t)adst->pos->ai_family;
498 		memcpy(in6->sin6_addr.s6_addr, payload, 16);
499 		adst->flags |= 2;
500 	} else
501 #endif
502 	{
503 		struct sockaddr_in *in = (struct sockaddr_in *)&adst->pos[1];
504 
505 		i = sizeof(*in);
506 		memset(in, 0, i);
507 		in->sin_family = (sa_family_t)adst->pos->ai_family;
508 		memcpy(&in->sin_addr.s_addr, payload, 4);
509 		adst->flags |= 1;
510 	}
511 
512 	adst->pos = (struct addrinfo *)((uint8_t *)adst->pos +
513 					sizeof(struct addrinfo) + i);
514 
515 #if defined(_DEBUG)
516 	if (lws_write_numeric_address(payload,
517 				type == LWS_ADNS_RECORD_AAAA ? 16 : 4,
518 							buf, sizeof(buf)) > 0)
519 		lwsl_info("%s: %d: %s: %s\n", __func__, adst->ctr,
520 				adst->name, buf);
521 #endif
522 	adst->ctr++;
523 
524 	return 0;
525 }
526 
527 /*
528  * We want to parse out all A or AAAA records
529  */
530 
531 void
lws_adns_parse_udp(lws_async_dns_t * dns,const uint8_t * pkt,size_t len)532 lws_adns_parse_udp(lws_async_dns_t *dns, const uint8_t *pkt, size_t len)
533 {
534 	const char *nm, *nmcname;
535 	lws_adns_cache_t *c;
536 	struct adstore adst;
537 	lws_adns_q_t *q;
538 	int n, ncname;
539 	size_t est;
540 
541 	// lwsl_hexdump_notice(pkt, len);
542 
543 	/* we have to at least have the header */
544 
545 	if (len < DHO_SIZEOF)
546 		return;
547 
548 	/* we asked with one query, so anything else is bogus */
549 
550 	if (lws_ser_ru16be(pkt + DHO_NQUERIES) != 1)
551 		return;
552 
553 	/* match both A and AAAA queries if any */
554 
555 	q = lws_adns_get_query(dns, 0, &dns->waiting,
556 			       lws_ser_ru16be(pkt + DHO_TID), NULL);
557 	if (!q) {
558 		lwsl_info("%s: dropping unknown query tid 0x%x\n",
559 			    __func__, lws_ser_ru16be(pkt + DHO_TID));
560 
561 		return;
562 	}
563 
564 	/* we can get dups... drop any that have already happened */
565 
566 	n = 1 << (lws_ser_ru16be(pkt + DHO_TID) & 1);
567 	if (q->responded & n) {
568 		lwsl_notice("%s: dup\n", __func__);
569 		goto fail_out;
570 	}
571 
572 	q->responded = (uint8_t)(q->responded | n);
573 
574 	/* we want to confirm the results against what we last requested... */
575 
576 	nmcname = ((const char *)&q[1]);
577 
578 	/*
579 	 * First walk the packet figuring out the allocation needed for all
580 	 * the results.  Produce the following layout at c
581 	 *
582 	 *  lws_adns_cache_t: new cache object
583 	 *  [struct addrinfo + struct sockaddr_in or _in6]: for each A or AAAA
584 	 *  char []: copy of resolved name
585 	 */
586 
587 	ncname = (int)strlen(nmcname) + 1;
588 
589 	est = sizeof(lws_adns_cache_t) + (unsigned int)ncname;
590 	if (lws_ser_ru16be(pkt + DHO_NANSWERS)) {
591 		int ir = lws_adns_iterate(q, pkt, (int)len, nmcname,
592 					  lws_async_dns_estimate, &est);
593 		if (ir < 0)
594 			goto fail_out;
595 
596 		if (ir == 2) /* CNAME recursive resolution */
597 			return;
598 	}
599 
600 	/* but we want to create the cache entry against the original request */
601 
602 	nm = ((const char *)&q[1]) + DNS_MAX;
603 	n = (int)strlen(nm) + 1;
604 
605 	lwsl_info("%s: create cache entry for %s, %zu\n", __func__, nm,
606 			est - sizeof(lws_adns_cache_t));
607 	c = lws_malloc(est + 1, "async-dns-entry");
608 	if (!c) {
609 		lwsl_err("%s: OOM %zu\n", __func__, est);
610 		goto fail_out;
611 	}
612 	memset(c, 0, sizeof(*c));
613 
614 	/* place it at end, no need to care about alignment padding */
615 	c->name = adst.name = ((const char *)c) + est - n;
616 	memcpy((char *)c->name, nm, (unsigned int)n);
617 
618 	/*
619 	 * Then walk the packet again, placing the objects we accounted for
620 	 * the first time into the result allocation after the cache object
621 	 * and copy of the name
622 	 */
623 
624 	adst.pos = (struct addrinfo *)&c[1];
625 	adst.prev = NULL;
626 	adst.ctr = 0;
627 	adst.smallest_ttl = 3600;
628 	adst.flags = 0;
629 
630 	/*
631 	 * smallest_ttl applies as it is to empty results (NXDOMAIN), or is
632 	 * set to the minimum ttl seen in all the results.
633 	 */
634 
635 	if (lws_ser_ru16be(pkt + DHO_NANSWERS) &&
636 	    lws_adns_iterate(q, pkt, (int)len, nmcname, lws_async_dns_store, &adst) < 0) {
637 		lws_free(c);
638 		goto fail_out;
639 	}
640 
641 	if (lws_ser_ru16be(pkt + DHO_NANSWERS)) {
642 		c->results = (struct addrinfo *)&c[1];
643 		if (q->last) /* chain the second one on */
644 			*q->last = c->results;
645 		else /* first one had no results, set first guy's c->results */
646 			if (q->firstcache)
647 				q->firstcache->results = c->results;
648 	}
649 
650 	if (adst.prev) /* so we know where to continue the addrinfo list */
651 		/* can be NULL if first resp empty */
652 		q->last = &adst.prev->ai_next;
653 
654 	if (q->firstcache) { /* also need to free chain when we free this guy */
655 		q->firstcache->chain = c;
656 		c->firstcache = q->firstcache;
657 	} else {
658 
659 		q->firstcache = c;
660 		c->incomplete = !q->responded;// != q->asked;
661 
662 		/*
663 		 * Only register the first one into the cache...
664 		 * Trim the oldest cache entry if necessary
665 		 */
666 
667 		lws_async_dns_trim_cache(dns);
668 
669 		/*
670 		 * cache the first results object... if a second one comes,
671 		 * we won't directly register it but will chain it on to this
672 		 * first one and continue to addinfo ai_next linked list from
673 		 * the first into the second
674 		 */
675 
676 		c->flags = adst.flags;
677 		lws_dll2_add_head(&c->list, &dns->cached);
678 		lws_sul_schedule(q->context, 0, &c->sul, sul_cb_expire,
679 				 lws_now_usecs() +
680 				 (adst.smallest_ttl * LWS_US_PER_SEC));
681 	}
682 
683 	if (q->responded != q->asked)
684 		return;
685 
686 	/*
687 	 * Now we captured everything into the new object, return the
688 	 * addrinfo results, if any, to all interested wsi, if any...
689 	 */
690 
691 	c->incomplete = 0;
692 	lws_async_dns_complete(q, q->firstcache);
693 
694 	q->go_nogo = METRES_GO;
695 
696 	/*
697 	 * the query is completely finished with
698 	 */
699 
700 fail_out:
701 	lws_adns_q_destroy(q);
702 }
703 
704