blob: 2ae46ed0b180e96f8398d5c382a8dfe0f93c651c [file] [log] [blame] [edit]
/* MIT License
*
* Copyright (c) 1998 Massachusetts Institute of Technology
* Copyright (c) The c-ares project and its contributors
*
* 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_private.h"
#ifdef HAVE_NETINET_IN_H
# include <netinet/in.h>
#endif
#include "ares_nameser.h"
static unsigned short generate_unique_qid(ares_channel_t *channel)
{
unsigned short id;
do {
id = ares_generate_new_id(channel->rand_state);
} while (ares_htable_szvp_get(channel->queries_by_qid, id, NULL));
return id;
}
/* https://datatracker.ietf.org/doc/html/draft-vixie-dnsext-dns0x20-00 */
static ares_status_t ares_apply_dns0x20(ares_channel_t *channel,
ares_dns_record_t *dnsrec)
{
ares_status_t status = ARES_SUCCESS;
const char *name = NULL;
char dns0x20name[256];
unsigned char randdata[256 / 8];
size_t len;
size_t remaining_bits;
size_t total_bits;
size_t i;
status = ares_dns_record_query_get(dnsrec, 0, &name, NULL, NULL);
if (status != ARES_SUCCESS) {
goto done;
}
len = ares_strlen(name);
if (len == 0) {
return ARES_SUCCESS;
}
if (len >= sizeof(dns0x20name)) {
status = ARES_EBADNAME;
goto done;
}
memset(dns0x20name, 0, sizeof(dns0x20name));
/* Fetch the minimum amount of random data we'd need for the string, which
* is 1 bit per byte */
total_bits = ((len + 7) / 8) * 8;
remaining_bits = total_bits;
ares_rand_bytes(channel->rand_state, randdata, total_bits / 8);
/* Randomly apply 0x20 to name */
for (i = 0; i < len; i++) {
size_t bit;
/* Only apply 0x20 to alpha characters */
if (!ares_isalpha(name[i])) {
dns0x20name[i] = name[i];
continue;
}
/* coin flip */
bit = total_bits - remaining_bits;
if (randdata[bit / 8] & (1 << (bit % 8))) {
dns0x20name[i] = name[i] | 0x20; /* Set 0x20 */
} else {
dns0x20name[i] = (char)(((unsigned char)name[i]) & 0xDF); /* Unset 0x20 */
}
remaining_bits--;
}
status = ares_dns_record_query_set_name(dnsrec, 0, dns0x20name);
done:
return status;
}
ares_status_t ares_send_nolock(ares_channel_t *channel,
ares_server_t *server,
ares_send_flags_t flags,
const ares_dns_record_t *dnsrec,
ares_callback_dnsrec callback, void *arg,
unsigned short *qid)
{
ares_query_t *query;
ares_timeval_t now;
ares_status_t status;
unsigned short id = generate_unique_qid(channel);
const ares_dns_record_t *dnsrec_resp = NULL;
ares_tvnow(&now);
if (ares_slist_len(channel->servers) == 0) {
callback(arg, ARES_ENOSERVER, 0, NULL);
return ARES_ENOSERVER;
}
if (!(flags & ARES_SEND_FLAG_NOCACHE)) {
/* Check query cache */
status = ares_qcache_fetch(channel, &now, dnsrec, &dnsrec_resp);
if (status != ARES_ENOTFOUND) {
/* ARES_SUCCESS means we retrieved the cache, anything else is a critical
* failure, all result in termination */
callback(arg, status, 0, dnsrec_resp);
return status;
}
}
/* Allocate space for query and allocated fields. */
query = ares_malloc(sizeof(ares_query_t));
if (!query) {
callback(arg, ARES_ENOMEM, 0, NULL); /* LCOV_EXCL_LINE: OutOfMemory */
return ARES_ENOMEM; /* LCOV_EXCL_LINE: OutOfMemory */
}
memset(query, 0, sizeof(*query));
query->channel = channel;
query->qid = id;
query->timeout.sec = 0;
query->timeout.usec = 0;
query->using_tcp =
(channel->flags & ARES_FLAG_USEVC) ? ARES_TRUE : ARES_FALSE;
/* Duplicate Query */
status = ares_dns_record_duplicate_ex(&query->query, dnsrec);
if (status != ARES_SUCCESS) {
ares_free(query);
callback(arg, status, 0, NULL);
return status;
}
ares_dns_record_set_id(query->query, id);
if (channel->flags & ARES_FLAG_DNS0x20 && !query->using_tcp) {
status = ares_apply_dns0x20(channel, query->query);
if (status != ARES_SUCCESS) {
/* LCOV_EXCL_START: OutOfMemory */
callback(arg, status, 0, NULL);
ares_free_query(query);
return status;
/* LCOV_EXCL_STOP */
}
}
/* Fill in query arguments. */
query->callback = callback;
query->arg = arg;
/* Initialize query status. */
query->try_count = 0;
if (flags & ARES_SEND_FLAG_NORETRY) {
query->no_retries = ARES_TRUE;
}
query->error_status = ARES_SUCCESS;
query->timeouts = 0;
/* Initialize our list nodes. */
query->node_queries_by_timeout = NULL;
query->node_queries_to_conn = NULL;
/* Chain the query into the list of all queries. */
query->node_all_queries = ares_llist_insert_last(channel->all_queries, query);
if (query->node_all_queries == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
callback(arg, ARES_ENOMEM, 0, NULL);
ares_free_query(query);
return ARES_ENOMEM;
/* LCOV_EXCL_STOP */
}
/* Keep track of queries bucketed by qid, so we can process DNS
* responses quickly.
*/
if (!ares_htable_szvp_insert(channel->queries_by_qid, query->qid, query)) {
/* LCOV_EXCL_START: OutOfMemory */
callback(arg, ARES_ENOMEM, 0, NULL);
ares_free_query(query);
return ARES_ENOMEM;
/* LCOV_EXCL_STOP */
}
/* Perform the first query action. */
status = ares_send_query(server, query, &now);
if (status == ARES_SUCCESS && qid) {
*qid = id;
}
return status;
}
ares_status_t ares_send_dnsrec(ares_channel_t *channel,
const ares_dns_record_t *dnsrec,
ares_callback_dnsrec callback, void *arg,
unsigned short *qid)
{
ares_status_t status;
if (channel == NULL) {
return ARES_EFORMERR; /* LCOV_EXCL_LINE: DefensiveCoding */
}
ares_channel_lock(channel);
status = ares_send_nolock(channel, NULL, 0, dnsrec, callback, arg, qid);
ares_channel_unlock(channel);
return status;
}
void ares_send(ares_channel_t *channel, const unsigned char *qbuf, int qlen,
ares_callback callback, void *arg)
{
ares_dns_record_t *dnsrec = NULL;
ares_status_t status;
void *carg = NULL;
if (channel == NULL) {
return;
}
/* Verify that the query is at least long enough to hold the header. */
if (qlen < HFIXEDSZ || qlen >= (1 << 16)) {
callback(arg, ARES_EBADQUERY, 0, NULL, 0);
return;
}
status = ares_dns_parse(qbuf, (size_t)qlen, 0, &dnsrec);
if (status != ARES_SUCCESS) {
callback(arg, (int)status, 0, NULL, 0);
return;
}
carg = ares_dnsrec_convert_arg(callback, arg);
if (carg == NULL) {
/* LCOV_EXCL_START: OutOfMemory */
status = ARES_ENOMEM;
ares_dns_record_destroy(dnsrec);
callback(arg, (int)status, 0, NULL, 0);
return;
/* LCOV_EXCL_STOP */
}
ares_send_dnsrec(channel, dnsrec, ares_dnsrec_convert_cb, carg, NULL);
ares_dns_record_destroy(dnsrec);
}
size_t ares_queue_active_queries(const ares_channel_t *channel)
{
size_t len;
if (channel == NULL) {
return 0;
}
ares_channel_lock(channel);
len = ares_llist_len(channel->all_queries);
ares_channel_unlock(channel);
return len;
}