From a942441615af65a5e80f2d8c1348a4feb7a2ff62 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 21 Sep 2006 21:48:06 +0000 Subject: [PATCH] r8872@Kushana: nickm | 2006-09-21 14:00:20 -0400 Implement server-side reverse DNS using eventdns. Add an option to routerdescs so we can tell which servers have eventdns enabled. svn:r8437 --- ChangeLog | 8 ++ doc/TODO | 8 +- doc/dir-spec.txt | 19 +++- src/or/dns.c | 216 +++++++++++++++++++++++++++++++++++++------ src/or/or.h | 4 +- src/or/router.c | 9 +- src/or/routerparse.c | 9 ++ 7 files changed, 239 insertions(+), 34 deletions(-) diff --git a/ChangeLog b/ChangeLog index b3c8e865af..cfc9857bac 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,12 @@ Changes in version 0.1.2.2-alpha - 2006-??-?? + o Major features: + + - Add server-side support for "reverse" DNS lookups (using PTR + records so clients can determine the canonical hostname for a given + IPv4 address). This has been specified for a long time, but was + previously never implemented. This is only supported by eventdns; + servers now announce in their descriptors whether they support + eventdns. o Security Fixes, minor - If a client asked for a server by name, and we didn't have a diff --git a/doc/TODO b/doc/TODO index c65fd08d73..88708a0588 100644 --- a/doc/TODO +++ b/doc/TODO @@ -100,13 +100,15 @@ d - Autodetect whether DNS is broken in this way. d - Add option to use /etc/hosts? d - Special-case localhost? - Verify that it works on windows -N - Make reverse DNS work. - - Specify + . Make reverse DNS work. + o Specify X Implement with dnsworkers (There's no point doing this, since we will throw away dnsworkers once eventdns is confirmed to work everywhere.) o Implement in eventdns - - Connect to resolve cells, server-side. + o Connect to resolve cells, server-side. + o Add element to routerinfo to note routers that aren't using eventdns, + so we can avoid sending them reverse DNS etc. - Add client-side interface - Performance improvements diff --git a/doc/dir-spec.txt b/doc/dir-spec.txt index 8c2bf75311..a211ebc095 100644 --- a/doc/dir-spec.txt +++ b/doc/dir-spec.txt @@ -254,7 +254,22 @@ $Id$ [We didn't start parsing these lines until Tor 0.1.0.6-rc; they should be marked with "opt" until earlier versions of Tor are obsolete.] -2.1. Nonterminals in routerdescriptors + "eventdns" bool NL + + Declare whether this version of Tor is using the newer enhanced + dns logic. Versions of Tor without eventdns SHOULD not be used for + reverse hostname lookups. + + [All versions of Tor before 0.1.2.2-alpha should be assumed to have + this option set to 0 if it is not present. All Tor versions at + 0.1.2.2-alpha or later should be assumed to have this option set to + 1 if it is not present. Until 0.1.2.1-alpha-dev, this option was + not generated, even when eventdns was in use. Versions of Tor + before 0.1.2.1-alpha-dev did not parse this option, so it should be + marked "opt". With some future version, the old 'dnsworker' logic + will be removed, rendering this option of historical interest only.] + +2.1. Nonterminals in router descriptors nickname ::= between 1 and 19 alphanumeric characters, case-insensitive. @@ -270,6 +285,8 @@ $Id$ ip6 ::= an IPv6 address, surrounded by square brackets. num_ip6_bits ::= an integer between 0 and 128 + bool ::= "0" | "1" + Ports are required; if they are not included in the router line, they must appear in the "ports" lines. diff --git a/src/or/dns.c b/src/or/dns.c index da6b768380..03ed0c03f4 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -97,8 +97,12 @@ typedef struct cached_resolve_t { HT_ENTRY(cached_resolve_t) node; uint32_t magic; char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */ - uint32_t addr; /**< IPv4 addr for address. */ + union { + uint32_t addr; /**< IPv4 addr for address. */ + char *hostname; /**< Hostname for address (if a reverse lookup) */ + } result; uint8_t state; /**< Is this cached entry pending/done/valid/failed? */ + uint8_t is_reverse; /**< Is this a reverse (addr-to-hostname) lookup? */ time_t expire; /**< Remove items from cache after this time. */ uint32_t ttl; /**< What TTL did the nameserver tell us? */ /** Connections that want to know when we get an answer for this resolve. */ @@ -106,7 +110,8 @@ typedef struct cached_resolve_t { } cached_resolve_t; static void purge_expired_resolves(time_t now); -static void dns_found_answer(const char *address, uint32_t addr, char outcome, +static void dns_found_answer(const char *address, int is_reverse, + uint32_t addr, const char *hostname, char outcome, uint32_t ttl); static void send_resolved_cell(edge_connection_t *conn, uint8_t answer_type); static int launch_resolve(edge_connection_t *exitconn); @@ -245,6 +250,8 @@ _free_cached_resolve(cached_resolve_t *r) r->pending_connections = victim->next; tor_free(victim); } + if (r->is_reverse) + tor_free(r->result.hostname); r->magic = 0xFF00FF00; tor_free(r); } @@ -362,6 +369,8 @@ purge_expired_resolves(time_t now) removed ? removed->address : "NULL", (void*)remove); } tor_assert(removed == resolve); + if (resolve->is_reverse) + tor_free(resolve->result.hostname); resolve->magic = 0xF0BBF0BB; tor_free(resolve); } else { @@ -415,6 +424,74 @@ send_resolved_cell(edge_connection_t *conn, uint8_t answer_type) conn->cpath_layer); } +/** Send a response to the RESOLVE request of a connection for an in-addr.arpa + * address on connection conn which yielded the result hostname. + * The answer type will be RESOLVED_HOSTNAME. + */ +static void +send_resolved_hostname_cell(edge_connection_t *conn, const char *hostname) +{ + char buf[RELAY_PAYLOAD_SIZE]; + size_t buflen; + uint32_t ttl; + size_t namelen = strlen(hostname); + + tor_assert(namelen < 256); + ttl = dns_clip_ttl(conn->address_ttl); + + buf[0] = RESOLVED_TYPE_HOSTNAME; + buf[1] = (uint8_t)namelen; + memcpy(buf+2, hostname, namelen); + set_uint32(buf+2+namelen, htonl(ttl)); + buflen = 2+namelen+4; + + connection_edge_send_command(conn, circuit_get_by_edge_conn(conn), + RELAY_COMMAND_RESOLVED, buf, buflen, + conn->cpath_layer); +} + +/** Given a lower-case address, check to see whether it's a + * 1.2.3.4.in-addr.arpa address used for reverse lookups. If so, + * parse it and place the address in in if present. Return 1 on success; + * 0 if the address is not in in-addr.arpa format, and -1 if the address is + * malformed. */ +static int +parse_inaddr_arpa_address(const char *address, struct in_addr *in) +{ + char buf[INET_NTOA_BUF_LEN]; + char *cp; + size_t len; + struct in_addr inaddr; + + cp = strstr(address, ".in-addr.arpa"); + if (!cp || *(cp+strlen(".in-addr.arpa"))) + return 0; /* not an .in-addr.arpa address */ + + len = cp - address; + + if (len >= INET_NTOA_BUF_LEN) + return -1; /* Too long. */ + + memcpy(buf, cp, len); + buf[len] = '\0'; + if (tor_inet_aton(buf, &inaddr) == 0) + return -1; /* malformed. */ + + if (in) { + uint32_t a; + /* reverse the bytes */ + a = ( ((inaddr.s_addr & 0x000000fful) << 24) + |((inaddr.s_addr & 0x0000ff00ul) << 8) + |((inaddr.s_addr & 0x00ff0000ul) >> 8) + |((inaddr.s_addr & 0xff000000ul) >> 24)); + inaddr.s_addr = a; + + memcpy(in, &inaddr, sizeof(inaddr)); + } + + return 1; +} + /** See if we have a cache entry for exitconn-\>address. if so, * if resolve valid, put it into exitconn-\>addr and return 1. * If resolve failed, unlink exitconn if needed, free it, and return -1. @@ -431,20 +508,23 @@ dns_resolve(edge_connection_t *exitconn) cached_resolve_t *resolve; cached_resolve_t search; pending_connection_t *pending_connection; - struct in_addr in; circuit_t *circ; + struct in_addr in; time_t now = time(NULL); + int is_reverse = 0, is_resolve, r; assert_connection_ok(TO_CONN(exitconn), 0); tor_assert(exitconn->_base.s == -1); assert_cache_ok(); + is_resolve = exitconn->_base.purpose = EXIT_PURPOSE_RESOLVE; + /* first check if exitconn->_base.address is an IP. If so, we already * know the answer. */ if (tor_inet_aton(exitconn->_base.address, &in) != 0) { exitconn->_base.addr = ntohl(in.s_addr); exitconn->address_ttl = DEFAULT_DNS_TTL; - if (exitconn->_base.purpose == EXIT_PURPOSE_RESOLVE) + if (is_resolve) send_resolved_cell(exitconn, RESOLVED_TYPE_IPV4); return 1; } @@ -456,6 +536,42 @@ dns_resolve(edge_connection_t *exitconn) /* lower-case exitconn->_base.address, so it's in canonical form */ tor_strlower(exitconn->_base.address); + /* Check whether this is a reverse lookup. If it's malformed, or it's a + * .in-addr.arpa address but this isn't a resolve request, kill the + * connecction. + */ + if ((r = parse_inaddr_arpa_address(exitconn->_base.address, NULL)) != 0) { + if (r == 1) + is_reverse = 1; + +#ifdef USE_EVENTDNS + if (!is_reverse || !is_resolve) { + if (!is_reverse) + log_info(LD_EXIT, "Bad .in-addr.arpa address \"%s\"; sending error.", + escaped_safe_str(exitconn->_base.address)); + else if (!is_resolve) + log_info(LD_EXIT, + "Attempt to connect to a .in-addr.arpa address \"%s\"; " + "sending error.", + escaped_safe_str(exitconn->_base.address)); +#else + if (1) { + log_info(LD_PROTOCOL, "Dnsworker code does not support in-addr.arpa " + "domain, but received a request for \"%s\"; sending error.", + escaped_safe_str(exitconn->_base.address)); +#endif + + if (exitconn->_base.purpose == EXIT_PURPOSE_RESOLVE) + send_resolved_cell(exitconn, RESOLVED_TYPE_ERROR); + circ = circuit_get_by_edge_conn(exitconn); + if (circ) + circuit_detach_stream(circ, exitconn); + if (!exitconn->_base.marked_for_close) + connection_free(TO_CONN(exitconn)); + return -1; + } + } + /* now check the hash table to see if 'address' is already there. */ strlcpy(search.address, exitconn->_base.address, sizeof(search.address)); resolve = HT_FIND(cache_map, &cache_root, &search); @@ -474,19 +590,24 @@ dns_resolve(edge_connection_t *exitconn) exitconn->_base.state = EXIT_CONN_STATE_RESOLVING; return 0; case CACHE_STATE_CACHED_VALID: - exitconn->_base.addr = resolve->addr; - exitconn->address_ttl = resolve->ttl; log_debug(LD_EXIT,"Connection (fd %d) found cached answer for %s", exitconn->_base.s, - escaped_safe_str(exitconn->_base.address)); - if (exitconn->_base.purpose == EXIT_PURPOSE_RESOLVE) - send_resolved_cell(exitconn, RESOLVED_TYPE_IPV4); + escaped_safe_str(resolve->address)); + exitconn->address_ttl = resolve->ttl; + if (resolve->is_reverse) { + tor_assert(is_resolve); + send_resolved_hostname_cell(exitconn, resolve->result.hostname); + } else { + exitconn->_base.addr = resolve->result.addr; + if (is_resolve) + send_resolved_cell(exitconn, RESOLVED_TYPE_IPV4); + } return 1; case CACHE_STATE_CACHED_FAILED: log_debug(LD_EXIT,"Connection (fd %d) found cached error for %s", exitconn->_base.s, escaped_safe_str(exitconn->_base.address)); - if (exitconn->_base.purpose == EXIT_PURPOSE_RESOLVE) + if (is_resolve) send_resolved_cell(exitconn, RESOLVED_TYPE_ERROR); circ = circuit_get_by_edge_conn(exitconn); if (circ) @@ -504,6 +625,7 @@ dns_resolve(edge_connection_t *exitconn) resolve = tor_malloc_zero(sizeof(cached_resolve_t)); resolve->magic = CACHED_RESOLVE_MAGIC; resolve->state = CACHE_STATE_PENDING; + resolve->is_reverse = is_reverse; strlcpy(resolve->address, exitconn->_base.address, sizeof(resolve->address)); /* add this connection to the pending list */ @@ -612,7 +734,7 @@ connection_dns_remove(edge_connection_t *conn) * address from the cache. */ void -dns_cancel_pending_resolve(char *address) +dns_cancel_pending_resolve(const char *address) { pending_connection_t *pend; cached_resolve_t search; @@ -675,10 +797,13 @@ dns_cancel_pending_resolve(char *address) /** Helper: adds an entry to the DNS cache mapping address to the ipv4 * address addr. ttl is a cache ttl; outcome is one of - * DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}. */ + * DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}. + * + * DOCDOC args + **/ static void -add_answer_to_cache(const char *address, uint32_t addr, char outcome, - uint32_t ttl) +add_answer_to_cache(const char *address, int is_reverse, uint32_t addr, + const char *hostname, char outcome, uint32_t ttl) { cached_resolve_t *resolve; if (outcome == DNS_RESOLVE_FAILED_TRANSIENT) @@ -689,7 +814,13 @@ add_answer_to_cache(const char *address, uint32_t addr, char outcome, resolve->state = (outcome == DNS_RESOLVE_SUCCEEDED) ? CACHE_STATE_CACHED_VALID : CACHE_STATE_CACHED_FAILED; strlcpy(resolve->address, address, sizeof(resolve->address)); - resolve->addr = addr; + if (is_reverse) { + tor_assert(hostname); + resolve->result.hostname = tor_strdup(hostname); + } else { + tor_assert(!hostname); + resolve->result.addr = addr; + } resolve->ttl = ttl; assert_resolve_ok(resolve); HT_INSERT(cache_map, &cache_root, resolve); @@ -704,8 +835,8 @@ add_answer_to_cache(const char *address, uint32_t addr, char outcome, * DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}. */ static void -dns_found_answer(const char *address, uint32_t addr, char outcome, - uint32_t ttl) +dns_found_answer(const char *address, int is_reverse, uint32_t addr, + const char *hostname, char outcome, uint32_t ttl) { pending_connection_t *pend; cached_resolve_t search; @@ -721,7 +852,7 @@ dns_found_answer(const char *address, uint32_t addr, char outcome, if (!resolve) { log_info(LD_EXIT,"Resolved unasked address %s; caching anyway.", escaped_safe_str(address)); - add_answer_to_cache(address, addr, outcome, ttl); + add_answer_to_cache(address, is_reverse, addr, hostname, outcome, ttl); return; } assert_resolve_ok(resolve); @@ -764,6 +895,7 @@ dns_found_answer(const char *address, uint32_t addr, char outcome, connection_free(TO_CONN(pendconn)); } else { if (pendconn->_base.purpose == EXIT_PURPOSE_CONNECT) { + tor_assert(!is_reverse); /* prevent double-remove. */ pend->conn->_base.state = EXIT_CONN_STATE_CONNECTING; @@ -782,7 +914,10 @@ dns_found_answer(const char *address, uint32_t addr, char outcome, /* prevent double-remove. This isn't really an accurate state, * but it does the right thing. */ pendconn->_base.state = EXIT_CONN_STATE_RESOLVEFAILED; - send_resolved_cell(pendconn, RESOLVED_TYPE_IPV4); + if (is_reverse) + send_resolved_hostname_cell(pendconn, hostname); + else + send_resolved_cell(pendconn, RESOLVED_TYPE_IPV4); circ = circuit_get_by_edge_conn(pendconn); tor_assert(circ); circuit_detach_stream(circ, pendconn); @@ -804,7 +939,7 @@ dns_found_answer(const char *address, uint32_t addr, char outcome, assert_resolve_ok(resolve); assert_cache_ok(); - add_answer_to_cache(address, addr, outcome, ttl); + add_answer_to_cache(address, is_reverse, addr, hostname, outcome, ttl); assert_cache_ok(); } @@ -942,7 +1077,7 @@ connection_dns_process_inbuf(connection_t *conn) tor_assert(success <= DNS_RESOLVE_SUCCEEDED); ttl = (success == DNS_RESOLVE_FAILED_TRANSIENT) ? 0 : MAX_DNS_ENTRY_AGE; - dns_found_answer(conn->address, ntohl(addr), success, ttl); + dns_found_answer(conn->address, 0, ntohl(addr), NULL, success, ttl); tor_free(conn->address); conn->address = tor_strdup(""); @@ -1320,8 +1455,11 @@ eventdns_callback(int result, char type, int count, int ttl, void *addresses, void *arg) { char *string_address = arg; + int is_reverse = 0; int status = DNS_RESOLVE_FAILED_PERMANENT; uint32_t addr = 0; + const char *hostname = NULL; + if (result == DNS_ERR_NONE) { if (type == DNS_IPv4_A && count) { char answer_buf[INET_NTOA_BUF_LEN+1]; @@ -1333,7 +1471,13 @@ eventdns_callback(int result, char type, int count, int ttl, void *addresses, tor_inet_ntoa(&in, answer_buf, sizeof(answer_buf)); log_debug(LD_EXIT, "eventdns said that %s resolves to %s", escaped_safe_str(string_address), - escaped_safe_str(answer_buf)); + escaped_safe_str(answer_buf)); // XXXX not ok. + } else if (type == DNS_PTR && count) { + is_reverse = 1; + hostname = ((char**)addresses)[0]; + log_debug(LD_EXIT, "eventdns said that %s resolves to %s", + escaped_safe_str(string_address), + escaped_safe_str(hostname)); // XXXX not ok. } else if (count) { log_warn(LD_EXIT, "eventdns returned only non-IPv4 answers for %s.", escaped_safe_str(string_address)); @@ -1345,7 +1489,7 @@ eventdns_callback(int result, char type, int count, int ttl, void *addresses, if (eventdns_err_is_transient(result)) status = DNS_RESOLVE_FAILED_TRANSIENT; } - dns_found_answer(string_address, addr, status, ttl); + dns_found_answer(string_address, is_reverse, addr, hostname, status, ttl); tor_free(string_address); } @@ -1355,6 +1499,7 @@ static int launch_resolve(edge_connection_t *exitconn) { char *addr = tor_strdup(exitconn->_base.address); + struct in_addr in; int r; int options = get_options()->SearchDomains ? 0 : DNS_QUERY_NO_SEARCH; /* What? Nameservers not configured? Sounds like a bug. */ @@ -1364,10 +1509,22 @@ launch_resolve(edge_connection_t *exitconn) if (configure_nameservers(1) < 0) return -1; } - log_info(LD_EXIT, "Launching eventdns request for %s", - escaped_safe_str(exitconn->_base.address)); - r = eventdns_resolve_ipv4(exitconn->_base.address, options, - eventdns_callback, addr); + + r = parse_inaddr_arpa_address(exitconn->_base.address, &in); + if (r == 0) { + log_info(LD_EXIT, "Launching eventdns request for %s", + escaped_safe_str(exitconn->_base.address)); + r = eventdns_resolve_ipv4(exitconn->_base.address, options, + eventdns_callback, addr); + } else if (r == 1) { + log_info(LD_EXIT, "Launching eventdns reverse request for %s", + escaped_safe_str(exitconn->_base.address)); + r = eventdns_resolve_reverse(&in, DNS_QUERY_NO_SEARCH, + eventdns_callback, addr); + } else if (r == -1) { + log_warn(LD_BUG, "Somehow a malformed in-addr.arpa address reached here."); + } + if (r) { log_warn(LD_EXIT, "eventdns rejected address %s: error %d.", escaped_safe_str(addr), r); @@ -1400,7 +1557,10 @@ assert_resolve_ok(cached_resolve_t *resolve) if (resolve->state == CACHE_STATE_PENDING || resolve->state == CACHE_STATE_DONE) { tor_assert(!resolve->ttl); - tor_assert(!resolve->addr); + if (resolve->is_reverse) + tor_assert(!resolve->result.hostname); + else + tor_assert(!resolve->result.addr); } } diff --git a/src/or/or.h b/src/or/or.h index 09d9778ac1..a24e8762f2 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -900,6 +900,8 @@ typedef struct { char *contact_info; /**< Declared contact info for this router. */ unsigned int is_hibernating:1; /**< Whether the router claims to be * hibernating */ + unsigned int has_old_dnsworkers:1; /**< Whether the router is using + * dnsworker code. */ /* local info */ unsigned int is_running:1; /**< As far as we know, is this OR currently @@ -2154,7 +2156,7 @@ void dns_reset(void); void connection_dns_remove(edge_connection_t *conn); void assert_connection_edge_not_dns_pending(edge_connection_t *conn); void assert_all_pending_dns_resolves_ok(void); -void dns_cancel_pending_resolve(char *question); +void dns_cancel_pending_resolve(const char *question); int dns_resolve(edge_connection_t *exitconn); /********************************* hibernate.c **********************/ diff --git a/src/or/router.c b/src/or/router.c index 73de2ddc2b..65f34c19c7 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1149,7 +1149,13 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, "uptime %ld\n" "bandwidth %d %d %d\n" "onion-key\n%s" - "signing-key\n%s%s%s%s", + "signing-key\n" +#ifdef USE_EVENTDNS + "opt eventdns 1\n" +#else + "opt eventdns 0\n" +#endif + "%s%s%s%s", router->nickname, router->address, router->or_port, @@ -1228,6 +1234,7 @@ router_dump_router_to_string(char *s, size_t maxlen, routerinfo_t *router, written += result; } } /* end for */ + if (written+256 > maxlen) /* Not enough room for signature. */ return -1; diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 12ff72d38d..2f214a1ef4 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -54,6 +54,7 @@ typedef enum { K_SERVER_VERSIONS, K_R, K_S, + K_EVENTDNS, _UNRECOGNIZED, _ERR, _EOF, @@ -145,6 +146,7 @@ static struct { { "dir-options", K_DIR_OPTIONS, ARGS, NO_OBJ, NETSTATUS }, { "client-versions", K_CLIENT_VERSIONS, ARGS, NO_OBJ, NETSTATUS }, { "server-versions", K_SERVER_VERSIONS, ARGS, NO_OBJ, NETSTATUS }, + { "eventdns", K_EVENTDNS, ARGS, NO_OBJ, RTR }, { NULL, -1, NO_ARGS, NO_OBJ, ANY } }; @@ -876,6 +878,13 @@ router_parse_entry_from_string(const char *s, const char *end, router->contact_info = tor_strdup(tok->args[0]); } + if ((tok = find_first_by_keyword(tokens, K_EVENTDNS))) { + router->has_old_dnsworkers = tok->n_args && !strcmp(tok->args[0], "0"); + } else if (router->platform) { + if (! tor_version_as_new_as(router->platform, "0.1.2.2-alpha")) + router->has_old_dnsworkers = 1; + } + exit_policy_tokens = find_all_exitpolicy(tokens); SMARTLIST_FOREACH(exit_policy_tokens, directory_token_t *, t, if (router_add_exit_policy(router,t)<0) {