#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;
}
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);
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;
}
name_len = ares_strlen(name);
if
(name_len && name[name_len - 1] ==
'.'
) {
name_len--;
}
if
(name_len > 0) {
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
ares_timeval_t *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
(now != NULL && entry->expire_ts > now->sec) {
break
;
}
ares_htable_strvp_remove(cache->cache, entry->key);
ares_slist_node_destroy(node);
}
}
void
ares_qcache_flush(ares_qcache_t *cache)
{
ares_qcache_expire(cache, NULL
);
}
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 ||
type == ARES_REC_TYPE_SIG) {
continue
;
}
if
(ttl < minttl) {
minttl = ttl;
}
}
}
return
minttl;
}
static
unsigned
int
ares_qcache_soa_minimum(ares_dns_record_t *dnsrec)
{
size_t
i;
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
ares_status_t ares_qcache_insert_int(ares_qcache_t *qcache,
ares_dns_record_t *qresp,
const
ares_dns_record_t *qreq,
const
ares_timeval_t *now)
{
ares_qcache_entry_t *entry;
unsigned
int
ttl;
ares_dns_rcode_t rcode = ares_dns_record_get_rcode(qresp);
ares_dns_flags_t flags = ares_dns_record_get_flags(qresp);
if
(qcache == NULL || qresp == NULL) {
return
ARES_EFORMERR;
}
if
(rcode != ARES_RCODE_NOERROR && rcode != ARES_RCODE_NXDOMAIN) {
return
ARES_ENOTIMP;
}
if
(flags & ARES_FLAG_TC) {
return
ARES_ENOTIMP;
}
if
(rcode == ARES_RCODE_NXDOMAIN) {
ttl = ares_qcache_soa_minimum(qresp);
}
else
{
ttl = ares_qcache_calc_minttl(qresp);
}
if
(ttl > qcache->max_ttl) {
ttl = qcache->max_ttl;
}
if
(ttl == 0) {
return
ARES_EREFUSED;
}
entry = ares_malloc_zero(
sizeof
(*entry));
if
(entry == NULL) {
goto
fail;
}
entry->dnsrec = qresp;
entry->expire_ts = (
time_t
)now->sec + (
time_t
)ttl;
entry->insert_ts = (
time_t
)now->sec;
entry->key = ares_qcache_calc_key(qreq);
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
ares_timeval_t *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_ttl_decrement(entry->dnsrec,
(unsigned
int
)(now->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
ares_timeval_t *now,
const
ares_query_t *query,
ares_dns_record_t *dnsrec)
{
return
ares_qcache_insert_int(channel->qcache, dnsrec, query->query, now);
}