You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
442 lines
13 KiB
442 lines
13 KiB
/*
|
|
* Copyright (c) 2014, Dustin Lundquist <dustin@null-ptr.net>
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright notice,
|
|
* this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <fcntl.h>
|
|
#include <ev.h>
|
|
#include <udns.h>
|
|
|
|
#ifdef __MINGW32__
|
|
#include "win32.h"
|
|
#else
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include "resolv.h"
|
|
#include "utils.h"
|
|
#include "netutils.h"
|
|
|
|
/*
|
|
* Implement DNS resolution interface using libudns
|
|
*/
|
|
|
|
struct ResolvQuery {
|
|
void (*client_cb)(struct sockaddr *, void *);
|
|
void (*client_free_cb)(void *);
|
|
void *client_cb_data;
|
|
struct dns_query *queries[2];
|
|
size_t response_count;
|
|
struct sockaddr **responses;
|
|
uint16_t port;
|
|
};
|
|
|
|
extern int verbose;
|
|
|
|
static struct ev_io resolv_io_watcher;
|
|
static struct ev_timer resolv_timeout_watcher;
|
|
static const int MODE_IPV4_ONLY = 0;
|
|
static const int MODE_IPV6_ONLY = 1;
|
|
static const int MODE_IPV4_FIRST = 2;
|
|
static const int MODE_IPV6_FIRST = 3;
|
|
static int resolv_mode = 0;
|
|
|
|
static void resolv_sock_cb(struct ev_loop *, struct ev_io *, int);
|
|
static void resolv_timeout_cb(struct ev_loop *, struct ev_timer *, int);
|
|
static void dns_query_v4_cb(struct dns_ctx *, struct dns_rr_a4 *, void *);
|
|
static void dns_query_v6_cb(struct dns_ctx *, struct dns_rr_a6 *, void *);
|
|
static void dns_timer_setup_cb(struct dns_ctx *, int, void *);
|
|
static void process_client_callback(struct ResolvQuery *);
|
|
static inline int all_queries_are_null(struct ResolvQuery *);
|
|
static struct sockaddr *choose_ipv4_first(struct ResolvQuery *);
|
|
static struct sockaddr *choose_ipv6_first(struct ResolvQuery *);
|
|
static struct sockaddr *choose_any(struct ResolvQuery *);
|
|
|
|
int
|
|
resolv_init(struct ev_loop *loop, char **nameservers, int nameserver_num, int ipv6first)
|
|
{
|
|
if (ipv6first)
|
|
resolv_mode = MODE_IPV6_FIRST;
|
|
else
|
|
resolv_mode = MODE_IPV4_FIRST;
|
|
|
|
struct dns_ctx *ctx = &dns_defctx;
|
|
if (nameservers == NULL) {
|
|
/* Nameservers not specified, use system resolver config */
|
|
dns_init(ctx, 0);
|
|
} else {
|
|
dns_reset(ctx);
|
|
|
|
for (int i = 0; i < nameserver_num; i++) {
|
|
char *server = nameservers[i];
|
|
dns_add_serv(ctx, server);
|
|
}
|
|
}
|
|
|
|
int sockfd = dns_open(ctx);
|
|
if (sockfd < 0) {
|
|
FATAL("Failed to open DNS resolver socket");
|
|
}
|
|
|
|
if (nameserver_num == 1 && nameservers != NULL) {
|
|
if (strncmp("127.0.0.1", nameservers[0], 9) == 0
|
|
|| strncmp("::1", nameservers[0], 3) == 0) {
|
|
if (verbose) {
|
|
LOGI("bind UDP resolver to %s", nameservers[0]);
|
|
}
|
|
if (bind_to_address(sockfd, nameservers[0]) == -1)
|
|
ERROR("bind_to_address");
|
|
}
|
|
}
|
|
|
|
#ifdef __MINGW32__
|
|
setnonblocking(sockfd);
|
|
#else
|
|
int flags = fcntl(sockfd, F_GETFL, 0);
|
|
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
|
|
#endif
|
|
|
|
ev_io_init(&resolv_io_watcher, resolv_sock_cb, sockfd, EV_READ);
|
|
resolv_io_watcher.data = ctx;
|
|
|
|
ev_io_start(loop, &resolv_io_watcher);
|
|
|
|
ev_timer_init(&resolv_timeout_watcher, resolv_timeout_cb, 0.0, 0.0);
|
|
resolv_timeout_watcher.data = ctx;
|
|
|
|
dns_set_tmcbck(ctx, dns_timer_setup_cb, loop);
|
|
|
|
return sockfd;
|
|
}
|
|
|
|
void
|
|
resolv_shutdown(struct ev_loop *loop)
|
|
{
|
|
struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data;
|
|
|
|
ev_io_stop(loop, &resolv_io_watcher);
|
|
|
|
if (ev_is_active(&resolv_timeout_watcher)) {
|
|
ev_timer_stop(loop, &resolv_timeout_watcher);
|
|
}
|
|
|
|
dns_close(ctx);
|
|
}
|
|
|
|
struct ResolvQuery *
|
|
resolv_query(const char *hostname, void (*client_cb)(struct sockaddr *, void *),
|
|
void (*client_free_cb)(void *), void *client_cb_data,
|
|
uint16_t port)
|
|
{
|
|
struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data;
|
|
|
|
/*
|
|
* Wrap udns's call back in our own
|
|
*/
|
|
struct ResolvQuery *cb_data = ss_malloc(sizeof(struct ResolvQuery));
|
|
if (cb_data == NULL) {
|
|
LOGE("Failed to allocate memory for DNS query callback data.");
|
|
return NULL;
|
|
}
|
|
cb_data->client_cb = client_cb;
|
|
cb_data->client_free_cb = client_free_cb;
|
|
cb_data->client_cb_data = client_cb_data;
|
|
memset(cb_data->queries, 0, sizeof(cb_data->queries));
|
|
cb_data->response_count = 0;
|
|
cb_data->responses = NULL;
|
|
cb_data->port = port;
|
|
|
|
/* Submit A and AAAA queries */
|
|
if (resolv_mode != MODE_IPV6_ONLY) {
|
|
cb_data->queries[0] = dns_submit_a4(ctx,
|
|
hostname, 0,
|
|
dns_query_v4_cb, cb_data);
|
|
if (cb_data->queries[0] == NULL) {
|
|
LOGE("Failed to submit DNS query: %s",
|
|
dns_strerror(dns_status(ctx)));
|
|
}
|
|
}
|
|
|
|
if (resolv_mode != MODE_IPV4_ONLY) {
|
|
cb_data->queries[1] = dns_submit_a6(ctx,
|
|
hostname, 0,
|
|
dns_query_v6_cb, cb_data);
|
|
if (cb_data->queries[1] == NULL) {
|
|
LOGE("Failed to submit DNS query: %s",
|
|
dns_strerror(dns_status(ctx)));
|
|
}
|
|
}
|
|
|
|
if (all_queries_are_null(cb_data)) {
|
|
if (cb_data->client_free_cb != NULL) {
|
|
cb_data->client_free_cb(cb_data->client_cb_data);
|
|
}
|
|
ss_free(cb_data);
|
|
}
|
|
|
|
return cb_data;
|
|
}
|
|
|
|
void
|
|
resolv_cancel(struct ResolvQuery *query_handle)
|
|
{
|
|
struct ResolvQuery *cb_data = (struct ResolvQuery *)query_handle;
|
|
struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data;
|
|
|
|
for (int i = 0; i < sizeof(cb_data->queries) / sizeof(cb_data->queries[0]);
|
|
i++)
|
|
if (cb_data->queries[i] != NULL) {
|
|
dns_cancel(ctx, cb_data->queries[i]);
|
|
ss_free(cb_data->queries[i]);
|
|
}
|
|
|
|
if (cb_data->client_free_cb != NULL) {
|
|
cb_data->client_free_cb(cb_data->client_cb_data);
|
|
}
|
|
|
|
ss_free(cb_data);
|
|
}
|
|
|
|
/*
|
|
* DNS UDP socket activity callback
|
|
*/
|
|
static void
|
|
resolv_sock_cb(struct ev_loop *loop, struct ev_io *w, int revents)
|
|
{
|
|
struct dns_ctx *ctx = (struct dns_ctx *)w->data;
|
|
|
|
if (revents & EV_READ) {
|
|
dns_ioevent(ctx, ev_now(loop));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wrapper for client callback we provide to udns
|
|
*/
|
|
static void
|
|
dns_query_v4_cb(struct dns_ctx *ctx, struct dns_rr_a4 *result, void *data)
|
|
{
|
|
struct ResolvQuery *cb_data = (struct ResolvQuery *)data;
|
|
|
|
if (result == NULL) {
|
|
if (verbose) {
|
|
LOGI("IPv4 resolv: %s", dns_strerror(dns_status(ctx)));
|
|
}
|
|
} else if (result->dnsa4_nrr > 0) {
|
|
struct sockaddr **new_responses = ss_realloc(cb_data->responses,
|
|
(cb_data->response_count +
|
|
result->dnsa4_nrr) *
|
|
sizeof(struct sockaddr *));
|
|
if (new_responses == NULL) {
|
|
LOGE("Failed to allocate memory for additional DNS responses");
|
|
} else {
|
|
cb_data->responses = new_responses;
|
|
|
|
for (int i = 0; i < result->dnsa4_nrr; i++) {
|
|
struct sockaddr_in *sa =
|
|
(struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
|
|
sa->sin_family = AF_INET;
|
|
sa->sin_port = cb_data->port;
|
|
sa->sin_addr = result->dnsa4_addr[i];
|
|
|
|
cb_data->responses[cb_data->response_count] =
|
|
(struct sockaddr *)sa;
|
|
if (cb_data->responses[cb_data->response_count] == NULL) {
|
|
LOGE(
|
|
"Failed to allocate memory for DNS query result address");
|
|
} else {
|
|
cb_data->response_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ss_free(result);
|
|
cb_data->queries[0] = NULL; /* mark A query as being completed */
|
|
|
|
/* Once all queries have completed, call client callback */
|
|
if (all_queries_are_null(cb_data)) {
|
|
return process_client_callback(cb_data);
|
|
}
|
|
}
|
|
|
|
static void
|
|
dns_query_v6_cb(struct dns_ctx *ctx, struct dns_rr_a6 *result, void *data)
|
|
{
|
|
struct ResolvQuery *cb_data = (struct ResolvQuery *)data;
|
|
|
|
if (result == NULL) {
|
|
if (verbose) {
|
|
LOGI("IPv6 resolv: %s", dns_strerror(dns_status(ctx)));
|
|
}
|
|
} else if (result->dnsa6_nrr > 0) {
|
|
struct sockaddr **new_responses = ss_realloc(cb_data->responses,
|
|
(cb_data->response_count +
|
|
result->dnsa6_nrr) *
|
|
sizeof(struct sockaddr *));
|
|
if (new_responses == NULL) {
|
|
LOGE("Failed to allocate memory for additional DNS responses");
|
|
} else {
|
|
cb_data->responses = new_responses;
|
|
|
|
for (int i = 0; i < result->dnsa6_nrr; i++) {
|
|
struct sockaddr_in6 *sa =
|
|
(struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
|
|
sa->sin6_family = AF_INET6;
|
|
sa->sin6_port = cb_data->port;
|
|
sa->sin6_addr = result->dnsa6_addr[i];
|
|
|
|
cb_data->responses[cb_data->response_count] =
|
|
(struct sockaddr *)sa;
|
|
if (cb_data->responses[cb_data->response_count] == NULL) {
|
|
LOGE(
|
|
"Failed to allocate memory for DNS query result address");
|
|
} else {
|
|
cb_data->response_count++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ss_free(result);
|
|
cb_data->queries[1] = NULL; /* mark AAAA query as being completed */
|
|
|
|
/* Once all queries have completed, call client callback */
|
|
if (all_queries_are_null(cb_data)) {
|
|
return process_client_callback(cb_data);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Called once all queries have been completed
|
|
*/
|
|
static void
|
|
process_client_callback(struct ResolvQuery *cb_data)
|
|
{
|
|
struct sockaddr *best_address = NULL;
|
|
|
|
if (resolv_mode == MODE_IPV4_FIRST) {
|
|
best_address = choose_ipv4_first(cb_data);
|
|
} else if (resolv_mode == MODE_IPV6_FIRST) {
|
|
best_address = choose_ipv6_first(cb_data);
|
|
} else {
|
|
best_address = choose_any(cb_data);
|
|
}
|
|
|
|
cb_data->client_cb(best_address, cb_data->client_cb_data);
|
|
|
|
for (int i = 0; i < cb_data->response_count; i++)
|
|
ss_free(cb_data->responses[i]);
|
|
|
|
ss_free(cb_data->responses);
|
|
if (cb_data->client_free_cb != NULL) {
|
|
cb_data->client_free_cb(cb_data->client_cb_data);
|
|
}
|
|
ss_free(cb_data);
|
|
}
|
|
|
|
static struct sockaddr *
|
|
choose_ipv4_first(struct ResolvQuery *cb_data)
|
|
{
|
|
for (int i = 0; i < cb_data->response_count; i++)
|
|
if (cb_data->responses[i]->sa_family == AF_INET) {
|
|
return cb_data->responses[i];
|
|
}
|
|
|
|
return choose_any(cb_data);
|
|
}
|
|
|
|
static struct sockaddr *
|
|
choose_ipv6_first(struct ResolvQuery *cb_data)
|
|
{
|
|
for (int i = 0; i < cb_data->response_count; i++)
|
|
if (cb_data->responses[i]->sa_family == AF_INET6) {
|
|
return cb_data->responses[i];
|
|
}
|
|
|
|
return choose_any(cb_data);
|
|
}
|
|
|
|
static struct sockaddr *
|
|
choose_any(struct ResolvQuery *cb_data)
|
|
{
|
|
if (cb_data->response_count >= 1) {
|
|
return cb_data->responses[0];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* DNS timeout callback
|
|
*/
|
|
static void
|
|
resolv_timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents)
|
|
{
|
|
struct dns_ctx *ctx = (struct dns_ctx *)w->data;
|
|
|
|
if (revents & EV_TIMER) {
|
|
dns_timeouts(ctx, 30, ev_now(loop));
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Callback to setup DNS timeout callback
|
|
*/
|
|
static void
|
|
dns_timer_setup_cb(struct dns_ctx *ctx, int timeout, void *data)
|
|
{
|
|
struct ev_loop *loop = (struct ev_loop *)data;
|
|
|
|
if (ev_is_active(&resolv_timeout_watcher)) {
|
|
ev_timer_stop(loop, &resolv_timeout_watcher);
|
|
}
|
|
|
|
if (ctx != NULL && timeout >= 0) {
|
|
ev_timer_set(&resolv_timeout_watcher, timeout, 0.0);
|
|
ev_timer_start(loop, &resolv_timeout_watcher);
|
|
}
|
|
}
|
|
|
|
static inline int
|
|
all_queries_are_null(struct ResolvQuery *cb_data)
|
|
{
|
|
int result = 1;
|
|
|
|
for (int i = 0; i < sizeof(cb_data->queries) / sizeof(cb_data->queries[0]);
|
|
i++)
|
|
result = result && cb_data->queries[i] == NULL;
|
|
|
|
return result;
|
|
}
|