blob: 38a914e6c3f6580dee2a375be06d0fe40aa67c05 [file] [log] [blame]
/*
* Copyright 2015 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <ares.h>
#include <arpa/nameser.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define RESOLV_CONF "/etc/resolv.conf"
#define RESOLV_CONF_EXTERNAL "/tmp/resolv.conf.external"
#define EXTRA_NAMESERVER_FILE "/tmp/dnsck_servers"
#define OPTION_NAMESERVER "nameserver"
#define MAX_SERVERS 128
#define MAX_LINE 256
struct ns_result {
const char *server;
int ares_result;
float msec;
};
void ares_error_die(const char *msg)
{
fprintf(stderr, "libcares error: %s\n", msg);
exit(1);
}
struct timespec timespec_diff(struct timespec *start, struct timespec *end)
{
struct timespec diff;
if (end->tv_nsec < start->tv_nsec) {
diff.tv_sec = end->tv_sec - start->tv_sec - 1;
diff.tv_nsec = 1000000000 + end->tv_nsec - start->tv_nsec;
} else {
diff.tv_sec = end->tv_sec - start->tv_sec;
diff.tv_nsec = end->tv_nsec - start->tv_nsec;
}
return diff;
}
void ares_wait(ares_channel channel)
{
int nfds, rc;
fd_set readers, writers;
struct timeval tv;
while (1) {
FD_ZERO(&readers);
FD_ZERO(&writers);
nfds = ares_fds(channel, &readers, &writers);
if (nfds == 0)
break;
ares_timeout(channel, NULL, &tv);
rc = select(nfds, &readers, &writers, NULL, &tv);
if (rc < 0)
perror("select");
ares_process(channel, &readers, &writers);
}
}
void result_callback(void *arg, int _result, int timeouts, unsigned char *abuf,
int alen)
{
int *result = arg;
*result = _result;
}
int resolve(ares_channel channel, const char *server,
struct ns_result *ns_result)
{
int rc;
int result;
struct timespec start, end;
struct timespec diff;
clock_gettime(CLOCK_MONOTONIC, &start);
rc = ares_set_servers_csv(channel, server);
if (rc)
ares_error_die(ares_strerror(rc));
ares_query(channel, "gstatic.com", ns_c_in, ns_t_a, result_callback, &result);
ares_wait(channel);
clock_gettime(CLOCK_MONOTONIC, &end);
diff = timespec_diff(&start, &end);
ns_result->server = server;
ns_result->ares_result = result;
ns_result->msec = (diff.tv_sec * 1000) + (diff.tv_nsec / 1000000.0f);
return result;
}
const char *ares_strresult(int result)
{
switch (result) {
case ARES_SUCCESS:
return "OK";
case ARES_ETIMEOUT:
return "TIMEOUT";
case ARES_ENOTFOUND:
return "DNSERR";
default:
return "ERROR";
}
}
/* from try_config() in ares_init.c */
const char *parse_nameserver_line(char *line)
{
char *p;
char *q;
/* trim line comment */
p = line;
while (*p && (*p != '#'))
p++;
*p = '\0';
/* trim trailing whitespace */
q = p - 1;
while ((q >= line) && isspace(*q))
q--;
*++q = '\0';
/* skip leading whitespace */
p = line;
while (*p && isspace(*p))
p++;
if (!*p)
/* empty line */
return NULL;
if (strncmp(p, OPTION_NAMESERVER, strlen(OPTION_NAMESERVER)) != 0)
/* line is not nameserver */
return NULL;
/* skip over option name */
p += strlen(OPTION_NAMESERVER);
if (!*p)
/* no nameserver value */
return NULL;
if (!isspace(*p))
/* whitespace between option name and value is mandatory */
return NULL;
/* skip over whitespace */
while (*p && isspace(*p))
p++;
if (!*p)
/* no nameserver value */
return NULL;
/* return pointer to nameserver value */
return p;
}
size_t read_resolv_conf(const char *servers[], size_t nservers)
{
char line[MAX_LINE];
const char *resolv_conf;
const char *ip;
struct stat st;
size_t count;
FILE *fp;
count = 0;
resolv_conf = RESOLV_CONF;
if (stat(RESOLV_CONF_EXTERNAL, &st) == 0)
resolv_conf = RESOLV_CONF_EXTERNAL;
fp = fopen(resolv_conf, "r");
if (!fp)
return 0;
while (count < nservers) {
if (fgets(line, sizeof(line), fp) == NULL)
break;
if ((ip = parse_nameserver_line(line)) != NULL) {
servers[count] = strdup(ip);
count++;
}
}
fclose(fp);
return count;
}
size_t read_extra_nameservers(const char *servers[], size_t nservers)
{
char line[MAX_LINE];
char *tokptr;
const char *delim = " \n\t,;";
const char *ip;
size_t count;
FILE *fp;
count = 0;
fp = fopen(EXTRA_NAMESERVER_FILE, "r");
if (!fp)
return 0;
while (count < nservers) {
if (fgets(line, sizeof(line), fp) == NULL)
break;
tokptr = line;
while (count < nservers) {
ip = strtok(tokptr, delim);
if (!ip)
break;
tokptr = NULL;
servers[count] = strdup(ip);
count++;
}
}
fclose(fp);
return count;
}
void resolve_array(ares_channel channel, const char *servers[], size_t nservers)
{
size_t i;
struct ns_result ns_result;
for (i = 0; i < nservers; i++) {
resolve(channel, servers[i], &ns_result);
printf("%s(%s),%.1fms ", servers[i], ares_strresult(ns_result.ares_result),
ns_result.msec);
}
}
void free_servers(const char *servers[], size_t nservers)
{
size_t i;
for (i = 0; i < nservers; i++)
free((char *)servers[i]);
}
void usage(const char *progname)
{
fprintf(stderr, "usage: %s [-i interface]\nwhere:\n", progname);
fprintf(stderr, "\t-i : name of interface to SO_BINDTODEVICE, like br0\n");
exit(1);
}
int main(int argc, char * const argv[])
{
ares_channel channel;
const char *servers[MAX_SERVERS];
size_t count;
const char *interface = NULL;
int rc, c;
struct ares_options options = {
.flags = ARES_FLAG_NOCHECKRESP,
.timeout = 3000, /* milliseconds */
.tries = 1,
};
while ((c = getopt(argc, argv, "i:")) != -1) {
switch (c) {
case 'i':
interface = optarg;
break;
default:
usage(argv[0]);
break;
}
}
rc = ares_library_init(ARES_LIB_INIT_NONE);
if (rc)
ares_error_die(ares_strerror(rc));
rc = ares_init_options(&channel, &options,
ARES_OPT_FLAGS | ARES_OPT_TIMEOUTMS | ARES_OPT_TRIES);
if (rc)
ares_error_die(ares_strerror(rc));
if (interface) {
ares_set_local_dev(channel, interface);
}
count = read_resolv_conf(servers, MAX_SERVERS);
resolve_array(channel, servers, count);
free_servers(servers, count);
count = read_extra_nameservers(servers, MAX_SERVERS);
resolve_array(channel, servers, count);
free_servers(servers, count);
if (optind > 0)
resolve_array(channel, (const char **)&argv[optind], argc - optind);
printf("\n");
ares_destroy(channel);
return 0;
}