quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

ares_qcache.c (12143B)


      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 
     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 
    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 
    162 void ares_qcache_flush(ares_qcache_t *cache)
    163 {
    164   ares_qcache_expire(cache, NULL /* flush all */);
    165 }
    166 
    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 
    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 
    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 
    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 
    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 
    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 */
    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 
    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 
    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 }