From a8a3ad5e9ae725a400eeff0634db93fa04f0d1f0 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Fri, 6 Jul 2012 21:28:30 -0700 Subject: [PATCH] Introduce hostip(8), a tool for resolving a name before dnscrypt-proxy starts. It should help fighting the chicken-and-egg issue seen on routers, where dnscrypt-proxy requires a working NTP server, but the NTP server requires a working resolver. --- .gitignore | 1 + ChangeLog | 28 ++++++++ NEWS | 5 ++ README.markdown | 11 +++ configure.ac | 4 +- man/Makefile.am | 10 ++- man/dnscrypt-proxy.8 | 5 +- man/dnscrypt-proxy.8.markdown | 5 +- man/hostip.8 | 57 ++++++++++++++++ man/hostip.8.markdown | 47 +++++++++++++ src/Makefile.am | 1 + src/hostip/Makefile.am | 18 +++++ src/hostip/app.c | 125 ++++++++++++++++++++++++++++++++++ src/hostip/app.h | 14 ++++ src/hostip/options.c | 101 +++++++++++++++++++++++++++ src/hostip/options.h | 10 +++ 16 files changed, 436 insertions(+), 6 deletions(-) create mode 100644 man/hostip.8 create mode 100644 man/hostip.8.markdown create mode 100644 src/hostip/Makefile.am create mode 100644 src/hostip/app.c create mode 100644 src/hostip/app.h create mode 100644 src/hostip/options.c create mode 100644 src/hostip/options.h diff --git a/.gitignore b/.gitignore index fc02714d..16234dd9 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ src/.deps src/Makefile.in src/dnscrypt-proxy/Makefile.in src/dnscrypt-proxy/dnscrypt-proxy +src/hostip/hostip src/libevent/*.la src/libevent/*.lo src/libevent/*.pc diff --git a/ChangeLog b/ChangeLog index bc297c0b..394be6e6 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,31 @@ +Date: 2012-07-06 + + Useless indentation nits. + +Date: 2012-07-02 + + Use ioctl() instead of fnctl(fnctl()) as much as possible. Saves 1 syscall. + +Date: 2012-07-02 + + Define getpwnam() and struct passwd if getpwnam(3) exists but the headers don't. + +Date: 2012-06-26 + + Xcode 4.5 DP2 + +Date: 2012-06-24 + + Bump Linux packages to 10.0.1 + +Date: 2012-06-24 + + Update ChangeLog + +Date: 2012-06-24 + + Current dev version is 0.10.1 + Date: 2012-06-24 chroot() as soon as we can again. Drop libevent2's evdns arc4random() to use diff --git a/NEWS b/NEWS index d0e90f16..e195f8ab 100644 --- a/NEWS +++ b/NEWS @@ -1,4 +1,9 @@ +* Version 0.11: + - A new tool, hostip, can resolve host names before dnscrypt-proxy is + started. This should help resolving chicken-and-egg problems on + routers, as reported by LanceThePants. + * Version 0.10.1: - The daemon didn't start on some Linux distributions lacking the RANDOM_UUID sysctl. This has been fixed. diff --git a/README.markdown b/README.markdown index 6a2a8e0f..67bb0f04 100644 --- a/README.markdown +++ b/README.markdown @@ -184,6 +184,17 @@ are usually safe. A value below or equal to 512 will disable this mechanism, unless a client sends a packet with an OPT section providing a payload size. +The `hostip` utility +-------------------- + +The DNSCrypt proxy ships with a simple tool named `hostip` that +resolves a name to IPv4 or IPv6 addresses. + +This tool can be useful for starting some services before +`dnscrypt-proxy`. + +Queries made by `hostip` are not authenticated. + GUIs for dnscrypt-proxy ----------------------- diff --git a/configure.ac b/configure.ac index d053ed5f..7677f1f2 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ(2.61) -AC_INIT(dnscrypt-proxy, 0.10.1, https://github.com/opendns/dnscrypt-proxy/issues) +AC_INIT(dnscrypt-proxy, 0.11, https://github.com/opendns/dnscrypt-proxy/issues) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_SRCDIR([src/dnscrypt-proxy/app.c]) AC_CONFIG_HEADER([config.h]) @@ -325,6 +325,7 @@ dnl Output. AC_CONFIG_FILES([Makefile man/Makefile src/Makefile + src/hostip/Makefile src/dnscrypt-proxy/Makefile src/ext/Makefile src/libnacl/Makefile @@ -336,4 +337,3 @@ AC_CONFIG_FILES([Makefile AC_OUTPUT chmod +x src/libnacl/okcompilers/do - diff --git a/man/Makefile.am b/man/Makefile.am index 613afd82..dbc9b20d 100644 --- a/man/Makefile.am +++ b/man/Makefile.am @@ -1,9 +1,15 @@ man_MANS = \ - dnscrypt-proxy.8 + dnscrypt-proxy.8 \ + hostip.8 EXTRA_DIST= \ dnscrypt-proxy.8 \ - dnscrypt-proxy.8.markdown + dnscrypt-proxy.8.markdown \ + hostip.8 \ + hostip.8.markdown dnscrypt-proxy.8: dnscrypt-proxy.8.markdown @RONN@ dnscrypt-proxy.8.markdown + +hostip.8: hostip.8.markdown + @RONN@ hostip.8.markdown diff --git a/man/dnscrypt-proxy.8 b/man/dnscrypt-proxy.8 index 09dd23fa..4317bdf3 100644 --- a/man/dnscrypt-proxy.8 +++ b/man/dnscrypt-proxy.8 @@ -1,7 +1,7 @@ .\" generated with Ronn/v0.7.3 .\" http://github.com/rtomayko/ronn/tree/0.7.3 . -.TH "DNSCRYPT\-PROXY" "8" "June 2012" "" "" +.TH "DNSCRYPT\-PROXY" "8" "July 2012" "" "" . .SH "NAME" \fBdnscrypt\-proxy\fR \- A DNSCrypt forwarder @@ -95,5 +95,8 @@ $ dnscrypt\-proxy \-\-provider\-key=B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3 . .fi . +.SH "SEE ALSO" +hostip(8) +. .SH "COPYRIGHT" dnscrypt\-proxy is Copyright (C) 2011\-2012 OpenDNS, Inc\. \fBhttp://www\.opendns\.com/\fR diff --git a/man/dnscrypt-proxy.8.markdown b/man/dnscrypt-proxy.8.markdown index b02d03ae..ed8d9331 100644 --- a/man/dnscrypt-proxy.8.markdown +++ b/man/dnscrypt-proxy.8.markdown @@ -87,8 +87,11 @@ string, with optional columns. $ dnscrypt-proxy --provider-key=B735:1140:206F:225D:3E2B:D822:D7FD:691E:A1C3:3CC8:D666:8D0C:BE04:BFAB:CA43:FB79 --provider-name=2.dnscrypt-cert.dnscrypt.org. --resolver-ip=208.67.220.220 --daemonize +## SEE ALSO + +hostip(8) + ## COPYRIGHT dnscrypt-proxy is Copyright (C) 2011-2012 OpenDNS, Inc. `http://www.opendns.com/` - diff --git a/man/hostip.8 b/man/hostip.8 new file mode 100644 index 00000000..19264169 --- /dev/null +++ b/man/hostip.8 @@ -0,0 +1,57 @@ +.\" generated with Ronn/v0.7.3 +.\" http://github.com/rtomayko/ronn/tree/0.7.3 +. +.TH "HOSTIP" "8" "July 2012" "" "" +. +.SH "NAME" +\fBhostip\fR \- Resolve a host name to an IP address +. +.SH "SYNOPSIS" +\fBhostip\fR [\fIoptions\fR] host_name +. +.SH "DESCRIPTION" +\fBhostip\fR sends a DNS query to a resolver, and prints the IP addresses for the given host name\. +. +.P +It can be useful in order to retrieve IP addresses before dnscrypt\-proxy(8) is started\. +. +.SH "OPTIONS" +. +.IP "\(bu" 4 +\fB\-6\fR, \fB\-\-ipv6\fR: ask for AAAA records\. +. +.IP "\(bu" 4 +\fB\-h\fR, \fB\-\-help\fR: show usage\. +. +.IP "\(bu" 4 +\fB\-r\fR, \fB\-\-resolver\-address=\fR: the resolver IP address (default: 208\.67\.222\.222, OpenDNS)\. +. +.IP "\(bu" 4 +\fB\-V\fR, \fB\-\-version\fR: show version number\. +. +.IP "" 0 +. +.SH "SIMPLE USAGE EXAMPLE" +. +.nf + +$ hostip www\.example\.com +. +.fi +. +.SH "ADVANCED USAGE EXAMPLE" +. +.nf + +$ hostip \-6 \-r 213\.154\.224\.3 www\.google\.com +. +.fi +. +.SH "EXIT STATUS" +The \fBhostip\fR utility exits 0 on success, and > 0 if an error occurs\. +. +.SH "SEE ALSO" +dnscrypt\-proxy(8) +. +.SH "COPYRIGHT" +hostip is Copyright (C) 2012 OpenDNS, Inc\. \fBhttp://www\.opendns\.com/\fR diff --git a/man/hostip.8.markdown b/man/hostip.8.markdown new file mode 100644 index 00000000..0436d810 --- /dev/null +++ b/man/hostip.8.markdown @@ -0,0 +1,47 @@ +hostip(8) -- Resolve a host name to an IP address +================================================= + +## SYNOPSIS + +`hostip` [] host_name + +## DESCRIPTION + +**hostip** sends a DNS query to a resolver, and prints the IP +addresses for the given host name. + +It can be useful in order to retrieve IP addresses before +dnscrypt-proxy(8) is started. + +## OPTIONS + + * `-6`, `--ipv6`: ask for AAAA records. + + * `-h`, `--help`: show usage. + + * `-r`, `--resolver-address=`: the resolver IP address (default: +208.67.222.222, OpenDNS). + + * `-V`, `--version`: show version number. + +## SIMPLE USAGE EXAMPLE + + $ hostip www.example.com + +## ADVANCED USAGE EXAMPLE + + $ hostip -6 -r 213.154.224.3 www.google.com + +## EXIT STATUS + +The `hostip` utility exits 0 on success, and > 0 if an error occurs. + +## SEE ALSO + +dnscrypt-proxy(8) + +## COPYRIGHT + +hostip is Copyright (C) 2012 OpenDNS, Inc. +`http://www.opendns.com/` + diff --git a/src/Makefile.am b/src/Makefile.am index 89e907cb..b5ab620f 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,6 +1,7 @@ SUBDIRS = \ ext \ + hostip \ libevent \ libnacl \ dnscrypt-proxy diff --git a/src/hostip/Makefile.am b/src/hostip/Makefile.am new file mode 100644 index 00000000..3127de82 --- /dev/null +++ b/src/hostip/Makefile.am @@ -0,0 +1,18 @@ + +bin_PROGRAMS = \ + hostip + +hostip_SOURCES = \ + app.c \ + app.h \ + options.c \ + options.h + +AM_CFLAGS = @CWFLAGS@ + +AM_CPPFLAGS = \ + -I../libevent/include + +hostip_LDADD = \ + ../libevent/libevent_extra.la \ + ../libevent/libevent_core.la diff --git a/src/hostip/app.c b/src/hostip/app.c new file mode 100644 index 00000000..cf92bdd9 --- /dev/null +++ b/src/hostip/app.c @@ -0,0 +1,125 @@ + +#include +#include +#ifdef _WIN32 +# include +#else +# include +# include +# include +#endif +#include +#include +#include + +#include +#include +#include + +#include "app.h" +#include "options.h" + +static AppContext app_context; + +#ifndef INET6_ADDRSTRLEN +# define INET6_ADDRSTRLEN 46U +#endif + +static void +query_cb_err_print(const int err) +{ + fprintf(stderr, "[%s]\n", evdns_err_to_string(err)); + exit(1); +} + +static void +ipv4_query_cb(int result, char type, int count, int ttl, + void * const ips_, void * const app_context_) +{ + char ip_s_buf[INET6_ADDRSTRLEN + 1U]; + AppContext *app_context = app_context_; + struct in_addr *ips = ips_; + const char *ip_s; + int i = 0; + + (void) ttl; + if (result != DNS_ERR_NONE) { + query_cb_err_print(result); + } + assert(type == DNS_IPv4_A); + assert(count >= 0); + while (i < count) { + ip_s = evutil_inet_ntop(AF_INET, &ips[i], ip_s_buf, sizeof ip_s_buf); + if (ip_s != NULL) { + puts(ip_s); + } + i++; + } + event_base_loopexit(app_context->event_loop, NULL); +} + +static void +ipv6_query_cb(int result, char type, int count, int ttl, + void * const ips_, void * const app_context_) +{ + char ip_s_buf[INET6_ADDRSTRLEN + 1U]; + AppContext *app_context = app_context_; + struct in6_addr *ips = ips_; + const char *ip_s; + int i = 0; + + (void) ttl; + if (result != DNS_ERR_NONE) { + query_cb_err_print(result); + } + assert(type == DNS_IPv6_AAAA); + assert(count >= 0); + while (i < count) { + ip_s = evutil_inet_ntop(AF_INET6, &ips[i], ip_s_buf, sizeof ip_s_buf); + if (ip_s != NULL) { + puts(ip_s); + } + i++; + } + event_base_loopexit(app_context->event_loop, NULL); +} + +int main(int argc, char *argv[]) +{ + struct evdns_base *evdns_base; + + if (options_parse(&app_context, argc, argv) != 0) { + return 1; + } +#ifdef _WIN32 + WSADATA wsa_data; + WSAStartup(MAKEWORD(2, 2), &wsa_data); +#endif + if ((app_context.event_loop = event_base_new()) == NULL) { + perror("event_base_new"); + return 1; + } + if ((evdns_base = evdns_base_new(app_context.event_loop, 0)) == NULL) { + perror("evdns_base"); + return 1; + } + if (evdns_base_nameserver_ip_add(evdns_base, + app_context.resolver_ip) != 0) { + fprintf(stderr, "Unable to use [%s] as a resolver\n", + app_context.resolver_ip); + return 1; + } + if (app_context.want_ipv6 != 0) { + evdns_base_resolve_ipv6(evdns_base, app_context.host_name, + DNS_QUERY_NO_SEARCH, + ipv6_query_cb, &app_context); + } else { + evdns_base_resolve_ipv4(evdns_base, app_context.host_name, + DNS_QUERY_NO_SEARCH, + ipv4_query_cb, &app_context); + } + event_base_dispatch(app_context.event_loop); + event_base_free(app_context.event_loop); + + return 0; +} diff --git a/src/hostip/app.h b/src/hostip/app.h new file mode 100644 index 00000000..a113d2ea --- /dev/null +++ b/src/hostip/app.h @@ -0,0 +1,14 @@ + +#ifndef __APP_H__ +#define __APP_H__ 1 + +#include + +typedef struct AppContext_ { + struct event_base *event_loop; + const char *host_name; + const char *resolver_ip; + _Bool want_ipv6; +} AppContext; + +#endif diff --git a/src/hostip/options.c b/src/hostip/options.c new file mode 100644 index 00000000..a68f15a0 --- /dev/null +++ b/src/hostip/options.c @@ -0,0 +1,101 @@ + +#include +#include + +#include +#include +#include +#include + +#include "app.h" +#include "options.h" + +#ifndef DEFAULT_RESOLVER_IP +# define DEFAULT_RESOLVER_IP "208.67.220.220" +#endif + +static struct option getopt_long_options[] = { + { "ipv6", 0, NULL, '6' }, + { "help", 0, NULL, 'h' }, + { "resolver-address", 1, NULL, 'r' }, + { "version", 0, NULL, 'V' }, + { NULL, 0, NULL, 0 } +}; +static const char *getopt_options = "6hr:V"; + +static void +options_version(void) +{ + puts("hostip v" PACKAGE_VERSION "\n" + "Copyright (C) 2012 OpenDNS, Inc."); +} + +static void +options_usage(void) +{ + puts("Usage: hostip [-6] [-r resolver_ip[:port]] host_name\n" + " -6, --ipv6: ask for AAAA records\n" + " -h, --help: show usage\n" + " -r, --resolver-address=: the resolver IP address (default: OpenDNS)\n" + " -V, --version: show version number\n" + "\n" + "Example: hostip -r 208.67.222.222 www.example.com\n"); +} + +static +void options_init_with_default(AppContext * const app_context) +{ + app_context->host_name = NULL; + app_context->resolver_ip = DEFAULT_RESOLVER_IP; + app_context->want_ipv6 = 0; +} + +static int +options_apply(AppContext * const app_context) +{ + if (app_context->resolver_ip == NULL) { + options_usage(); + exit(1); + } + return 0; +} + +int +options_parse(AppContext * const app_context, int argc, char *argv[]) +{ + int opt_flag; + int option_index = 0; + + options_init_with_default(app_context); + while ((opt_flag = getopt_long(argc, argv, + getopt_options, getopt_long_options, + &option_index)) != -1) { + switch (opt_flag) { + case '6': + app_context->want_ipv6 = 1; + break; + case 'h': + options_usage(); + exit(0); + case 'r': + app_context->resolver_ip = optarg; + break; + case 'V': + options_version(); + exit(0); + default: + options_usage(); + exit(0); + } + } + argc -= optind; + argv += optind; + if (argc != 1 || *argv == NULL) { + options_usage(); + exit(1); + } + app_context->host_name = *argv; + options_apply(app_context); + + return 0; +} diff --git a/src/hostip/options.h b/src/hostip/options.h new file mode 100644 index 00000000..b0ac733e --- /dev/null +++ b/src/hostip/options.h @@ -0,0 +1,10 @@ + +#ifndef __OPTIONS_H__ +#define __OPTIONS_H__ 1 + +#include "options.h" + +int options_parse(AppContext * const app_context, int argc, char *argv[]); + +#endif +