diff options
-rw-r--r-- | changes/bug14193 | 4 | ||||
-rw-r--r-- | changes/bug14259 | 6 | ||||
-rw-r--r-- | changes/bug7555 | 5 | ||||
-rw-r--r-- | src/or/addressmap.c | 42 | ||||
-rw-r--r-- | src/or/addressmap.h | 7 | ||||
-rw-r--r-- | src/or/connection_edge.c | 352 | ||||
-rw-r--r-- | src/or/connection_edge.h | 25 | ||||
-rw-r--r-- | src/test/include.am | 1 | ||||
-rw-r--r-- | src/test/test.c | 2 | ||||
-rw-r--r-- | src/test/test_config.c | 3 | ||||
-rw-r--r-- | src/test/test_entryconn.c | 770 |
11 files changed, 1118 insertions, 99 deletions
diff --git a/changes/bug14193 b/changes/bug14193 new file mode 100644 index 0000000000..a7006685f5 --- /dev/null +++ b/changes/bug14193 @@ -0,0 +1,4 @@ + o Minor bugfixes (client DNS): + - Report the correct cached DNS expiration times. Previously, we + would report everything as "never expires." Fixes bug 14193; + bugfix on 0.2.3.17-beta. diff --git a/changes/bug14259 b/changes/bug14259 new file mode 100644 index 0000000000..1b5b9b80b3 --- /dev/null +++ b/changes/bug14259 @@ -0,0 +1,6 @@ + o Minor bugfixes (client): + - Avoid a small memory leak when we find a cached answer for a reverse + DNS lookup in a client-side DNS cache. (Remember, client-side DNS + caching is off by default, and is not recommended.) Fixes bug 14259; + bugfix on 0.2.0.1-alpha. + diff --git a/changes/bug7555 b/changes/bug7555 new file mode 100644 index 0000000000..a43ff739cb --- /dev/null +++ b/changes/bug7555 @@ -0,0 +1,5 @@ + o Major bugfixes (client): + - Allow MapAddress and AutomapHostsOnResolve to work together when an + address is mapped into another address type that must be + automapped at resolve time. Fixes bug 7555; bugfix on + 0.2.0.1-alpha. diff --git a/src/or/addressmap.c b/src/or/addressmap.c index 40e975fd3e..9c29fb2acb 100644 --- a/src/or/addressmap.c +++ b/src/or/addressmap.c @@ -390,13 +390,35 @@ addressmap_rewrite(char *address, size_t maxlen, goto done; } - if (ent && ent->source == ADDRMAPSRC_DNS) { - sa_family_t f; - tor_addr_t tmp; - f = tor_addr_parse(&tmp, ent->new_address); - if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS)) - goto done; - else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) + switch (ent->source) { + case ADDRMAPSRC_DNS: + { + sa_family_t f; + tor_addr_t tmp; + f = tor_addr_parse(&tmp, ent->new_address); + if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS)) + goto done; + else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) + goto done; + } + break; + case ADDRMAPSRC_CONTROLLER: + case ADDRMAPSRC_TORRC: + if (!(flags & AMR_FLAG_USE_MAPADDRESS)) + goto done; + break; + case ADDRMAPSRC_AUTOMAP: + if (!(flags & AMR_FLAG_USE_AUTOMAP)) + goto done; + break; + case ADDRMAPSRC_TRACKEXIT: + if (!(flags & AMR_FLAG_USE_TRACKEXIT)) + goto done; + break; + case ADDRMAPSRC_NONE: + default: + log_warn(LD_BUG, "Unknown addrmap source value %d. Ignoring it.", + (int) ent->source); goto done; } @@ -431,7 +453,7 @@ addressmap_rewrite(char *address, size_t maxlen, if (exit_source_out) *exit_source_out = exit_source; if (expires_out) - *expires_out = TIME_MAX; + *expires_out = expires; return (rewrites > 0); } @@ -455,6 +477,8 @@ addressmap_rewrite_reverse(char *address, size_t maxlen, unsigned flags, return 0; else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS)) return 0; + /* FFFF we should reverse-map virtual addresses even if we haven't + * enabled DNS cacheing. */ } tor_asprintf(&s, "REVERSE[%s]", address); @@ -981,6 +1005,8 @@ addressmap_register_virtual_address(int type, char *new_address) strmap_set(virtaddress_reversemap, new_address, vent); addressmap_register(*addrp, new_address, 2, ADDRMAPSRC_AUTOMAP, 0, 0); + /* FFFF register corresponding reverse mapping. */ + #if 0 { /* Try to catch possible bugs */ diff --git a/src/or/addressmap.h b/src/or/addressmap.h index bb737e47f4..ff108df024 100644 --- a/src/or/addressmap.h +++ b/src/or/addressmap.h @@ -16,8 +16,11 @@ void addressmap_clean(time_t now); void addressmap_clear_configured(void); void addressmap_clear_transient(void); void addressmap_free_all(void); -#define AMR_FLAG_USE_IPV4_DNS (1u<<0) -#define AMR_FLAG_USE_IPV6_DNS (1u<<1) +#define AMR_FLAG_USE_IPV4_DNS (1u<<0) +#define AMR_FLAG_USE_IPV6_DNS (1u<<1) +#define AMR_FLAG_USE_MAPADDRESS (1u<<2) +#define AMR_FLAG_USE_AUTOMAP (1u<<3) +#define AMR_FLAG_USE_TRACKEXIT (1u<<4) int addressmap_rewrite(char *address, size_t maxlen, unsigned flags, time_t *expires_out, addressmap_entry_source_t *exit_source_out); diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 33c42574f0..13053a3847 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -908,64 +908,83 @@ connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn, return connection_ap_handshake_rewrite_and_attach(conn, circ, cpath); } -/** Connection <b>conn</b> just finished its socks handshake, or the - * controller asked us to take care of it. If <b>circ</b> is defined, - * then that's where we'll want to attach it. Otherwise we have to - * figure it out ourselves. - * - * First, parse whether it's a .exit address, remap it, and so on. Then - * if it's for a general circuit, try to attach it to a circuit (or launch - * one as needed), else if it's for a rendezvous circuit, fetch a - * rendezvous descriptor first (or attach/launch a circuit if the - * rendezvous descriptor is already here and fresh enough). - * - * The stream will exit from the hop - * indicated by <b>cpath</b>, or from the last hop in circ's cpath if - * <b>cpath</b> is NULL. +/* Try to perform any map-based rewriting of the target address in + * <b>conn</b>, filling in the fields of <b>out</b> as we go, and modifying + * conn->socks_request.address as appropriate. */ -int -connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, - origin_circuit_t *circ, - crypt_path_t *cpath) +STATIC void +connection_ap_handshake_rewrite(entry_connection_t *conn, + rewrite_result_t *out) { socks_request_t *socks = conn->socks_request; - hostname_type_t addresstype; const or_options_t *options = get_options(); tor_addr_t addr_tmp; - /* We set this to true if this is an address we should automatically - * remap to a local address in VirtualAddrNetwork */ - int automap = 0; - char orig_address[MAX_SOCKS_ADDR_LEN]; - time_t map_expires = TIME_MAX; - time_t now = time(NULL); - connection_t *base_conn = ENTRY_TO_CONN(conn); - addressmap_entry_source_t exit_source = ADDRMAPSRC_NONE; - tor_strlower(socks->address); /* normalize it */ - strlcpy(orig_address, socks->address, sizeof(orig_address)); + /* Initialize all the fields of 'out' to reasonable defaults */ + out->automap = 0; + out->exit_source = ADDRMAPSRC_NONE; + out->map_expires = TIME_MAX; + out->end_reason = 0; + out->should_close = 0; + out->orig_address[0] = 0; + + /* We convert all incoming addresses to lowercase. */ + tor_strlower(socks->address); + /* Remember the original address. */ + strlcpy(out->orig_address, socks->address, sizeof(out->orig_address)); log_debug(LD_APP,"Client asked for %s:%d", safe_str_client(socks->address), socks->port); + /* Check for whether this is a .exit address. By default, those are + * disallowed when they're coming straight from the client, but you're + * allowed to have them in MapAddress commands and so forth. */ if (!strcmpend(socks->address, ".exit") && !options->AllowDotExit) { log_warn(LD_APP, "The \".exit\" notation is disabled in Tor due to " "security risks. Set AllowDotExit in your torrc to enable " "it (at your own risk)."); control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); - return -1; + out->end_reason = END_STREAM_REASON_TORPROTOCOL; + out->should_close = 1; + return; } - if (! conn->original_dest_address) + /* Remember the original address so we can tell the user about what + * they actually said, not just what it turned into. */ + if (! conn->original_dest_address) { + /* Is the 'if' necessary here? XXXX */ conn->original_dest_address = tor_strdup(conn->socks_request->address); + } + + /* First, apply MapAddress and MAPADDRESS mappings. We need to do + * these only for non-reverse lookups, since they don't exist for those. + * We need to do this before we consider automapping, since we might + * e.g. resolve irc.oftc.net into irconionaddress.onion, at which point + * we'd need to automap it. */ + if (socks->command != SOCKS_COMMAND_RESOLVE_PTR) { + const unsigned rewrite_flags = AMR_FLAG_USE_MAPADDRESS; + if (addressmap_rewrite(socks->address, sizeof(socks->address), + rewrite_flags, &out->map_expires, &out->exit_source)) { + control_event_stream_status(conn, STREAM_EVENT_REMAP, + REMAP_STREAM_SOURCE_CACHE); + } + } + /* Now, handle automapping. Automapping happens when we're asked to + * resolve a hostname, and AutomapHostsOnResolve is set, and + * the hostname has a suffix listed in AutomapHostsSuffixes. + */ if (socks->command == SOCKS_COMMAND_RESOLVE && tor_addr_parse(&addr_tmp, socks->address)<0 && options->AutomapHostsOnResolve) { - automap = addressmap_address_should_automap(socks->address, options); - if (automap) { + /* Check the suffix... */ + out->automap = addressmap_address_should_automap(socks->address, options); + if (out->automap) { + /* If we get here, then we should apply an automapping for this. */ const char *new_addr; + /* We return an IPv4 address by default, or an IPv6 address if we + * are allowed to do so. */ int addr_type = RESOLVED_TYPE_IPV4; if (conn->socks_request->socks_version != 4) { if (!conn->entry_cfg.ipv4_traffic || @@ -973,13 +992,18 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, conn->entry_cfg.prefer_ipv6_virtaddr) addr_type = RESOLVED_TYPE_IPV6; } + /* Okay, register the target address as automapped, and find the new + * address we're supposed to give as a resolve answer. (Return a cached + * value if we've looked up this address before. + */ new_addr = addressmap_register_virtual_address( addr_type, tor_strdup(socks->address)); if (! new_addr) { log_warn(LD_APP, "Unable to automap address %s", escaped_safe_str(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_INTERNAL); - return -1; + out->end_reason = END_STREAM_REASON_INTERNAL; + out->should_close = 1; + return; } log_info(LD_APP, "Automapping %s to %s", escaped_safe_str_client(socks->address), @@ -988,6 +1012,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } + /* Now handle reverse lookups, if they're in the cache. This doesn't + * happen too often, since client-side DNS caching is off by default. */ if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) { unsigned rewrite_flags = 0; if (conn->entry_cfg.use_cached_ipv4_answers) @@ -996,20 +1022,25 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, rewrite_flags |= AMR_FLAG_USE_IPV6_DNS; if (addressmap_rewrite_reverse(socks->address, sizeof(socks->address), - rewrite_flags, &map_expires)) { + rewrite_flags, &out->map_expires)) { char *result = tor_strdup(socks->address); /* remember _what_ is supposed to have been resolved. */ tor_snprintf(socks->address, sizeof(socks->address), "REVERSE[%s]", - orig_address); + out->orig_address); connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_HOSTNAME, strlen(result), (uint8_t*)result, -1, - map_expires); - connection_mark_unattached_ap(conn, - END_STREAM_REASON_DONE | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return 0; + out->map_expires); + tor_free(result); + out->end_reason = END_STREAM_REASON_DONE | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED; + out->should_close = 1; + return; } + + /* Hang on, did we find an answer saying that this is a reverse lookup for + * an internal address? If so, we should reject it if we're condigured to + * do so. */ if (options->ClientDNSRejectInternalAddresses) { /* Don't let people try to do a reverse lookup on 10.0.0.1. */ tor_addr_t addr; @@ -1019,43 +1050,108 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (ok == 1 && tor_addr_is_internal(&addr, 0)) { connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_ERROR, 0, NULL, -1, TIME_MAX); - connection_mark_unattached_ap(conn, - END_STREAM_REASON_SOCKSPROTOCOL | - END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); - return -1; + out->end_reason = END_STREAM_REASON_SOCKSPROTOCOL | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED; + out->should_close = 1; + return; } } - } else if (!automap) { - /* For address map controls, remap the address. */ - unsigned rewrite_flags = 0; + } + + /* If we didn't automap it before, then this is still the address + * that came straight from the user, mapped according to any + * MapAddress/MAPADDRESS commands. Now other mappings, including + * previously registered Automap entries, TrackHostExits entries, + * and client-side DNS cache entries (not recommended). + */ + if (!socks->command != SOCKS_COMMAND_RESOLVE_PTR && + !out->automap) { + unsigned rewrite_flags = AMR_FLAG_USE_AUTOMAP | AMR_FLAG_USE_TRACKEXIT; + addressmap_entry_source_t exit_source2; if (conn->entry_cfg.use_cached_ipv4_answers) rewrite_flags |= AMR_FLAG_USE_IPV4_DNS; if (conn->entry_cfg.use_cached_ipv6_answers) rewrite_flags |= AMR_FLAG_USE_IPV6_DNS; if (addressmap_rewrite(socks->address, sizeof(socks->address), - rewrite_flags, &map_expires, &exit_source)) { + rewrite_flags, &out->map_expires, &exit_source2)) { control_event_stream_status(conn, STREAM_EVENT_REMAP, REMAP_STREAM_SOURCE_CACHE); } + if (out->exit_source == ADDRMAPSRC_NONE) { + /* If it wasn't a .exit before, maybe it turned into a .exit. Remember + * the original source of a .exit. */ + out->exit_source = exit_source2; + } } - if (!automap && address_is_in_virtual_range(socks->address)) { - /* This address was probably handed out by client_dns_get_unmapped_address, - * but the mapping was discarded for some reason. We *don't* want to send - * the address through Tor; that's likely to fail, and may leak - * information. + /* Check to see whether we're about to use an address in the virtual + * range without actually having gotten it from an Automap. */ + if (!out->automap && address_is_in_virtual_range(socks->address)) { + /* This address was probably handed out by + * client_dns_get_unmapped_address, but the mapping was discarded for some + * reason. Or the user typed in a virtual address range manually. We + * *don't* want to send the address through Tor; that's likely to fail, + * and may leak information. */ log_warn(LD_APP,"Missing mapping for virtual address '%s'. Refusing.", safe_str_client(socks->address)); - connection_mark_unattached_ap(conn, END_STREAM_REASON_INTERNAL); - return -1; + out->end_reason = END_STREAM_REASON_INTERNAL; + out->should_close = 1; + return; + } +} + +/** Connection <b>conn</b> just finished its socks handshake, or the + * controller asked us to take care of it. If <b>circ</b> is defined, + * then that's where we'll want to attach it. Otherwise we have to + * figure it out ourselves. + * + * First, parse whether it's a .exit address, remap it, and so on. Then + * if it's for a general circuit, try to attach it to a circuit (or launch + * one as needed), else if it's for a rendezvous circuit, fetch a + * rendezvous descriptor first (or attach/launch a circuit if the + * rendezvous descriptor is already here and fresh enough). + * + * The stream will exit from the hop + * indicated by <b>cpath</b>, or from the last hop in circ's cpath if + * <b>cpath</b> is NULL. + */ +int +connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, + origin_circuit_t *circ, + crypt_path_t *cpath) +{ + socks_request_t *socks = conn->socks_request; + const or_options_t *options = get_options(); + connection_t *base_conn = ENTRY_TO_CONN(conn); + time_t now = time(NULL); + rewrite_result_t rr; + + memset(&rr, 0, sizeof(rr)); + connection_ap_handshake_rewrite(conn,&rr); + + if (rr.should_close) { + /* connection_ap_handshake_rewrite told us to close the connection, + * either because it sent back an answer, or because it sent back an + * error */ + connection_mark_unattached_ap(conn, rr.end_reason); + if (END_STREAM_REASON_DONE == (rr.end_reason & END_STREAM_REASON_MASK)) + return 0; + else + return -1; } + const time_t map_expires = rr.map_expires; + const int automap = rr.automap; + const addressmap_entry_source_t exit_source = rr.exit_source; + /* Parse the address provided by SOCKS. Modify it in-place if it * specifies a hidden-service (.onion) or particular exit node (.exit). */ - addresstype = parse_extended_hostname(socks->address); + const hostname_type_t addresstype = parse_extended_hostname(socks->address); + /* Now see whether the hostname is bogus. This could happen because of an + * onion hostname whose format we don't recognize. */ if (addresstype == BAD_HOSTNAME) { control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); @@ -1063,16 +1159,21 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* If this is a .exit hostname, strip off the .name.exit part, and + * see whether we're going to connect there, and otherwise handle it. + * (The ".exit" part got stripped off by "parse_extended_hostname"). + * + * We'll set chosen_exit_name and/or close the connection as appropriate. + */ if (addresstype == EXIT_HOSTNAME) { - /* foo.exit -- modify conn->chosen_exit_node to specify the exit - * node, and conn->address to hold only the address portion. */ - char *s = strrchr(socks->address,'.'); - - /* If StrictNodes is not set, then .exit overrides ExcludeNodes. */ + /* If StrictNodes is not set, then .exit overrides ExcludeNodes but + * not ExcludeExitNodes. */ routerset_t *excludeset = options->StrictNodes ? options->ExcludeExitNodesUnion_ : options->ExcludeExitNodes; - const node_t *node; + const node_t *node = NULL; + /* If this .exit was added by an AUTOMAP, then it came straight from + * a user. Make sure that options->AllowDotExit permits that. */ if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) { /* Whoops; this one is stale. It must have gotten added earlier, * when AllowDotExit was on. */ @@ -1085,6 +1186,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* Double-check to make sure there are no .exits coming from + * impossible/weird sources. */ if (exit_source == ADDRMAPSRC_DNS || (exit_source == ADDRMAPSRC_NONE && !options->AllowDotExit)) { /* It shouldn't be possible to get a .exit address from any of these @@ -1099,9 +1202,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } tor_assert(!automap); + /* Now, find the character before the .(name) part. */ + char *s = strrchr(socks->address,'.'); if (s) { /* The address was of the form "(stuff).(name).exit */ if (s[1] != '\0') { + /* Looks like a real .exit one. */ conn->chosen_exit_name = tor_strdup(s+1); node = node_get_by_nickname(conn->chosen_exit_name, 1); @@ -1120,7 +1226,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } } else { - /* It looks like they just asked for "foo.exit". */ + /* It looks like they just asked for "foo.exit". That's a special + * form that means (foo's address).foo.exit. */ conn->chosen_exit_name = tor_strdup(socks->address); node = node_get_by_nickname(conn->chosen_exit_name, 1); @@ -1129,6 +1236,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, node_get_address_string(node, socks->address, sizeof(socks->address)); } } + /* Now make sure that the chosen exit exists... */ if (!node) { log_warn(LD_APP, @@ -1150,8 +1258,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, implies no. */ } + /* Now, handle everything that isn't a .onion address. */ if (addresstype != ONION_HOSTNAME) { - /* not a hidden-service request (i.e. normal or .exit) */ + /* Not a hidden-service request. It's either a hostname or an IP, + * possibly with a .exit that we stripped off. */ + + /* Check for funny characters in the address. */ if (address_is_invalid_destination(socks->address, 1)) { control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s", escaped(socks->address)); @@ -1162,6 +1274,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* If we're running in Tor2webMode, we don't allow anything BUT .onion + * addresses. */ if (options->Tor2webMode) { log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname %s " "because tor2web mode is enabled.", @@ -1170,12 +1284,15 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* See if this is a hostname lookup that we can answer immediately. + * (For example, an attempt to look up the IP address for an IP address.) + */ if (socks->command == SOCKS_COMMAND_RESOLVE) { tor_addr_t answer; /* Reply to resolves immediately if we can. */ if (tor_addr_parse(&answer, socks->address) >= 0) {/* is it an IP? */ /* remember _what_ is supposed to have been resolved. */ - strlcpy(socks->address, orig_address, sizeof(socks->address)); + strlcpy(socks->address, rr.orig_address, sizeof(socks->address)); connection_ap_handshake_socks_resolved_addr(conn, &answer, -1, map_expires); connection_mark_unattached_ap(conn, @@ -1186,14 +1303,22 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, tor_assert(!automap); rep_hist_note_used_resolve(now); /* help predict this next time */ } else if (socks->command == SOCKS_COMMAND_CONNECT) { + /* Special handling for attempts to connect */ tor_assert(!automap); + /* Don't allow connections to port 0. */ if (socks->port == 0) { log_notice(LD_APP,"Application asked to connect to port 0. Refusing."); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); return -1; } + /* You can't make connections to internal addresses, by default. + * Exceptions are begindir requests (where the address is meaningless, + * or cases where you've hand-configured a particular exit, thereby + * making the local address meaningful. */ if (options->ClientRejectInternalAddresses && !conn->use_begindir && !conn->chosen_exit_name && !circ) { + /* If we reach this point then we don't want to allow internal + * addresses. Check if we got one. */ tor_addr_t addr; if (tor_addr_hostname_is_local(socks->address) || (tor_addr_parse(&addr, socks->address) >= 0 && @@ -1228,31 +1353,47 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, connection_mark_unattached_ap(conn, END_STREAM_REASON_PRIVATE_ADDR); return -1; } - } + } /* end "if we should check for internal addresses" */ + /* Okay. We're still doing a CONNECT, and it wasn't a private + * address. Do special handling for literal IP addresses */ { tor_addr_t addr; /* XXX Duplicate call to tor_addr_parse. */ if (tor_addr_parse(&addr, socks->address) >= 0) { + /* If we reach this point, it's an IPv4 or an IPv6 address. */ sa_family_t family = tor_addr_family(&addr); + + /* XXXX bug: the second one should be "ipv6_traffic" */ if ((family == AF_INET && ! conn->entry_cfg.ipv4_traffic) || (family == AF_INET6 && ! conn->entry_cfg.ipv4_traffic)) { + /* You can't do an IPv4 address on a v6-only socks listener, + * or vice versa. */ log_warn(LD_NET, "Rejecting SOCKS request for an IP address " "family that this listener does not support."); connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } else if (family == AF_INET6 && socks->socks_version == 4) { + /* You can't make a socks4 request to an IPv6 address. Socks4 + * doesn't support that. */ log_warn(LD_NET, "Rejecting SOCKS4 request for an IPv6 address."); connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } else if (socks->socks_version == 4 && !conn->entry_cfg.ipv4_traffic) { + /* You can't do any kind of Socks4 request when IPv4 is forbidden. + * + * XXX raise this check outside the enclosing block? */ log_warn(LD_NET, "Rejecting SOCKS4 request on a listener with " "no IPv4 traffic supported."); connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY); return -1; } else if (family == AF_INET6) { + /* Tell the exit: we won't accept any ipv4 connection to an IPv6 + * address. */ conn->entry_cfg.ipv4_traffic = 0; } else if (family == AF_INET) { + /* Tell the exit: we won't accept any ipv6 connection to an IPv4 + * address. */ conn->entry_cfg.ipv6_traffic = 0; } } @@ -1261,6 +1402,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, if (socks->socks_version == 4) conn->entry_cfg.ipv6_traffic = 0; + /* Still handling CONNECT. Now, check for exit enclaves. (Which we + * don't do on BEGINDIR, or there is a chosen exit.) + */ if (!conn->use_begindir && !conn->chosen_exit_name && !circ) { /* see if we can find a suitable enclave exit */ const node_t *r = @@ -1277,11 +1421,13 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } } - /* warn or reject if it's using a dangerous port */ + /* Still handling CONNECT: warn or reject if it's using a dangerous + * port. */ if (!conn->use_begindir && !conn->chosen_exit_name && !circ) if (consider_plaintext_ports(conn, socks->port) < 0) return -1; + /* Remember the port so that we do predicted requests there. */ if (!conn->use_begindir) { /* help predict this next time */ rep_hist_note_used_port(now, socks->port); @@ -1290,25 +1436,41 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, rep_hist_note_used_resolve(now); /* help predict this next time */ /* no extra processing needed */ } else { + /* We should only be doing CONNECT or RESOLVE! */ tor_fragile_assert(); } + + /* Okay. At this point we've set chosen_exit_name if needed, rewritten the + * address, and decided not to reject it for any number of reasons. Now + * mark the connection as waiting for a circuit, and try to attach it! + */ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; - if ((circ && connection_ap_handshake_attach_chosen_circuit( - conn, circ, cpath) < 0) || - (!circ && - connection_ap_handshake_attach_circuit(conn) < 0)) { + + /* If we were given a circuit to attach to, try to attach. Otherwise, + * try to find a good one and attach to that. */ + int rv; + if (circ) + rv = connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath); + else + rv = connection_ap_handshake_attach_circuit(conn); + + /* If the above function returned 0 then we're waiting for a circuit. + * if it returned 1, we're attached. Both are okay. But if it returned + * -1, there was an error, so make sure the connection is marked, and + * return -1. */ + if (rv < 0) { if (!base_conn->marked_for_close) connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH); return -1; } + return 0; } else { - /* it's a hidden-service request */ - rend_cache_entry_t *entry; - int r; - rend_service_authorization_t *client_auth; - rend_data_t *rend_data; + /* If we get here, it's a request for a .onion address! */ tor_assert(!automap); + + /* Check whether it's RESOLVE or RESOLVE_PTR. We don't handle those + * for hidden service addresses. */ if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) { /* if it's a resolve request, fail it right now, rather than * building all the circuits and then realizing it won't work. */ @@ -1322,6 +1484,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } + /* If we were passed a circuit, then we need to fail. .onion addresses + * only work when we launch our own circuits for now. */ if (circ) { log_warn(LD_CONTROL, "Attachstream to a circuit is not " "supported for .onion addresses currently. Failing."); @@ -1329,15 +1493,22 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, return -1; } - ENTRY_TO_EDGE_CONN(conn)->rend_data = rend_data = + /* Fill in the rend_data field so we can start doing a connection to + * a hidden service. */ + rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data = tor_malloc_zero(sizeof(rend_data_t)); strlcpy(rend_data->onion_address, socks->address, sizeof(rend_data->onion_address)); log_info(LD_REND,"Got a hidden service request for ID '%s'", safe_str_client(rend_data->onion_address)); - /* see if we already have it cached */ - r = rend_cache_lookup_entry(rend_data->onion_address, -1, &entry); - if (r<0) { + + /* see if we already have a hidden service descriptor cached for this + * address. */ + rend_cache_entry_t *entry = NULL; + const int rend_cache_lookup_result = + rend_cache_lookup_entry(rend_data->onion_address, -1, &entry); + if (rend_cache_lookup_result < 0) { + /* We should already have rejected this address! */ log_warn(LD_BUG,"Invalid service name '%s'", safe_str_client(rend_data->onion_address)); connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); @@ -1348,8 +1519,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, * a stable circuit yet, but we know we'll need *something*. */ rep_hist_note_used_internal(now, 0, 1); - /* Look up if we have client authorization for it. */ - client_auth = rend_client_lookup_service_authorization( + /* Look up if we have client authorization configured for this hidden + * service. If we do, associate it with the rend_data. */ + rend_service_authorization_t *client_auth = + rend_client_lookup_service_authorization( rend_data->onion_address); if (client_auth) { log_info(LD_REND, "Using previously configured client authorization " @@ -1358,12 +1531,16 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, client_auth->descriptor_cookie, REND_DESC_COOKIE_LEN); rend_data->auth_type = client_auth->auth_type; } - if (r==0) { + + /* Now, we either launch an attempt to connect to the hidden service, + * or we launch an attempt to look up its descriptor, depending on + * whether we had the descriptor. */ + if (rend_cache_lookup_result == 0) { base_conn->state = AP_CONN_STATE_RENDDESC_WAIT; log_info(LD_REND, "Unknown descriptor %s. Fetching.", safe_str_client(rend_data->onion_address)); rend_client_refetch_v2_renddesc(rend_data); - } else { /* r > 0 */ + } else { /* rend_cache_lookup_result > 0 */ base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT; log_info(LD_REND, "Descriptor is here. Great."); if (connection_ap_handshake_attach_circuit(conn) < 0) { @@ -1374,6 +1551,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn, } return 0; } + return 0; /* unreached but keeps the compiler happy */ } diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index e6adad91d8..0cc391ed82 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -143,6 +143,31 @@ STATIC int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell, STATIC int connected_cell_format_payload(uint8_t *payload_out, const tor_addr_t *addr, uint32_t ttl); + + +typedef struct { + /** Original address, after we lowercased it but before we started + * mapping it. + */ + char orig_address[MAX_SOCKS_ADDR_LEN]; + /** True iff the address has been automatically remapped to a local + * address in VirtualAddrNetwork. (Only set true when we do a resolve + * and get a virtual address; not when we connect to the address.) */ + int automap; + /** If this connection has a .exit address, who put it there? */ + addressmap_entry_source_t exit_source; + /** If we've rewritten the address, when does this map expire? */ + time_t map_expires; + /** If we should close the connection, this is the end_reason to pass + * to connection_mark_unattached_ap */ + int end_reason; + /** True iff we should close the connection, either because of error or + * because of successful early RESOLVED reply. */ + int should_close; +} rewrite_result_t; + +STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn, + rewrite_result_t *out); #endif #endif diff --git a/src/test/include.am b/src/test/include.am index b9b381fdae..134c2ff56c 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -31,6 +31,7 @@ src_test_test_SOURCES = \ src/test/test_data.c \ src/test/test_dir.c \ src/test/test_checkdir.c \ + src/test/test_entryconn.c \ src/test/test_entrynodes.c \ src/test/test_extorport.c \ src/test/test_introduce.c \ diff --git a/src/test/test.c b/src/test/test.c index de6efaf873..65d15f12bd 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1313,6 +1313,7 @@ extern struct testcase_t channel_tests[]; extern struct testcase_t channeltls_tests[]; extern struct testcase_t relay_tests[]; extern struct testcase_t scheduler_tests[]; +extern struct testcase_t entryconn_tests[]; static struct testgroup_t testgroups[] = { { "", test_array }, @@ -1337,6 +1338,7 @@ static struct testgroup_t testgroups[] = { { "circuitmux/", circuitmux_tests }, { "options/", options_tests }, { "entrynodes/", entrynodes_tests }, + { "entryconn/", entryconn_tests }, { "extorport/", extorport_tests }, { "control/", controller_event_tests }, { "hs/", hs_tests }, diff --git a/src/test/test_config.c b/src/test/test_config.c index fb8e4020dc..b1f5017b78 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -51,8 +51,7 @@ test_config_addressmap(void *arg) /* Use old interface for now, so we don't need to rewrite the unit tests */ #define addressmap_rewrite(a,s,eo,ao) \ - addressmap_rewrite((a),(s),AMR_FLAG_USE_IPV4_DNS|AMR_FLAG_USE_IPV6_DNS, \ - (eo),(ao)) + addressmap_rewrite((a),(s), ~0, (eo),(ao)) /* MapAddress .invalidwildcard.com .torserver.exit - no match */ strlcpy(address, "www.invalidwildcard.com", sizeof(address)); diff --git a/src/test/test_entryconn.c b/src/test/test_entryconn.c new file mode 100644 index 0000000000..146cddc331 --- /dev/null +++ b/src/test/test_entryconn.c @@ -0,0 +1,770 @@ +/* Copyright (c) 2014-2015, The Tor Project, Inc. */ +/* See LICENSE for licensing information */ + +#include "orconfig.h" + +#define CONNECTION_PRIVATE +#define CONNECTION_EDGE_PRIVATE + +#include "or.h" +#include "test.h" + +#include "addressmap.h" +#include "config.h" +#include "confparse.h" +#include "connection.h" +#include "connection_edge.h" + +static void * +entryconn_rewrite_setup(const struct testcase_t *tc) +{ + (void)tc; + entry_connection_t *ec = entry_connection_new(CONN_TYPE_AP, AF_INET); + addressmap_init(); + return ec; +} + +static int +entryconn_rewrite_teardown(const struct testcase_t *tc, void *arg) +{ + (void)tc; + entry_connection_t *ec = arg; + if (ec) + connection_free_(ENTRY_TO_CONN(ec)); + addressmap_free_all(); + return 1; +} + +static struct testcase_setup_t test_rewrite_setup = { + entryconn_rewrite_setup, entryconn_rewrite_teardown +}; + +/* Simple rewrite: no changes needed */ +static void +test_entryconn_rewrite_basic(void *arg) +{ + entry_connection_t *ec = arg; + rewrite_result_t rr; + + tt_assert(ec->socks_request); + strlcpy(ec->socks_request->address, "www.TORproject.org", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_int_op(rr.automap, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.torproject.org"); + tt_str_op(ec->socks_request->address, OP_EQ, "www.torproject.org"); + tt_str_op(ec->original_dest_address, OP_EQ, "www.torproject.org"); + + done: + ; +} + +/* Rewrite but reject because of disallowed .exit */ +static void +test_entryconn_rewrite_bad_dotexit(void *arg) +{ + entry_connection_t *ec = arg; + rewrite_result_t rr; + + get_options_mutable()->AllowDotExit = 0; + tt_assert(ec->socks_request); + strlcpy(ec->socks_request->address, "www.TORproject.org.foo.exit", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.should_close, OP_EQ, 1); + tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_TORPROTOCOL); + + done: + ; +} + +/* Automap on resolve, connect to automapped address, resolve again and get + * same answer. (IPv4) */ +static void +test_entryconn_rewrite_automap_ipv4(void *arg) +{ + entry_connection_t *ec = arg; + entry_connection_t *ec2=NULL, *ec3=NULL; + rewrite_result_t rr; + char *msg = NULL; + + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET); + ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET); + + get_options_mutable()->AutomapHostsOnResolve = 1; + smartlist_add(get_options_mutable()->AutomapHostsSuffixes, tor_strdup(".")); + parse_virtual_addr_network("127.202.0.0/16", AF_INET, 0, &msg); + + /* Automap this on resolve. */ + strlcpy(ec->socks_request->address, "WWW.MIT.EDU", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu"); + tt_str_op(ec->original_dest_address, OP_EQ, "www.mit.edu"); + + tt_assert(!strcmpstart(ec->socks_request->address,"127.202.")); + + /* Connect to it and make sure we get the original address back. */ + strlcpy(ec2->socks_request->address, ec->socks_request->address, + sizeof(ec2->socks_request->address)); + + ec2->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec2, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, ec->socks_request->address); + tt_str_op(ec2->original_dest_address, OP_EQ, ec->socks_request->address); + tt_str_op(ec2->socks_request->address, OP_EQ, "www.mit.edu"); + + /* Resolve it again, make sure the answer is the same. */ + strlcpy(ec3->socks_request->address, "www.MIT.EDU", + sizeof(ec3->socks_request->address)); + ec3->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec3, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu"); + tt_str_op(ec3->original_dest_address, OP_EQ, "www.mit.edu"); + + tt_str_op(ec3->socks_request->address, OP_EQ, + ec->socks_request->address); + + done: + connection_free_(ENTRY_TO_CONN(ec2)); + connection_free_(ENTRY_TO_CONN(ec3)); +} + +/* Automap on resolve, connect to automapped address, resolve again and get + * same answer. (IPv6) */ +static void +test_entryconn_rewrite_automap_ipv6(void *arg) +{ + (void)arg; + entry_connection_t *ec =NULL; + entry_connection_t *ec2=NULL, *ec3=NULL; + rewrite_result_t rr; + char *msg = NULL; + + ec = entry_connection_new(CONN_TYPE_AP, AF_INET6); + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET6); + ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET6); + + get_options_mutable()->AutomapHostsOnResolve = 1; + smartlist_add(get_options_mutable()->AutomapHostsSuffixes, tor_strdup(".")); + parse_virtual_addr_network("FE80::/32", AF_INET6, 0, &msg); + + /* Automap this on resolve. */ + strlcpy(ec->socks_request->address, "WWW.MIT.EDU", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu"); + tt_str_op(ec->original_dest_address, OP_EQ, "www.mit.edu"); + + /* Yes, this [ should be here. */ + tt_assert(!strcmpstart(ec->socks_request->address,"[fe80:")); + + /* Connect to it and make sure we get the original address back. */ + strlcpy(ec2->socks_request->address, ec->socks_request->address, + sizeof(ec2->socks_request->address)); + + ec2->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec2, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, ec->socks_request->address); + tt_str_op(ec2->original_dest_address, OP_EQ, ec->socks_request->address); + tt_str_op(ec2->socks_request->address, OP_EQ, "www.mit.edu"); + + /* Resolve it again, make sure the answer is the same. */ + strlcpy(ec3->socks_request->address, "www.MIT.EDU", + sizeof(ec3->socks_request->address)); + ec3->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec3, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu"); + tt_str_op(ec3->original_dest_address, OP_EQ, "www.mit.edu"); + + tt_str_op(ec3->socks_request->address, OP_EQ, + ec->socks_request->address); + + done: + connection_free_(ENTRY_TO_CONN(ec)); + connection_free_(ENTRY_TO_CONN(ec2)); + connection_free_(ENTRY_TO_CONN(ec3)); +} + +#if 0 +/* FFFF not actually supported. */ +/* automap on resolve, reverse lookup. */ +static void +test_entryconn_rewrite_automap_reverse(void *arg) +{ + entry_connection_t *ec = arg; + entry_connection_t *ec2=NULL; + rewrite_result_t rr; + char *msg = NULL; + + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET); + + get_options_mutable()->AutomapHostsOnResolve = 1; + get_options_mutable()->SafeLogging_ = SAFELOG_SCRUB_NONE; + smartlist_add(get_options_mutable()->AutomapHostsSuffixes, + tor_strdup(".bloom")); + parse_virtual_addr_network("127.80.0.0/16", AF_INET, 0, &msg); + + /* Automap this on resolve. */ + strlcpy(ec->socks_request->address, "www.poldy.BLOOM", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.poldy.bloom"); + tt_str_op(ec->original_dest_address, OP_EQ, "www.poldy.bloom"); + + tt_assert(!strcmpstart(ec->socks_request->address,"127.80.")); + + strlcpy(ec2->socks_request->address, ec->socks_request->address, + sizeof(ec2->socks_request->address)); + ec2->entry_cfg.use_cached_ipv4_answers = 1; // XXXX REMOVE. This is only there to hide a bug. + ec2->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; + connection_ap_handshake_rewrite(ec2, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 1); + tt_int_op(rr.end_reason, OP_EQ, + END_STREAM_REASON_DONE|END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + + done: + connection_free_(ENTRY_TO_CONN(ec2)); +} +#endif + +/* Rewrite because of cached DNS entry. */ +static void +test_entryconn_rewrite_cached_dns_ipv4(void *arg) +{ + entry_connection_t *ec = arg; + rewrite_result_t rr; + time_t expires = time(NULL) + 3600; + entry_connection_t *ec2=NULL; + + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET); + + addressmap_register("www.friendly.example.com", + tor_strdup("240.240.241.241"), + expires, + ADDRMAPSRC_DNS, + 0, 0); + + strlcpy(ec->socks_request->address, "www.friendly.example.com", + sizeof(ec->socks_request->address)); + strlcpy(ec2->socks_request->address, "www.friendly.example.com", + sizeof(ec2->socks_request->address)); + + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + ec2->socks_request->command = SOCKS_COMMAND_CONNECT; + + ec2->entry_cfg.use_cached_ipv4_answers = 1; /* only ec2 gets this flag */ + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com"); + tt_str_op(ec->socks_request->address, OP_EQ, "www.friendly.example.com"); + + connection_ap_handshake_rewrite(ec2, &rr); + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, expires); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com"); + tt_str_op(ec2->socks_request->address, OP_EQ, "240.240.241.241"); + + done: + connection_free_(ENTRY_TO_CONN(ec2)); +} + +/* Rewrite because of cached DNS entry. */ +static void +test_entryconn_rewrite_cached_dns_ipv6(void *arg) +{ + entry_connection_t *ec = NULL; + rewrite_result_t rr; + time_t expires = time(NULL) + 3600; + entry_connection_t *ec2=NULL; + + (void)arg; + + ec = entry_connection_new(CONN_TYPE_AP, AF_INET6); + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET6); + + addressmap_register("www.friendly.example.com", + tor_strdup("[::f00f]"), + expires, + ADDRMAPSRC_DNS, + 0, 0); + + strlcpy(ec->socks_request->address, "www.friendly.example.com", + sizeof(ec->socks_request->address)); + strlcpy(ec2->socks_request->address, "www.friendly.example.com", + sizeof(ec2->socks_request->address)); + + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + ec2->socks_request->command = SOCKS_COMMAND_CONNECT; + + ec2->entry_cfg.use_cached_ipv6_answers = 1; /* only ec2 gets this flag */ + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com"); + tt_str_op(ec->socks_request->address, OP_EQ, "www.friendly.example.com"); + + connection_ap_handshake_rewrite(ec2, &rr); + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, expires); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com"); + tt_str_op(ec2->socks_request->address, OP_EQ, "[::f00f]"); + + done: + connection_free_(ENTRY_TO_CONN(ec)); + connection_free_(ENTRY_TO_CONN(ec2)); +} + +/* Fail to connect to unmapped address in virtual range. */ +static void +test_entryconn_rewrite_unmapped_virtual(void *arg) +{ + entry_connection_t *ec = arg; + rewrite_result_t rr; + entry_connection_t *ec2 = NULL; + char *msg = NULL; + + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET6); + + parse_virtual_addr_network("18.202.0.0/16", AF_INET, 0, &msg); + parse_virtual_addr_network("[ABCD::]/16", AF_INET6, 0, &msg); + + strlcpy(ec->socks_request->address, "18.202.5.5", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.should_close, OP_EQ, 1); + tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_INTERNAL); + tt_int_op(rr.automap, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + + strlcpy(ec2->socks_request->address, "[ABCD:9::5314:9543]", + sizeof(ec2->socks_request->address)); + ec2->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec2, &rr); + + tt_int_op(rr.should_close, OP_EQ, 1); + tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_INTERNAL); + tt_int_op(rr.automap, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + + done: + connection_free_(ENTRY_TO_CONN(ec2)); +} + +/* Rewrite because of mapaddress option */ +static void +test_entryconn_rewrite_mapaddress(void *arg) +{ + entry_connection_t *ec = arg; + rewrite_result_t rr; + + config_line_append(&get_options_mutable()->AddressMap, + "MapAddress", "meta metaobjects.example"); + config_register_addressmaps(get_options()); + + strlcpy(ec->socks_request->address, "meta", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_int_op(rr.automap, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(ec->socks_request->address, OP_EQ, "metaobjects.example"); + + done: + ; +} + +/* Reject reverse lookups of internal address. */ +static void +test_entryconn_rewrite_reject_internal_reverse(void *arg) +{ + entry_connection_t *ec = arg; + rewrite_result_t rr; + + strlcpy(ec->socks_request->address, "10.0.0.1", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.should_close, OP_EQ, 1); + tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_SOCKSPROTOCOL | + END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); + tt_int_op(rr.automap, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + + done: + ; +} + +/* Rewrite into .exit because of virtual address mapping */ +static void +test_entryconn_rewrite_automap_exit(void *arg) +{ + entry_connection_t *ec = arg; + entry_connection_t *ec2=NULL; + rewrite_result_t rr; + char *msg = NULL; + + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET); + + get_options_mutable()->AutomapHostsOnResolve = 1; + get_options_mutable()->AllowDotExit = 1; + smartlist_add(get_options_mutable()->AutomapHostsSuffixes, + tor_strdup(".EXIT")); + parse_virtual_addr_network("127.1.0.0/16", AF_INET, 0, &msg); + + /* Automap this on resolve. */ + strlcpy(ec->socks_request->address, "website.example.exit", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "website.example.exit"); + tt_str_op(ec->original_dest_address, OP_EQ, "website.example.exit"); + + tt_assert(!strcmpstart(ec->socks_request->address,"127.1.")); + + /* Connect to it and make sure we get the original address back. */ + strlcpy(ec2->socks_request->address, ec->socks_request->address, + sizeof(ec2->socks_request->address)); + + ec2->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec2, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_AUTOMAP); + tt_str_op(rr.orig_address, OP_EQ, ec->socks_request->address); + tt_str_op(ec2->original_dest_address, OP_EQ, ec->socks_request->address); + tt_str_op(ec2->socks_request->address, OP_EQ, "website.example.exit"); + + done: + connection_free_(ENTRY_TO_CONN(ec2)); +} + +/* Rewrite into .exit because of mapaddress */ +static void +test_entryconn_rewrite_mapaddress_exit(void *arg) +{ + entry_connection_t *ec = arg; + rewrite_result_t rr; + + config_line_append(&get_options_mutable()->AddressMap, + "MapAddress", "*.example.com *.example.com.abc.exit"); + config_register_addressmaps(get_options()); + + /* Automap this on resolve. */ + strlcpy(ec->socks_request->address, "abc.example.com", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_TORRC); + tt_str_op(rr.orig_address, OP_EQ, "abc.example.com"); + tt_str_op(ec->socks_request->address, OP_EQ, "abc.example.com.abc.exit"); + done: + ; +} + +/* Map foo.onion to longthing.onion, and also automap. */ +static void +test_entryconn_rewrite_mapaddress_automap_onion(void *arg) +{ + entry_connection_t *ec = arg; + entry_connection_t *ec2 = NULL; + entry_connection_t *ec3 = NULL; + entry_connection_t *ec4 = NULL; + rewrite_result_t rr; + char *msg = NULL; + + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET); + ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET); + ec4 = entry_connection_new(CONN_TYPE_AP, AF_INET); + + get_options_mutable()->AutomapHostsOnResolve = 1; + get_options_mutable()->AllowDotExit = 1; + smartlist_add(get_options_mutable()->AutomapHostsSuffixes, + tor_strdup(".onion")); + parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg); + config_line_append(&get_options_mutable()->AddressMap, + "MapAddress", "foo.onion abcdefghijklmnop.onion"); + config_register_addressmaps(get_options()); + + /* Connect to foo.onion. */ + strlcpy(ec->socks_request->address, "foo.onion", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "foo.onion"); + tt_str_op(ec->socks_request->address, OP_EQ, "abcdefghijklmnop.onion"); + + /* Okay, resolve foo.onion */ + strlcpy(ec2->socks_request->address, "foo.onion", + sizeof(ec2->socks_request->address)); + ec2->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec2, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "foo.onion"); + tt_assert(!strcmpstart(ec2->socks_request->address, "192.168.")); + + /* Now connect */ + strlcpy(ec3->socks_request->address, ec2->socks_request->address, + sizeof(ec3->socks_request->address)); + ec3->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec3, &rr); + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_assert(!strcmpstart(ec3->socks_request->address, "abcdefghijklmnop.onion")); + + /* Now resolve abcefghijklmnop.onion. */ + strlcpy(ec4->socks_request->address, "abcdefghijklmnop.onion", + sizeof(ec4->socks_request->address)); + ec4->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec4, &rr); + + tt_int_op(rr.automap, OP_EQ, 1); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "abcdefghijklmnop.onion"); + tt_assert(!strcmpstart(ec4->socks_request->address, "192.168.")); + /* XXXX doesn't work + tt_str_op(ec4->socks_request->address, OP_EQ, ec2->socks_request->address); + */ + + done: + connection_free_(ENTRY_TO_CONN(ec2)); + connection_free_(ENTRY_TO_CONN(ec3)); + connection_free_(ENTRY_TO_CONN(ec4)); +} + +static void +test_entryconn_rewrite_mapaddress_automap_onion_common(entry_connection_t *ec, + int map_to_onion, + int map_to_address) +{ + entry_connection_t *ec2 = NULL; + entry_connection_t *ec3 = NULL; + rewrite_result_t rr; + + ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET); + ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET); + + /* Connect to irc.example.com */ + strlcpy(ec->socks_request->address, "irc.example.com", + sizeof(ec->socks_request->address)); + ec->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec, &rr); + + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "irc.example.com"); + tt_str_op(ec->socks_request->address, OP_EQ, + map_to_onion ? "abcdefghijklmnop.onion" : "irc.example.com"); + + /* Okay, resolve irc.example.com */ + strlcpy(ec2->socks_request->address, "irc.example.com", + sizeof(ec2->socks_request->address)); + ec2->socks_request->command = SOCKS_COMMAND_RESOLVE; + connection_ap_handshake_rewrite(ec2, &rr); + + tt_int_op(rr.automap, OP_EQ, map_to_onion && map_to_address); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX); + tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE); + tt_str_op(rr.orig_address, OP_EQ, "irc.example.com"); + if (map_to_onion && map_to_address) + tt_assert(!strcmpstart(ec2->socks_request->address, "192.168.")); + + /* Now connect */ + strlcpy(ec3->socks_request->address, ec2->socks_request->address, + sizeof(ec3->socks_request->address)); + ec3->socks_request->command = SOCKS_COMMAND_CONNECT; + connection_ap_handshake_rewrite(ec3, &rr); + tt_int_op(rr.automap, OP_EQ, 0); + tt_int_op(rr.should_close, OP_EQ, 0); + tt_int_op(rr.end_reason, OP_EQ, 0); + if (map_to_onion) + tt_assert(!strcmpstart(ec3->socks_request->address, + "abcdefghijklmnop.onion")); + + done: + connection_free_(ENTRY_TO_CONN(ec2)); + connection_free_(ENTRY_TO_CONN(ec3)); +} + +/* This time is the same, but we start with a mapping from a non-onion + * address. */ +static void +test_entryconn_rewrite_mapaddress_automap_onion2(void *arg) +{ + char *msg = NULL; + get_options_mutable()->AutomapHostsOnResolve = 1; + smartlist_add(get_options_mutable()->AutomapHostsSuffixes, + tor_strdup(".onion")); + parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg); + config_line_append(&get_options_mutable()->AddressMap, + "MapAddress", "irc.example.com abcdefghijklmnop.onion"); + config_register_addressmaps(get_options()); + + test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 1, 1); +} + +/* Same as above, with automapped turned off */ +static void +test_entryconn_rewrite_mapaddress_automap_onion3(void *arg) +{ + config_line_append(&get_options_mutable()->AddressMap, + "MapAddress", "irc.example.com abcdefghijklmnop.onion"); + config_register_addressmaps(get_options()); + + test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 1, 0); +} + +/* As above, with no mapping. */ +static void +test_entryconn_rewrite_mapaddress_automap_onion4(void *arg) +{ + char *msg = NULL; + get_options_mutable()->AutomapHostsOnResolve = 1; + smartlist_add(get_options_mutable()->AutomapHostsSuffixes, + tor_strdup(".onion")); + parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg); + + test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 0, 1); +} + + +#define REWRITE(name) \ + { #name, test_entryconn_##name, TT_FORK, &test_rewrite_setup, NULL } + +struct testcase_t entryconn_tests[] = { + REWRITE(rewrite_basic), + REWRITE(rewrite_bad_dotexit), + REWRITE(rewrite_automap_ipv4), + REWRITE(rewrite_automap_ipv6), + // REWRITE(rewrite_automap_reverse), + REWRITE(rewrite_cached_dns_ipv4), + REWRITE(rewrite_cached_dns_ipv6), + REWRITE(rewrite_unmapped_virtual), + REWRITE(rewrite_mapaddress), + REWRITE(rewrite_reject_internal_reverse), + REWRITE(rewrite_automap_exit), + REWRITE(rewrite_mapaddress_exit), + REWRITE(rewrite_mapaddress_automap_onion), + REWRITE(rewrite_mapaddress_automap_onion2), + REWRITE(rewrite_mapaddress_automap_onion3), + REWRITE(rewrite_mapaddress_automap_onion4), + + END_OF_TESTCASES +}; + |