From 72dd666777d4e341fc5c066cd7dc1975d0dd0090 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 7 Dec 2009 17:21:41 -0500 Subject: [PATCH] evdns_getaddrinfo() now supports the /etc/hosts file. The regular blocking evutil_getaddrinfo() already supported /etc/hosts by falling back to getaddrinfo() or gethostbyname(). But evdns_getaddrinfo() had no such facility. Now it does. The data structure here isn't very clever. I guess people with huge /etc/hosts files will either need to get out of the 1980s, or submit a patch to this code so that it uses a hashtable instead of a linked list. Includes basic unit tests. --- evdns.c | 280 ++++++++++++++++++++++++++++++++++++------- include/event2/dns.h | 17 ++- test/regress_dns.c | 40 +++++++ 3 files changed, 290 insertions(+), 47 deletions(-) diff --git a/evdns.c b/evdns.c index 6df9eb7d..7d92038d 100644 --- a/evdns.c +++ b/evdns.c @@ -363,11 +363,24 @@ struct evdns_base { struct search_state *global_search_state; + TAILQ_HEAD(hosts_list, hosts_entry) hostsdb; + #ifndef _EVENT_DISABLE_THREAD_SUPPORT void *lock; #endif }; +struct hosts_entry { + TAILQ_ENTRY(hosts_entry) next; + union { + struct sockaddr sa; + struct sockaddr_in sin; + struct sockaddr_in6 sin6; + } addr; + int addrlen; + char hostname[1]; +}; + static struct evdns_base *current_base = NULL; struct evdns_base * @@ -2502,6 +2515,28 @@ evdns_nameserver_add(unsigned long int address) { return evdns_base_nameserver_add(current_base, address); } +static void +sockaddr_setport(struct sockaddr *sa, ev_uint16_t port) +{ + if (sa->sa_family == AF_INET) { + ((struct sockaddr_in *)sa)->sin_port = htons(port); + } else if (sa->sa_family == AF_INET6) { + ((struct sockaddr_in6 *)sa)->sin6_port = htons(port); + } +} + +static ev_uint16_t +sockaddr_getport(struct sockaddr *sa) +{ + if (sa->sa_family == AF_INET) { + return ntohs(((struct sockaddr_in *)sa)->sin_port); + } else if (sa->sa_family == AF_INET6) { + return ntohs(((struct sockaddr_in6 *)sa)->sin6_port); + } else { + return 0; + } +} + /* exported function */ int evdns_base_nameserver_ip_add(struct evdns_base *base, const char *ip_as_string) { @@ -2516,20 +2551,8 @@ evdns_base_nameserver_ip_add(struct evdns_base *base, const char *ip_as_string) return 4; } sa = (struct sockaddr *) &ss; - if (sa->sa_family == AF_INET) { - struct sockaddr_in *sin = (struct sockaddr_in *)sa; - if (sin->sin_port == 0) - sin->sin_port = htons(53); - } -#ifdef AF_INET6 - else if (sa->sa_family == AF_INET6) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; - if (sin6->sin6_port == 0) - sin6->sin6_port = htons(53); - } -#endif - else - return -1; + if (sockaddr_getport(sa) == 0) + sockaddr_setport(sa, 53); EVDNS_LOCK(base); res = _evdns_nameserver_add_impl(base, sa, len); @@ -3370,42 +3393,32 @@ evdns_base_resolv_conf_parse(struct evdns_base *base, int flags, const char *con static int evdns_base_resolv_conf_parse_impl(struct evdns_base *base, int flags, const char *const filename) { - struct stat st; - int fd, n, r; - u8 *resolv; + size_t n; + char *resolv; char *start; int err = 0; log(EVDNS_LOG_DEBUG, "Parsing resolv.conf file %s", filename); - fd = open(filename, O_RDONLY); - if (fd < 0) { - evdns_resolv_set_defaults(base, flags); - return 1; - } - - if (fstat(fd, &st)) { err = 2; goto out1; } - if (!st.st_size) { - evdns_resolv_set_defaults(base, flags); - err = (flags & DNS_OPTION_NAMESERVERS) ? 6 : 0; - goto out1; + if (flags & DNS_OPTION_HOSTSFILE) { +#ifdef WIN32 + evdns_base_load_hosts(base, NULL); +#else + evdns_base_load_hosts(base, "/etc/hosts"); +#endif } - if (st.st_size > 65535) { err = 3; goto out1; } /* no resolv.conf should be any bigger */ - resolv = (u8 *) mm_malloc((size_t)st.st_size + 1); - if (!resolv) { err = 4; goto out1; } - - n = 0; - while ((r = read(fd, resolv+n, (size_t)st.st_size-n)) > 0) { - n += r; - if (n == st.st_size) - break; - EVUTIL_ASSERT(n < st.st_size); + if ((err = evutil_read_file(filename, &resolv, &n, 0)) < 0) { + if (err == -1) { + /* No file. */ + evdns_resolv_set_defaults(base, flags); + return 1; + } else { + return 2; + } } - if (r < 0) { err = 5; goto out2; } - resolv[n] = 0; /* we malloced an extra byte; this should be fine. */ - start = (char *) resolv; + start = resolv; for (;;) { char *const newline = strchr(start, '\n'); if (!newline) { @@ -3427,10 +3440,7 @@ evdns_base_resolv_conf_parse_impl(struct evdns_base *base, int flags, const char search_set_from_hostname(base); } -out2: mm_free(resolv); -out1: - close(fd); return err; } @@ -3441,6 +3451,8 @@ evdns_resolv_conf_parse(int flags, const char *const filename) { return evdns_base_resolv_conf_parse(current_base, flags, filename); } + + #ifdef WIN32 /* Add multiple nameservers from a space-or-comma-separated list. */ static int @@ -3703,6 +3715,8 @@ evdns_base_new(struct event_base *event_base, int initialize_nameservers) base->global_nameserver_probe_initial_timeout.tv_sec = 10; base->global_nameserver_probe_initial_timeout.tv_usec = 0; + TAILQ_INIT(&base->hostsdb); + if (initialize_nameservers) { int r; #ifdef WIN32 @@ -3798,6 +3812,15 @@ evdns_base_free_and_unlock(struct evdns_base *base, int fail_requests) mm_free(base->global_search_state); base->global_search_state = NULL; } + + { + struct hosts_entry *victim; + while ((victim = TAILQ_FIRST(&base->hostsdb))) { + TAILQ_REMOVE(&base->hostsdb, victim, next); + mm_free(victim); + } + } + EVDNS_UNLOCK(base); EVTHREAD_FREE_LOCK(base->lock, EVTHREAD_LOCKTYPE_RECURSIVE); @@ -3822,6 +3845,111 @@ evdns_shutdown(int fail_requests) evdns_log_fn = NULL; } +static int +evdns_base_parse_hosts_line(struct evdns_base *base, char *line) +{ + char *strtok_state; + static const char *const delims = " \t"; + char *const addr = strtok_r(line, delims, &strtok_state); + char *hostname, *hash; + struct sockaddr_storage ss; + int socklen = sizeof(ss); + ASSERT_LOCKED(base); + +#define NEXT_TOKEN strtok_r(NULL, delims, &strtok_state) + + if (!addr || *addr == '#') + return 0; + + memset(&ss, 0, sizeof(ss)); + if (evutil_parse_sockaddr_port(addr, (struct sockaddr*)&ss, &socklen)<0) + return -1; + if (socklen > sizeof(struct sockaddr_in6)) + return -1; + + if (sockaddr_getport((struct sockaddr*)&ss)) + return -1; + + while ((hostname = NEXT_TOKEN)) { + struct hosts_entry *he; + size_t namelen; + if ((hash = strchr(hostname, '#'))) { + if (hash == hostname) + return 0; + *hash = '\0'; + } + + namelen = strlen(hostname); + + he = mm_calloc(1, sizeof(struct hosts_entry)+namelen); + if (!he) + return -1; + EVUTIL_ASSERT(socklen <= sizeof(he->addr)); + memcpy(&he->addr, &ss, socklen); + memcpy(he->hostname, hostname, namelen+1); + he->addrlen = socklen; + + TAILQ_INSERT_TAIL(&base->hostsdb, he, next); + + if (hash) + return 0; + } + + return 0; +#undef NEXT_TOKEN +} + +static int +evdns_base_load_hosts_impl(struct evdns_base *base, const char *hosts_fname) +{ + char *str=NULL, *cp, *eol; + size_t len; + int err=0; + + ASSERT_LOCKED(base); + + if (hosts_fname == NULL || + (err = evutil_read_file(hosts_fname, &str, &len, 0)) < 0) { + char tmp[64]; + strlcpy(tmp, "127.0.0.1 localhost", sizeof(tmp)); + evdns_base_parse_hosts_line(base, tmp); + strlcpy(tmp, "::1 localhost", sizeof(tmp)); + evdns_base_parse_hosts_line(base, tmp); + return err ? -1 : 0; + } + + /* This will break early if there is a NUL in the hosts file. + * Probably not a problem.*/ + cp = str; + for (;;) { + eol = strchr(cp, '\n'); + + if (eol) { + *eol = '\0'; + evdns_base_parse_hosts_line(base, cp); + cp = eol+1; + } else { + evdns_base_parse_hosts_line(base, cp); + break; + } + } + + mm_free(str); + return 0; +} + +int +evdns_base_load_hosts(struct evdns_base *base, const char *hosts_fname) +{ + int res; + if (!base) + base = current_base; + EVDNS_LOCK(base); + res = evdns_base_load_hosts_impl(base, hosts_fname); + EVDNS_UNLOCK(base); + return res; +} + /* A single request for a getaddrinfo, either v4 or v6. */ struct getaddrinfo_subrequest { struct evdns_request *r; @@ -4106,6 +4234,64 @@ evdns_getaddrinfo_gotresolve(int result, char type, int count, } } +static struct hosts_entry * +find_hosts_entry(struct evdns_base *base, const char *hostname, + struct hosts_entry *find_after) +{ + struct hosts_entry *e; + + if (find_after) + e = TAILQ_NEXT(find_after, next); + else + e = TAILQ_FIRST(&base->hostsdb); + + for (; e; e = TAILQ_NEXT(e, next)) { + if (!evutil_ascii_strcasecmp(e->hostname, hostname)) + return e; + } + return NULL; +} + +static int +evdns_getaddrinfo_fromhosts(struct evdns_base *base, + const char *nodename, struct evutil_addrinfo *hints, ev_uint16_t port, + struct evutil_addrinfo **res) +{ + int n_found = 0; + struct hosts_entry *e; + struct evutil_addrinfo *ai=NULL; + int f = hints->ai_family; + + EVDNS_LOCK(base); + for (e = find_hosts_entry(base, nodename, NULL); e; + e = find_hosts_entry(base, nodename, e)) { + struct evutil_addrinfo *ai_new; + ++n_found; + if ((e->addr.sa.sa_family == AF_INET && f == PF_INET6) || + (e->addr.sa.sa_family == AF_INET6 && f == PF_INET)) + continue; + ai_new = evutil_new_addrinfo(&e->addr.sa, e->addrlen, hints); + if (!ai_new) { + n_found = 0; + goto out; + } + sockaddr_setport(ai_new->ai_addr, port); + ai = evutil_addrinfo_append(ai, ai_new); + } + EVDNS_UNLOCK(base); +out: + if (n_found) { + /* Note that we return an empty answer if we found entries for + * this hostname but none were of the right address type. */ + *res = ai; + return 0; + } else { + if (ai) + evutil_freeaddrinfo(ai); + return -1; + } +} + struct evdns_getaddrinfo_request * evdns_getaddrinfo(struct evdns_base *dns_base, const char *nodename, const char *servname, @@ -4158,6 +4344,12 @@ evdns_getaddrinfo(struct evdns_base *dns_base, return NULL; } + /* If there is an entry in the hosts file, we should give it now. */ + if (!evdns_getaddrinfo_fromhosts(dns_base, nodename, &hints, port, &res)) { + cb(0, res, arg); + return NULL; + } + /* Okay, things are serious now. We're going to need to actually * launch a request. */ diff --git a/include/event2/dns.h b/include/event2/dns.h index 60b4e6f3..fb3bb681 100644 --- a/include/event2/dns.h +++ b/include/event2/dns.h @@ -201,7 +201,8 @@ extern "C" { #define DNS_OPTION_SEARCH 1 #define DNS_OPTION_NAMESERVERS 2 #define DNS_OPTION_MISC 4 -#define DNS_OPTIONS_ALL 7 +#define DNS_OPTION_HOSTSFILE 8 +#define DNS_OPTIONS_ALL 15 /** * The callback that contains the results from a lookup. @@ -431,7 +432,7 @@ int evdns_base_set_option(struct evdns_base *base, const char *option, const cha @param base the evdns_base to which to apply this operation @param flags any of DNS_OPTION_NAMESERVERS|DNS_OPTION_SEARCH|DNS_OPTION_MISC| - DNS_OPTIONS_ALL + DNS_OPTIONS_HOSTSFILE|DNS_OPTIONS_ALL @param filename the path to the resolv.conf file @return 0 if successful, or various positive error codes if an error occurred (see above) @@ -439,6 +440,17 @@ int evdns_base_set_option(struct evdns_base *base, const char *option, const cha */ int evdns_base_resolv_conf_parse(struct evdns_base *base, int flags, const char *const filename); +/** + Load an /etc/hosts-style file from 'hosts_fname' into 'base'. + + If hosts_fname is NULL, add minimal entries for localhost, and nothing + else. + + Note that only evdns_getaddrinfo uses the /etc/hosts entries. + + Return 0 on success, negative on failure. +*/ +int evdns_base_load_hosts(struct evdns_base *base, const char *hosts_fname); /** Obtain nameserver information using the Windows API. @@ -616,7 +628,6 @@ struct evdns_getaddrinfo_request; * Limitations: * * - The AI_V4MAPPED and AI_ALL flags are not currently implemented. - * - We don't look at the /etc/hosts file. * - For ai_socktype, we only handle SOCKTYPE_STREAM, SOCKTYPE_UDP, and 0. * - For ai_protocol, we only handle IPPROTO_TCP, IPPROTO_UDP, and 0. */ diff --git a/test/regress_dns.c b/test/regress_dns.c index 2f3cdb89..73881913 100644 --- a/test/regress_dns.c +++ b/test/regress_dns.c @@ -1151,6 +1151,9 @@ test_getaddrinfo_async(void *arg) struct evdns_base *dns_base = evdns_base_new(data->base, 0); + /* for localhost */ + evdns_base_load_hosts(dns_base, NULL); + memset(a_out, 0, sizeof(a_out)); n_gai_results_pending = 10000; /* don't think about exiting yet. */ @@ -1260,6 +1263,43 @@ test_getaddrinfo_async(void *arg) evutil_freeaddrinfo(local_outcome.ai); local_outcome.ai = NULL; + /* 1g. We find localhost immediately. (pf_unspec) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, "LOCALHOST", "80", + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + /* we should get a v4 address of 127.0.0.1 .... */ + a = ai_find_by_family(local_outcome.ai, PF_INET); + tt_assert(a); + test_ai_eq(a, "127.0.0.1:80", SOCK_STREAM, IPPROTO_TCP); + /* ... and a v6 address of ::1 */ + a = ai_find_by_family(local_outcome.ai, PF_INET6); + tt_assert(a); + test_ai_eq(a, "[::1]:80", SOCK_STREAM, IPPROTO_TCP); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + + /* 1g. We find localhost immediately. (pf_inet6) */ + memset(&hints, 0, sizeof(hints)); + memset(&local_outcome, 0, sizeof(local_outcome)); + hints.ai_family = PF_INET6; + hints.ai_socktype = SOCK_STREAM; + r = evdns_getaddrinfo(dns_base, "LOCALHOST", "9999", + &hints, gai_cb, &local_outcome); + tt_int_op(r,==,0); + tt_int_op(local_outcome.err,==,0); + tt_assert(local_outcome.ai); + a = local_outcome.ai; + test_ai_eq(a, "[::1]:9999", SOCK_STREAM, IPPROTO_TCP); + tt_ptr_op(a->ai_next, ==, NULL); + evutil_freeaddrinfo(local_outcome.ai); + local_outcome.ai = NULL; + /* 2. Okay, now we can actually test the asynchronous resolver. */ /* Start a dummy local dns server... */ port = get_generic_server(data->base, &dns_port, -- 2.40.0