aboutsummaryrefslogtreecommitdiffstats
path: root/contrib/libs/c-ares/src/lib/ares_qcache.c
diff options
context:
space:
mode:
authorthegeorg <thegeorg@yandex-team.com>2024-07-30 09:57:24 +0300
committerthegeorg <thegeorg@yandex-team.com>2024-07-30 10:08:52 +0300
commit5b405616d0467647cb365e71237976a9f3810b42 (patch)
treec35e742877f95ba2b9357f178197c755f121cb1a /contrib/libs/c-ares/src/lib/ares_qcache.c
parent997c68115bc1c9dd2fce5a6a6f8eae92ad628df7 (diff)
downloadydb-5b405616d0467647cb365e71237976a9f3810b42.tar.gz
Update contrib/libs/c-ares to 1.28.1
db71d0a3bd9ec3cf1e1ccb5a9cfac8abfd43fdb7
Diffstat (limited to 'contrib/libs/c-ares/src/lib/ares_qcache.c')
-rw-r--r--contrib/libs/c-ares/src/lib/ares_qcache.c444
1 files changed, 444 insertions, 0 deletions
diff --git a/contrib/libs/c-ares/src/lib/ares_qcache.c b/contrib/libs/c-ares/src/lib/ares_qcache.c
new file mode 100644
index 0000000000..2af1125a0d
--- /dev/null
+++ b/contrib/libs/c-ares/src/lib/ares_qcache.c
@@ -0,0 +1,444 @@
+/* MIT License
+ *
+ * Copyright (c) 2023 Brad House
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+#include "ares_setup.h"
+#include "ares.h"
+#include "ares_private.h"
+
+struct ares__qcache {
+ ares__htable_strvp_t *cache;
+ ares__slist_t *expire;
+ unsigned int max_ttl;
+};
+
+typedef struct {
+ char *key;
+ ares_dns_record_t *dnsrec;
+ time_t expire_ts;
+ time_t insert_ts;
+} ares__qcache_entry_t;
+
+static char *ares__qcache_calc_key(const ares_dns_record_t *dnsrec)
+{
+ ares__buf_t *buf = ares__buf_create();
+ size_t i;
+ ares_status_t status;
+ ares_dns_flags_t flags;
+
+ if (dnsrec == NULL || buf == NULL) {
+ return NULL;
+ }
+
+ /* Format is OPCODE|FLAGS[|QTYPE1|QCLASS1|QNAME1]... */
+
+ status = ares__buf_append_str(
+ buf, ares_dns_opcode_tostr(ares_dns_record_get_opcode(dnsrec)));
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ status = ares__buf_append_byte(buf, '|');
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ flags = ares_dns_record_get_flags(dnsrec);
+ /* Only care about RD and CD */
+ if (flags & ARES_FLAG_RD) {
+ status = ares__buf_append_str(buf, "rd");
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+ }
+ if (flags & ARES_FLAG_CD) {
+ status = ares__buf_append_str(buf, "cd");
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+ }
+
+ for (i = 0; i < ares_dns_record_query_cnt(dnsrec); i++) {
+ const char *name;
+ size_t name_len;
+ ares_dns_rec_type_t qtype;
+ ares_dns_class_t qclass;
+
+ status = ares_dns_record_query_get(dnsrec, i, &name, &qtype, &qclass);
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ status = ares__buf_append_byte(buf, '|');
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ status = ares__buf_append_str(buf, ares_dns_rec_type_tostr(qtype));
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ status = ares__buf_append_byte(buf, '|');
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ status = ares__buf_append_str(buf, ares_dns_class_tostr(qclass));
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ status = ares__buf_append_byte(buf, '|');
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+
+ /* On queries, a '.' may be appended to the name to indicate an explicit
+ * name lookup without performing a search. Strip this since its not part
+ * of a cached response. */
+ name_len = ares_strlen(name);
+ if (name_len && name[name_len - 1] == '.') {
+ name_len--;
+ }
+
+ status = ares__buf_append(buf, (const unsigned char *)name, name_len);
+ if (status != ARES_SUCCESS) {
+ goto fail;
+ }
+ }
+
+ return ares__buf_finish_str(buf, NULL);
+
+fail:
+ ares__buf_destroy(buf);
+ return NULL;
+}
+
+static void ares__qcache_expire(ares__qcache_t *cache,
+ const struct timeval *now)
+{
+ ares__slist_node_t *node;
+
+ if (cache == NULL) {
+ return;
+ }
+
+ while ((node = ares__slist_node_first(cache->expire)) != NULL) {
+ const ares__qcache_entry_t *entry = ares__slist_node_val(node);
+ if (entry->expire_ts > now->tv_sec) {
+ break;
+ }
+
+ ares__htable_strvp_remove(cache->cache, entry->key);
+ ares__slist_node_destroy(node);
+ }
+}
+
+void ares__qcache_flush(ares__qcache_t *cache)
+{
+ struct timeval now;
+ memset(&now, 0, sizeof(now));
+ ares__qcache_expire(cache, &now);
+}
+
+void ares__qcache_destroy(ares__qcache_t *cache)
+{
+ if (cache == NULL) {
+ return;
+ }
+
+ ares__htable_strvp_destroy(cache->cache);
+ ares__slist_destroy(cache->expire);
+ ares_free(cache);
+}
+
+static int ares__qcache_entry_sort_cb(const void *arg1, const void *arg2)
+{
+ const ares__qcache_entry_t *entry1 = arg1;
+ const ares__qcache_entry_t *entry2 = arg2;
+
+ if (entry1->expire_ts > entry2->expire_ts) {
+ return 1;
+ }
+
+ if (entry1->expire_ts < entry2->expire_ts) {
+ return -1;
+ }
+
+ return 0;
+}
+
+static void ares__qcache_entry_destroy_cb(void *arg)
+{
+ ares__qcache_entry_t *entry = arg;
+ if (entry == NULL) {
+ return;
+ }
+
+ ares_free(entry->key);
+ ares_dns_record_destroy(entry->dnsrec);
+ ares_free(entry);
+}
+
+ares_status_t ares__qcache_create(ares_rand_state *rand_state,
+ unsigned int max_ttl,
+ ares__qcache_t **cache_out)
+{
+ ares_status_t status = ARES_SUCCESS;
+ ares__qcache_t *cache;
+
+ cache = ares_malloc_zero(sizeof(*cache));
+ if (cache == NULL) {
+ status = ARES_ENOMEM;
+ goto done;
+ }
+
+ cache->cache = ares__htable_strvp_create(NULL);
+ if (cache->cache == NULL) {
+ status = ARES_ENOMEM;
+ goto done;
+ }
+
+ cache->expire = ares__slist_create(rand_state, ares__qcache_entry_sort_cb,
+ ares__qcache_entry_destroy_cb);
+ if (cache->expire == NULL) {
+ status = ARES_ENOMEM;
+ goto done;
+ }
+
+ cache->max_ttl = max_ttl;
+
+done:
+ if (status != ARES_SUCCESS) {
+ *cache_out = NULL;
+ ares__qcache_destroy(cache);
+ return status;
+ }
+
+ *cache_out = cache;
+ return status;
+}
+
+static unsigned int ares__qcache_calc_minttl(ares_dns_record_t *dnsrec)
+{
+ unsigned int minttl = 0xFFFFFFFF;
+ size_t sect;
+
+ for (sect = ARES_SECTION_ANSWER; sect <= ARES_SECTION_ADDITIONAL; sect++) {
+ size_t i;
+ for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, (ares_dns_section_t)sect);
+ i++) {
+ const ares_dns_rr_t *rr =
+ ares_dns_record_rr_get(dnsrec, (ares_dns_section_t)sect, i);
+ ares_dns_rec_type_t type = ares_dns_rr_get_type(rr);
+ unsigned int ttl = ares_dns_rr_get_ttl(rr);
+ if (type == ARES_REC_TYPE_OPT || type == ARES_REC_TYPE_SOA) {
+ continue;
+ }
+
+ if (ttl < minttl) {
+ minttl = ttl;
+ }
+ }
+ }
+
+ return minttl;
+}
+
+static unsigned int ares__qcache_soa_minimum(ares_dns_record_t *dnsrec)
+{
+ size_t i;
+
+ /* RFC 2308 Section 5 says its the minimum of MINIMUM and the TTL of the
+ * record. */
+ for (i = 0; i < ares_dns_record_rr_cnt(dnsrec, ARES_SECTION_AUTHORITY); i++) {
+ const ares_dns_rr_t *rr =
+ ares_dns_record_rr_get(dnsrec, ARES_SECTION_AUTHORITY, i);
+ ares_dns_rec_type_t type = ares_dns_rr_get_type(rr);
+ unsigned int ttl;
+ unsigned int minimum;
+
+ if (type != ARES_REC_TYPE_SOA) {
+ continue;
+ }
+
+ minimum = ares_dns_rr_get_u32(rr, ARES_RR_SOA_MINIMUM);
+ ttl = ares_dns_rr_get_ttl(rr);
+
+ if (ttl > minimum) {
+ return minimum;
+ }
+ return ttl;
+ }
+
+ return 0;
+}
+
+static char *ares__qcache_calc_key_frombuf(const unsigned char *qbuf,
+ size_t qlen)
+{
+ ares_status_t status;
+ ares_dns_record_t *dnsrec = NULL;
+ char *key = NULL;
+
+ status = ares_dns_parse(qbuf, qlen, 0, &dnsrec);
+ if (status != ARES_SUCCESS) {
+ goto done;
+ }
+
+ key = ares__qcache_calc_key(dnsrec);
+
+done:
+ ares_dns_record_destroy(dnsrec);
+ return key;
+}
+
+/* On success, takes ownership of dnsrec */
+static ares_status_t ares__qcache_insert(ares__qcache_t *qcache,
+ ares_dns_record_t *dnsrec,
+ const unsigned char *qbuf, size_t qlen,
+ const struct timeval *now)
+{
+ ares__qcache_entry_t *entry;
+ unsigned int ttl;
+ ares_dns_rcode_t rcode = ares_dns_record_get_rcode(dnsrec);
+ ares_dns_flags_t flags = ares_dns_record_get_flags(dnsrec);
+
+ if (qcache == NULL || dnsrec == NULL) {
+ return ARES_EFORMERR;
+ }
+
+ /* Only save NOERROR or NXDOMAIN */
+ if (rcode != ARES_RCODE_NOERROR && rcode != ARES_RCODE_NXDOMAIN) {
+ return ARES_ENOTIMP;
+ }
+
+ /* Don't save truncated queries */
+ if (flags & ARES_FLAG_TC) {
+ return ARES_ENOTIMP;
+ }
+
+ /* Look at SOA for NXDOMAIN for minimum */
+ if (rcode == ARES_RCODE_NXDOMAIN) {
+ ttl = ares__qcache_soa_minimum(dnsrec);
+ } else {
+ ttl = ares__qcache_calc_minttl(dnsrec);
+ }
+
+ /* Don't cache something that is already expired */
+ if (ttl == 0) {
+ return ARES_EREFUSED;
+ }
+
+ if (ttl > qcache->max_ttl) {
+ ttl = qcache->max_ttl;
+ }
+
+ entry = ares_malloc_zero(sizeof(*entry));
+ if (entry == NULL) {
+ goto fail;
+ }
+
+ entry->dnsrec = dnsrec;
+ entry->expire_ts = now->tv_sec + (time_t)ttl;
+ entry->insert_ts = now->tv_sec;
+
+ /* We can't guarantee the server responded with the same flags as the
+ * request had, so we have to re-parse the request in order to generate the
+ * key for caching, but we'll only do this once we know for sure we really
+ * want to cache it */
+ entry->key = ares__qcache_calc_key_frombuf(qbuf, qlen);
+ if (entry->key == NULL) {
+ goto fail;
+ }
+
+ if (!ares__htable_strvp_insert(qcache->cache, entry->key, entry)) {
+ goto fail;
+ }
+
+ if (ares__slist_insert(qcache->expire, entry) == NULL) {
+ goto fail;
+ }
+
+ return ARES_SUCCESS;
+
+fail:
+ if (entry != NULL && entry->key != NULL) {
+ ares__htable_strvp_remove(qcache->cache, entry->key);
+ ares_free(entry->key);
+ ares_free(entry);
+ }
+ return ARES_ENOMEM;
+}
+
+ares_status_t ares_qcache_fetch(ares_channel_t *channel,
+ const struct timeval *now,
+ const ares_dns_record_t *dnsrec,
+ const ares_dns_record_t **dnsrec_resp)
+{
+ char *key = NULL;
+ ares__qcache_entry_t *entry;
+ ares_status_t status = ARES_SUCCESS;
+
+ if (channel == NULL || dnsrec == NULL || dnsrec_resp == NULL) {
+ return ARES_EFORMERR;
+ }
+
+ if (channel->qcache == NULL) {
+ return ARES_ENOTFOUND;
+ }
+
+ ares__qcache_expire(channel->qcache, now);
+
+ key = ares__qcache_calc_key(dnsrec);
+ if (key == NULL) {
+ status = ARES_ENOMEM;
+ goto done;
+ }
+
+ entry = ares__htable_strvp_get_direct(channel->qcache->cache, key);
+ if (entry == NULL) {
+ status = ARES_ENOTFOUND;
+ goto done;
+ }
+
+ ares_dns_record_write_ttl_decrement(
+ entry->dnsrec, (unsigned int)(now->tv_sec - entry->insert_ts));
+
+ *dnsrec_resp = entry->dnsrec;
+
+done:
+ ares_free(key);
+ return status;
+}
+
+ares_status_t ares_qcache_insert(ares_channel_t *channel,
+ const struct timeval *now,
+ const struct query *query,
+ ares_dns_record_t *dnsrec)
+{
+ return ares__qcache_insert(channel->qcache, dnsrec, query->qbuf, query->qlen,
+ now);
+}