• 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_private.h"
27 
28 struct ares_qcache {
29   ares_htable_strvp_t *cache;
30   ares_slist_t        *expire;
31   unsigned int         max_ttl;
32 };
33 
34 typedef struct {
35   char              *key;
36   ares_dns_record_t *dnsrec;
37   time_t             expire_ts;
38   time_t             insert_ts;
39 } ares_qcache_entry_t;
40 
ares_qcache_calc_key(const ares_dns_record_t * dnsrec)41 static char *ares_qcache_calc_key(const ares_dns_record_t *dnsrec)
42 {
43   ares_buf_t      *buf = ares_buf_create();
44   size_t           i;
45   ares_status_t    status;
46   ares_dns_flags_t flags;
47 
48   if (dnsrec == NULL || buf == NULL) {
49     return NULL; /* LCOV_EXCL_LINE: DefensiveCoding */
50   }
51 
52   /* Format is OPCODE|FLAGS[|QTYPE1|QCLASS1|QNAME1]... */
53 
54   status = ares_buf_append_str(
55     buf, ares_dns_opcode_tostr(ares_dns_record_get_opcode(dnsrec)));
56   if (status != ARES_SUCCESS) {
57     goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
58   }
59 
60   status = ares_buf_append_byte(buf, '|');
61   if (status != ARES_SUCCESS) {
62     goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
63   }
64 
65   flags = ares_dns_record_get_flags(dnsrec);
66   /* Only care about RD and CD */
67   if (flags & ARES_FLAG_RD) {
68     status = ares_buf_append_str(buf, "rd");
69     if (status != ARES_SUCCESS) {
70       goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
71     }
72   }
73   if (flags & ARES_FLAG_CD) {
74     status = ares_buf_append_str(buf, "cd");
75     if (status != ARES_SUCCESS) {
76       goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
77     }
78   }
79 
80   for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) {
81     const char         *name;
82     size_t              name_len;
83     ares_dns_rec_type_t qtype;
84     ares_dns_class_t    qclass;
85 
86     status = ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass);
87     if (status != ARES_SUCCESS) {
88       goto fail; /* LCOV_EXCL_LINE: DefensiveCoding */
89     }
90 
91     status = ares_buf_append_byte(buf, '|');
92     if (status != ARES_SUCCESS) {
93       goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
94     }
95 
96     status = ares_buf_append_str(buf, ares_dns_rec_type_tostr(qtype));
97     if (status != ARES_SUCCESS) {
98       goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
99     }
100 
101     status = ares_buf_append_byte(buf, '|');
102     if (status != ARES_SUCCESS) {
103       goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
104     }
105 
106     status = ares_buf_append_str(buf, ares_dns_class_tostr(qclass));
107     if (status != ARES_SUCCESS) {
108       goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
109     }
110 
111     status = ares_buf_append_byte(buf, '|');
112     if (status != ARES_SUCCESS) {
113       goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
114     }
115 
116     /* On queries, a '.' may be appended to the name to indicate an explicit
117      * name lookup without performing a search.  Strip this since its not part
118      * of a cached response. */
119     name_len = ares_strlen(name);
120     if (name_len && name[name_len - 1] == '.') {
121       name_len--;
122     }
123 
124     if (name_len > 0) {
125       status = ares_buf_append(buf, (const unsigned char *)name, name_len);
126       if (status != ARES_SUCCESS) {
127         goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
128       }
129     }
130   }
131 
132   return ares_buf_finish_str(buf, NULL);
133 
134 /* LCOV_EXCL_START: OutOfMemory */
135 fail:
136   ares_buf_destroy(buf);
137   return NULL;
138   /* LCOV_EXCL_STOP */
139 }
140 
ares_qcache_expire(ares_qcache_t * cache,const ares_timeval_t * now)141 static void ares_qcache_expire(ares_qcache_t *cache, const ares_timeval_t *now)
142 {
143   ares_slist_node_t *node;
144 
145   if (cache == NULL) {
146     return;
147   }
148 
149   while ((node = ares_slist_node_first(cache->expire)) != NULL) {
150     const ares_qcache_entry_t *entry = ares_slist_node_val(node);
151 
152     /* If now is NULL, we're flushing everything, so don't break */
153     if (now != NULL && entry->expire_ts > now->sec) {
154       break;
155     }
156 
157     ares_htable_strvp_remove(cache->cache, entry->key);
158     ares_slist_node_destroy(node);
159   }
160 }
161 
ares_qcache_flush(ares_qcache_t * cache)162 void ares_qcache_flush(ares_qcache_t *cache)
163 {
164   ares_qcache_expire(cache, NULL /* flush all */);
165 }
166 
ares_qcache_destroy(ares_qcache_t * cache)167 void ares_qcache_destroy(ares_qcache_t *cache)
168 {
169   if (cache == NULL) {
170     return;
171   }
172 
173   ares_htable_strvp_destroy(cache->cache);
174   ares_slist_destroy(cache->expire);
175   ares_free(cache);
176 }
177 
ares_qcache_entry_sort_cb(const void * arg1,const void * arg2)178 static int ares_qcache_entry_sort_cb(const void *arg1, const void *arg2)
179 {
180   const ares_qcache_entry_t *entry1 = arg1;
181   const ares_qcache_entry_t *entry2 = arg2;
182 
183   if (entry1->expire_ts > entry2->expire_ts) {
184     return 1;
185   }
186 
187   if (entry1->expire_ts < entry2->expire_ts) {
188     return -1;
189   }
190 
191   return 0;
192 }
193 
ares_qcache_entry_destroy_cb(void * arg)194 static void ares_qcache_entry_destroy_cb(void *arg)
195 {
196   ares_qcache_entry_t *entry = arg;
197   if (entry == NULL) {
198     return; /* LCOV_EXCL_LINE: DefensiveCoding */
199   }
200 
201   ares_free(entry->key);
202   ares_dns_record_destroy(entry->dnsrec);
203   ares_free(entry);
204 }
205 
ares_qcache_create(ares_rand_state * rand_state,unsigned int max_ttl,ares_qcache_t ** cache_out)206 ares_status_t ares_qcache_create(ares_rand_state *rand_state,
207                                  unsigned int     max_ttl,
208                                  ares_qcache_t  **cache_out)
209 {
210   ares_status_t  status = ARES_SUCCESS;
211   ares_qcache_t *cache;
212 
213   cache = ares_malloc_zero(sizeof(*cache));
214   if (cache == NULL) {
215     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
216     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
217   }
218 
219   cache->cache = ares_htable_strvp_create(NULL);
220   if (cache->cache == NULL) {
221     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
222     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
223   }
224 
225   cache->expire = ares_slist_create(rand_state, ares_qcache_entry_sort_cb,
226                                     ares_qcache_entry_destroy_cb);
227   if (cache->expire == NULL) {
228     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
229     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
230   }
231 
232   cache->max_ttl = max_ttl;
233 
234 done:
235   if (status != ARES_SUCCESS) {
236     *cache_out = NULL;
237     ares_qcache_destroy(cache);
238     return status;
239   }
240 
241   *cache_out = cache;
242   return status;
243 }
244 
ares_qcache_calc_minttl(ares_dns_record_t * dnsrec)245 static unsigned int ares_qcache_calc_minttl(ares_dns_record_t *dnsrec)
246 {
247   unsigned int minttl = 0xFFFFFFFF;
248   size_t       sect;
249 
250   for (sect = ARES_SECTION_ANSWER; sect <= ARES_SECTION_ADDITIONAL; sect++) {
251     size_t i;
252     for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, (ares_dns_section_t)sect);
253          i++) {
254       const ares_dns_rr_t *rr =
255         ares_dns_record_rr_get(dnsrec, (ares_dns_section_t)sect, i);
256       ares_dns_rec_type_t type = ares_dns_rr_get_type(rr);
257       unsigned int        ttl  = ares_dns_rr_get_ttl(rr);
258 
259       /* TTL is meaningless on these record types */
260       if (type == ARES_REC_TYPE_OPT || type == ARES_REC_TYPE_SOA ||
261           type == ARES_REC_TYPE_SIG) {
262         continue;
263       }
264 
265       if (ttl < minttl) {
266         minttl = ttl;
267       }
268     }
269   }
270 
271   return minttl;
272 }
273 
ares_qcache_soa_minimum(ares_dns_record_t * dnsrec)274 static unsigned int ares_qcache_soa_minimum(ares_dns_record_t *dnsrec)
275 {
276   size_t i;
277 
278   /* RFC 2308 Section 5 says its the minimum of MINIMUM and the TTL of the
279    * record. */
280   for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY); i++) {
281     const ares_dns_rr_t *rr =
282       ares_dns_record_rr_get(dnsrec, ARES_SECTION_AUTHORITY, i);
283     ares_dns_rec_type_t type = ares_dns_rr_get_type(rr);
284     unsigned int        ttl;
285     unsigned int        minimum;
286 
287     if (type != ARES_REC_TYPE_SOA) {
288       continue;
289     }
290 
291     minimum = ares_dns_rr_get_u32(rr, ARES_RR_SOA_MINIMUM);
292     ttl     = ares_dns_rr_get_ttl(rr);
293 
294     if (ttl > minimum) {
295       return minimum;
296     }
297     return ttl;
298   }
299 
300   return 0;
301 }
302 
303 /* On success, takes ownership of dnsrec */
ares_qcache_insert_int(ares_qcache_t * qcache,ares_dns_record_t * qresp,const ares_dns_record_t * qreq,const ares_timeval_t * now)304 static ares_status_t ares_qcache_insert_int(ares_qcache_t           *qcache,
305                                             ares_dns_record_t       *qresp,
306                                             const ares_dns_record_t *qreq,
307                                             const ares_timeval_t    *now)
308 {
309   ares_qcache_entry_t *entry;
310   unsigned int         ttl;
311   ares_dns_rcode_t     rcode = ares_dns_record_get_rcode(qresp);
312   ares_dns_flags_t     flags = ares_dns_record_get_flags(qresp);
313 
314   if (qcache == NULL || qresp == NULL) {
315     return ARES_EFORMERR;
316   }
317 
318   /* Only save NOERROR or NXDOMAIN */
319   if (rcode != ARES_RCODE_NOERROR && rcode != ARES_RCODE_NXDOMAIN) {
320     return ARES_ENOTIMP;
321   }
322 
323   /* Don't save truncated queries */
324   if (flags & ARES_FLAG_TC) {
325     return ARES_ENOTIMP;
326   }
327 
328   /* Look at SOA for NXDOMAIN for minimum */
329   if (rcode == ARES_RCODE_NXDOMAIN) {
330     ttl = ares_qcache_soa_minimum(qresp);
331   } else {
332     ttl = ares_qcache_calc_minttl(qresp);
333   }
334 
335   if (ttl > qcache->max_ttl) {
336     ttl = qcache->max_ttl;
337   }
338 
339   /* Don't cache something that is already expired */
340   if (ttl == 0) {
341     return ARES_EREFUSED;
342   }
343 
344   entry = ares_malloc_zero(sizeof(*entry));
345   if (entry == NULL) {
346     goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
347   }
348 
349   entry->dnsrec    = qresp;
350   entry->expire_ts = (time_t)now->sec + (time_t)ttl;
351   entry->insert_ts = (time_t)now->sec;
352 
353   /* We can't guarantee the server responded with the same flags as the
354    * request had, so we have to re-parse the request in order to generate the
355    * key for caching, but we'll only do this once we know for sure we really
356    * want to cache it */
357   entry->key = ares_qcache_calc_key(qreq);
358   if (entry->key == NULL) {
359     goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
360   }
361 
362   if (!ares_htable_strvp_insert(qcache->cache, entry->key, entry)) {
363     goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
364   }
365 
366   if (ares_slist_insert(qcache->expire, entry) == NULL) {
367     goto fail; /* LCOV_EXCL_LINE: OutOfMemory */
368   }
369 
370   return ARES_SUCCESS;
371 
372 /* LCOV_EXCL_START: OutOfMemory */
373 fail:
374   if (entry != NULL && entry->key != NULL) {
375     ares_htable_strvp_remove(qcache->cache, entry->key);
376     ares_free(entry->key);
377     ares_free(entry);
378   }
379   return ARES_ENOMEM;
380   /* LCOV_EXCL_STOP */
381 }
382 
ares_qcache_fetch(ares_channel_t * channel,const ares_timeval_t * now,const ares_dns_record_t * dnsrec,const ares_dns_record_t ** dnsrec_resp)383 ares_status_t ares_qcache_fetch(ares_channel_t           *channel,
384                                 const ares_timeval_t     *now,
385                                 const ares_dns_record_t  *dnsrec,
386                                 const ares_dns_record_t **dnsrec_resp)
387 {
388   char                *key = NULL;
389   ares_qcache_entry_t *entry;
390   ares_status_t        status = ARES_SUCCESS;
391 
392   if (channel == NULL || dnsrec == NULL || dnsrec_resp == NULL) {
393     return ARES_EFORMERR;
394   }
395 
396   if (channel->qcache == NULL) {
397     return ARES_ENOTFOUND;
398   }
399 
400   ares_qcache_expire(channel->qcache, now);
401 
402   key = ares_qcache_calc_key(dnsrec);
403   if (key == NULL) {
404     status = ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
405     goto done;            /* LCOV_EXCL_LINE: OutOfMemory */
406   }
407 
408   entry = ares_htable_strvp_get_direct(channel->qcache->cache, key);
409   if (entry == NULL) {
410     status = ARES_ENOTFOUND;
411     goto done;
412   }
413 
414   ares_dns_record_ttl_decrement(entry->dnsrec,
415                                 (unsigned int)(now->sec - entry->insert_ts));
416 
417   *dnsrec_resp = entry->dnsrec;
418 
419 done:
420   ares_free(key);
421   return status;
422 }
423 
ares_qcache_insert(ares_channel_t * channel,const ares_timeval_t * now,const ares_query_t * query,ares_dns_record_t * dnsrec)424 ares_status_t ares_qcache_insert(ares_channel_t       *channel,
425                                  const ares_timeval_t *now,
426                                  const ares_query_t   *query,
427                                  ares_dns_record_t    *dnsrec)
428 {
429   return ares_qcache_insert_int(channel->qcache, dnsrec, query->query, now);
430 }
431