1 /* MIT License
2 *
3 * Copyright (c) 2023 Brad House
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to deal
7 * in the Software without restriction, including without limitation the rights
8 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 * copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice (including the next
13 * paragraph) shall be included in all copies or substantial portions of the
14 * 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 FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22 * SOFTWARE.
23 *
24 * SPDX-License-Identifier: MIT
25 */
26 #include "ares_setup.h"
27 #include "ares.h"
28 #include "ares_private.h"
29
30 struct ares__qcache {
31 ares__htable_strvp_t *cache;
32 ares__slist_t *expire;
33 unsigned int max_ttl;
34 };
35
36 typedef struct {
37 char *key;
38 ares_dns_record_t *dnsrec;
39 time_t expire_ts;
40 time_t insert_ts;
41 } ares__qcache_entry_t;
42
ares__qcache_calc_key(const ares_dns_record_t * dnsrec)43 static char *ares__qcache_calc_key(const ares_dns_record_t *dnsrec)
44 {
45 ares__buf_t *buf = ares__buf_create();
46 size_t i;
47 ares_status_t status;
48 ares_dns_flags_t flags;
49
50 if (dnsrec == NULL || buf == NULL) {
51 return NULL;
52 }
53
54 /* Format is OPCODE|FLAGS[|QTYPE1|QCLASS1|QNAME1]... */
55
56 status = ares__buf_append_str(
57 buf, ares_dns_opcode_tostr(ares_dns_record_get_opcode(dnsrec)));
58 if (status != ARES_SUCCESS) {
59 goto fail;
60 }
61
62 status = ares__buf_append_byte(buf, '|');
63 if (status != ARES_SUCCESS) {
64 goto fail;
65 }
66
67 flags = ares_dns_record_get_flags(dnsrec);
68 /* Only care about RD and CD */
69 if (flags & ARES_FLAG_RD) {
70 status = ares__buf_append_str(buf, "rd");
71 if (status != ARES_SUCCESS) {
72 goto fail;
73 }
74 }
75 if (flags & ARES_FLAG_CD) {
76 status = ares__buf_append_str(buf, "cd");
77 if (status != ARES_SUCCESS) {
78 goto fail;
79 }
80 }
81
82 for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) {
83 const char *name;
84 ares_dns_rec_type_t qtype;
85 ares_dns_class_t qclass;
86
87 status = ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass);
88 if (status != ARES_SUCCESS) {
89 goto fail;
90 }
91
92 status = ares__buf_append_byte(buf, '|');
93 if (status != ARES_SUCCESS) {
94 goto fail;
95 }
96
97 status = ares__buf_append_str(buf, ares_dns_rec_type_tostr(qtype));
98 if (status != ARES_SUCCESS) {
99 goto fail;
100 }
101
102 status = ares__buf_append_byte(buf, '|');
103 if (status != ARES_SUCCESS) {
104 goto fail;
105 }
106
107 status = ares__buf_append_str(buf, ares_dns_class_tostr(qclass));
108 if (status != ARES_SUCCESS) {
109 goto fail;
110 }
111
112 status = ares__buf_append_byte(buf, '|');
113 if (status != ARES_SUCCESS) {
114 goto fail;
115 }
116
117 status = ares__buf_append_str(buf, name);
118 if (status != ARES_SUCCESS) {
119 goto fail;
120 }
121 }
122
123 return ares__buf_finish_str(buf, NULL);
124
125 fail:
126 ares__buf_destroy(buf);
127 return NULL;
128 }
129
ares__qcache_expire(ares__qcache_t * cache,const struct timeval * now)130 static void ares__qcache_expire(ares__qcache_t *cache,
131 const struct timeval *now)
132 {
133 ares__slist_node_t *node;
134
135 if (cache == NULL) {
136 return;
137 }
138
139 while ((node = ares__slist_node_first(cache->expire)) != NULL) {
140 const ares__qcache_entry_t *entry = ares__slist_node_val(node);
141 if (entry->expire_ts > now->tv_sec) {
142 break;
143 }
144
145 ares__htable_strvp_remove(cache->cache, entry->key);
146 ares__slist_node_destroy(node);
147 }
148 }
149
ares__qcache_flush(ares__qcache_t * cache)150 void ares__qcache_flush(ares__qcache_t *cache)
151 {
152 struct timeval now;
153 memset(&now, 0, sizeof(now));
154 ares__qcache_expire(cache, &now);
155 }
156
ares__qcache_destroy(ares__qcache_t * cache)157 void ares__qcache_destroy(ares__qcache_t *cache)
158 {
159 if (cache == NULL) {
160 return;
161 }
162
163 ares__htable_strvp_destroy(cache->cache);
164 ares__slist_destroy(cache->expire);
165 ares_free(cache);
166 }
167
ares__qcache_entry_sort_cb(const void * arg1,const void * arg2)168 static int ares__qcache_entry_sort_cb(const void *arg1, const void *arg2)
169 {
170 const ares__qcache_entry_t *entry1 = arg1;
171 const ares__qcache_entry_t *entry2 = arg2;
172
173 if (entry1->expire_ts > entry2->expire_ts) {
174 return 1;
175 }
176
177 if (entry1->expire_ts < entry2->expire_ts) {
178 return -1;
179 }
180
181 return 0;
182 }
183
ares__qcache_entry_destroy_cb(void * arg)184 static void ares__qcache_entry_destroy_cb(void *arg)
185 {
186 ares__qcache_entry_t *entry = arg;
187 if (entry == NULL) {
188 return;
189 }
190
191 ares_free(entry->key);
192 ares_dns_record_destroy(entry->dnsrec);
193 ares_free(entry);
194 }
195
ares__qcache_create(ares_rand_state * rand_state,unsigned int max_ttl,ares__qcache_t ** cache_out)196 ares_status_t ares__qcache_create(ares_rand_state *rand_state,
197 unsigned int max_ttl,
198 ares__qcache_t **cache_out)
199 {
200 ares_status_t status = ARES_SUCCESS;
201 ares__qcache_t *cache;
202
203 cache = ares_malloc_zero(sizeof(*cache));
204 if (cache == NULL) {
205 status = ARES_ENOMEM;
206 goto done;
207 }
208
209 cache->cache = ares__htable_strvp_create(NULL);
210 if (cache->cache == NULL) {
211 status = ARES_ENOMEM;
212 goto done;
213 }
214
215 cache->expire = ares__slist_create(rand_state, ares__qcache_entry_sort_cb,
216 ares__qcache_entry_destroy_cb);
217 if (cache->expire == NULL) {
218 status = ARES_ENOMEM;
219 goto done;
220 }
221
222 cache->max_ttl = max_ttl;
223
224 done:
225 if (status != ARES_SUCCESS) {
226 *cache_out = NULL;
227 ares__qcache_destroy(cache);
228 return status;
229 }
230
231 *cache_out = cache;
232 return status;
233 }
234
ares__qcache_calc_minttl(ares_dns_record_t * dnsrec)235 static unsigned int ares__qcache_calc_minttl(ares_dns_record_t *dnsrec)
236 {
237 unsigned int minttl = 0xFFFFFFFF;
238 size_t sect;
239
240 for (sect = ARES_SECTION_ANSWER; sect <= ARES_SECTION_ADDITIONAL; sect++) {
241 size_t i;
242 for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, (ares_dns_section_t)sect);
243 i++) {
244 const ares_dns_rr_t *rr =
245 ares_dns_record_rr_get(dnsrec, (ares_dns_section_t)sect, i);
246 ares_dns_rec_type_t type = ares_dns_rr_get_type(rr);
247 unsigned int ttl = ares_dns_rr_get_ttl(rr);
248 if (type == ARES_REC_TYPE_OPT || type == ARES_REC_TYPE_SOA) {
249 continue;
250 }
251
252 if (ttl < minttl) {
253 minttl = ttl;
254 }
255 }
256 }
257
258 return minttl;
259 }
260
ares__qcache_soa_minimum(ares_dns_record_t * dnsrec)261 static unsigned int ares__qcache_soa_minimum(ares_dns_record_t *dnsrec)
262 {
263 size_t i;
264
265 /* RFC 2308 Section 5 says its the minimum of MINIMUM and the TTL of the
266 * record. */
267 for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY); i++) {
268 const ares_dns_rr_t *rr =
269 ares_dns_record_rr_get(dnsrec, ARES_SECTION_AUTHORITY, i);
270 ares_dns_rec_type_t type = ares_dns_rr_get_type(rr);
271 unsigned int ttl;
272 unsigned int minimum;
273
274 if (type != ARES_REC_TYPE_SOA) {
275 continue;
276 }
277
278 minimum = ares_dns_rr_get_u32(rr, ARES_RR_SOA_MINIMUM);
279 ttl = ares_dns_rr_get_ttl(rr);
280
281 if (ttl > minimum) {
282 return minimum;
283 }
284 return ttl;
285 }
286
287 return 0;
288 }
289
ares__qcache_calc_key_frombuf(const unsigned char * qbuf,size_t qlen)290 static char *ares__qcache_calc_key_frombuf(const unsigned char *qbuf,
291 size_t qlen)
292 {
293 ares_status_t status;
294 ares_dns_record_t *dnsrec = NULL;
295 char *key = NULL;
296
297 status = ares_dns_parse(qbuf, qlen, 0, &dnsrec);
298 if (status != ARES_SUCCESS) {
299 goto done;
300 }
301
302 key = ares__qcache_calc_key(dnsrec);
303
304 done:
305 ares_dns_record_destroy(dnsrec);
306 return key;
307 }
308
309 /* On success, takes ownership of dnsrec */
ares__qcache_insert(ares__qcache_t * qcache,ares_dns_record_t * dnsrec,const unsigned char * qbuf,size_t qlen,const struct timeval * now)310 static ares_status_t ares__qcache_insert(ares__qcache_t *qcache,
311 ares_dns_record_t *dnsrec,
312 const unsigned char *qbuf, size_t qlen,
313 const struct timeval *now)
314 {
315 ares__qcache_entry_t *entry;
316 unsigned int ttl;
317 ares_dns_rcode_t rcode = ares_dns_record_get_rcode(dnsrec);
318 ares_dns_flags_t flags = ares_dns_record_get_flags(dnsrec);
319
320 if (qcache == NULL || dnsrec == NULL) {
321 return ARES_EFORMERR;
322 }
323
324 /* Only save NOERROR or NXDOMAIN */
325 if (rcode != ARES_RCODE_NOERROR && rcode != ARES_RCODE_NXDOMAIN) {
326 return ARES_ENOTIMP;
327 }
328
329 /* Don't save truncated queries */
330 if (flags & ARES_FLAG_TC) {
331 return ARES_ENOTIMP;
332 }
333
334 /* Look at SOA for NXDOMAIN for minimum */
335 if (rcode == ARES_RCODE_NXDOMAIN) {
336 ttl = ares__qcache_soa_minimum(dnsrec);
337 } else {
338 ttl = ares__qcache_calc_minttl(dnsrec);
339 }
340
341 /* Don't cache something that is already expired */
342 if (ttl == 0) {
343 return ARES_EREFUSED;
344 }
345
346 if (ttl > qcache->max_ttl) {
347 ttl = qcache->max_ttl;
348 }
349
350 entry = ares_malloc_zero(sizeof(*entry));
351 if (entry == NULL) {
352 goto fail;
353 }
354
355 entry->dnsrec = dnsrec;
356 entry->expire_ts = now->tv_sec + (time_t)ttl;
357 entry->insert_ts = now->tv_sec;
358
359 /* We can't guarantee the server responded with the same flags as the
360 * request had, so we have to re-parse the request in order to generate the
361 * key for caching, but we'll only do this once we know for sure we really
362 * want to cache it */
363 entry->key = ares__qcache_calc_key_frombuf(qbuf, qlen);
364 if (entry->key == NULL) {
365 goto fail;
366 }
367
368 if (!ares__htable_strvp_insert(qcache->cache, entry->key, entry)) {
369 goto fail;
370 }
371
372 if (ares__slist_insert(qcache->expire, entry) == NULL) {
373 goto fail;
374 }
375
376 return ARES_SUCCESS;
377
378 fail:
379 if (entry != NULL && entry->key != NULL) {
380 ares__htable_strvp_remove(qcache->cache, entry->key);
381 ares_free(entry->key);
382 ares_free(entry);
383 }
384 return ARES_ENOMEM;
385 }
386
ares__qcache_fetch(ares__qcache_t * qcache,const ares_dns_record_t * dnsrec,const struct timeval * now,unsigned char ** buf,size_t * buf_len)387 static ares_status_t ares__qcache_fetch(ares__qcache_t *qcache,
388 const ares_dns_record_t *dnsrec,
389 const struct timeval *now,
390 unsigned char **buf, size_t *buf_len)
391 {
392 char *key = NULL;
393 ares__qcache_entry_t *entry;
394 ares_status_t status;
395
396 if (qcache == NULL || dnsrec == NULL) {
397 return ARES_EFORMERR;
398 }
399
400 ares__qcache_expire(qcache, now);
401
402 key = ares__qcache_calc_key(dnsrec);
403 if (key == NULL) {
404 status = ARES_ENOMEM;
405 goto done;
406 }
407
408 entry = ares__htable_strvp_get_direct(qcache->cache, key);
409 if (entry == NULL) {
410 status = ARES_ENOTFOUND;
411 goto done;
412 }
413
414 ares_dns_record_write_ttl_decrement(
415 entry->dnsrec, (unsigned int)(now->tv_sec - entry->insert_ts));
416
417 status = ares_dns_write(entry->dnsrec, buf, buf_len);
418
419 done:
420 ares_free(key);
421 return status;
422 }
423
ares_qcache_insert(ares_channel_t * channel,const struct timeval * now,const struct query * query,ares_dns_record_t * dnsrec)424 ares_status_t ares_qcache_insert(ares_channel_t *channel,
425 const struct timeval *now,
426 const struct query *query,
427 ares_dns_record_t *dnsrec)
428 {
429 return ares__qcache_insert(channel->qcache, dnsrec, query->qbuf, query->qlen,
430 now);
431 }
432
ares_qcache_fetch(ares_channel_t * channel,const struct timeval * now,const unsigned char * qbuf,size_t qlen,unsigned char ** abuf,size_t * alen)433 ares_status_t ares_qcache_fetch(ares_channel_t *channel,
434 const struct timeval *now,
435 const unsigned char *qbuf, size_t qlen,
436 unsigned char **abuf, size_t *alen)
437 {
438 ares_status_t status;
439 ares_dns_record_t *dnsrec = NULL;
440
441 if (channel->qcache == NULL) {
442 return ARES_ENOTFOUND;
443 }
444
445 status = ares_dns_parse(qbuf, qlen, 0, &dnsrec);
446 if (status != ARES_SUCCESS) {
447 goto done;
448 }
449
450 status = ares__qcache_fetch(channel->qcache, dnsrec, now, abuf, alen);
451
452 done:
453 ares_dns_record_destroy(dnsrec);
454 return status;
455 }
456