• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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