/* Copyright (c) 2020, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file resolve_addr.c * \brief Implement resolving address functions **/ #define RESOLVE_ADDR_PRIVATE #include "app/config/config.h" #include "app/config/resolve_addr.h" #include "core/mainloop/mainloop.h" #include "feature/control/control_events.h" #include "feature/dirauth/authmode.h" #include "lib/encoding/confline.h" #include "lib/net/gethostname.h" #include "lib/net/resolve.h" /** Maximum "Address" statement allowed in our configuration. */ #define MAX_CONFIG_ADDRESS 2 /** Ease our life. Arrays containing state per address family. These are to * add semantic to the code so we know what is accessed. */ #define IDX_NULL 0 /* Index to zeroed address object. */ #define IDX_IPV4 1 /* Index to AF_INET. */ #define IDX_IPV6 2 /* Index to AF_INET6. */ #define IDX_SIZE 3 /* How many indexes do we have. */ /** Function in our address function table return one of these code. */ typedef enum { /* The address has been found. */ FN_RET_OK = 0, /* The failure requirements were not met and thus it is recommended that the * caller stops the search. */ FN_RET_BAIL = 1, /* The address was not found or failure is transient so the caller should go * to the next method. */ FN_RET_NEXT = 2, } fn_address_ret_t; /** Last resolved addresses. */ static tor_addr_t last_resolved_addrs[] = { TOR_ADDR_NULL, TOR_ADDR_NULL, TOR_ADDR_NULL }; CTASSERT(ARRAY_LENGTH(last_resolved_addrs) == IDX_SIZE); /** Last suggested addresses. * * These addresses come from a NETINFO cell from a trusted relay (currently * only authorities). We only use those in last resort. */ static tor_addr_t last_suggested_addrs[] = { TOR_ADDR_NULL, TOR_ADDR_NULL, TOR_ADDR_NULL }; CTASSERT(ARRAY_LENGTH(last_suggested_addrs) == IDX_SIZE); /** True iff the address was found to be configured that is from the * configuration file either using Address or ORPort. */ static bool last_addrs_configured[] = { false, false, false }; CTASSERT(ARRAY_LENGTH(last_addrs_configured) == IDX_SIZE); static inline int af_to_idx(const int family) { switch (family) { case AF_INET: return IDX_IPV4; case AF_INET6: return IDX_IPV6; default: /* It wouldn't be safe to just die here with an assert but we can heavily * scream with a bug. Return the index of the NULL address. */ tor_assert_nonfatal_unreached(); return IDX_NULL; } } /** Return string representation of the given method. */ const char * resolved_addr_method_to_str(const resolved_addr_method_t method) { switch (method) { case RESOLVED_ADDR_NONE: return "NONE"; case RESOLVED_ADDR_CONFIGURED: return "CONFIGURED"; case RESOLVED_ADDR_CONFIGURED_ORPORT: return "CONFIGURED_ORPORT"; case RESOLVED_ADDR_GETHOSTNAME: return "GETHOSTNAME"; case RESOLVED_ADDR_INTERFACE: return "INTERFACE"; case RESOLVED_ADDR_RESOLVED: return "RESOLVED"; default: tor_assert_nonfatal_unreached(); return "???"; } } /** Return true if the last address of family was configured or not. An * address is considered configured if it was found in the Address or ORPort * statement. * * This applies to the address returned by the function * resolved_addr_get_last() which is the cache of discovered addresses. */ bool resolved_addr_is_configured(int family) { return last_addrs_configured[af_to_idx(family)]; } /** Copy the last suggested address of family into addr_out. * * If no last suggested address exists, the addr_out is a null address (use * tor_addr_is_null() to confirm). */ void resolved_addr_get_suggested(int family, tor_addr_t *addr_out) { tor_addr_copy(addr_out, &last_suggested_addrs[af_to_idx(family)]); } /** Set the last suggested address into our cache. This is called when we get * a new NETINFO cell from a trusted source. */ void resolved_addr_set_suggested(const tor_addr_t *addr) { if (BUG(tor_addr_family(addr) != AF_INET && tor_addr_family(addr) != AF_INET6)) { return; } tor_addr_copy(&last_suggested_addrs[af_to_idx(tor_addr_family(addr))], addr); } /** Copy the last resolved address of family into addr_out. * * If not last resolved address existed, the addr_out is a null address (use * tor_addr_is_null()). */ void resolved_addr_get_last(int family, tor_addr_t *addr_out) { tor_addr_copy(addr_out, &last_resolved_addrs[af_to_idx(family)]); } /** Reset the last resolved address of family. * * This makes it null address. */ void resolved_addr_reset_last(int family) { tor_addr_make_null(&last_resolved_addrs[af_to_idx(family)], family); } /** Errors returned by address_can_be_used() in order for the caller to know * why the address is denied or not. */ #define ERR_DEFAULT_DIRAUTH -1 /* Using default authorities. */ #define ERR_ADDRESS_IS_INTERNAL -2 /* IP is internal. */ /** @brief Return true iff the given IP address can be used as a valid * external resolved address. * * Two tests are done in this function: * 1) If the address if NOT internal, it can be used. * 2) If the address is internal and we have custom directory authorities * configured then it can they be used. Important for testing networks. * * @param addr The IP address to validate. * @param options Global configuration options. * @param warn_severity Log level that should be used on error. * @param explicit_ip Was the IP address explicitly given. * * @return Return 0 if it can be used. Return error code ERR_* found at the * top of the file. */ static int address_can_be_used(const tor_addr_t *addr, const or_options_t *options, int warn_severity, const bool explicit_ip) { tor_assert(addr); /* Public address, this is fine. */ if (!tor_addr_is_internal(addr, 0)) { goto allow; } /* We have a private IP address. It is allowed only if we set custom * directory authorities. */ if (using_default_dir_authorities(options)) { log_fn(warn_severity, LD_CONFIG, "Address '%s' is a private IP address. Tor relays that use " "the default DirAuthorities must have public IP addresses.", fmt_addr(addr)); return ERR_DEFAULT_DIRAUTH; } if (!explicit_ip) { /* Even with custom directory authorities, only an explicit internal * address is accepted. */ log_fn(warn_severity, LD_CONFIG, "Address %s was resolved and thus not explicitly " "set. Even if DirAuthorities are custom, this is " "not allowed.", fmt_addr(addr)); return ERR_ADDRESS_IS_INTERNAL; } allow: return 0; } /** @brief Get IP address from the given config line and for a specific address * family. * * This can fail is more than two Address statement are found for the same * address family. It also fails if no statement is found. * * @param options Global configuration options. * @param warn_severity Log level that should be used on error. * @param family IP address family. Only AF_INET and AF_INET6 are supported. * @param method_out OUT: Method denoting how the address was found. * This is described in the control-spec.txt as * actions for "STATUS_SERVER". * @param hostname_out OUT: String containing the hostname gotten from the * Address value if any. * @param addr_out OUT: Tor address of the address found in the cline or * resolved from the cline. * * @return Return 0 on success that is an address has been found or resolved * successfully. Return error code ERR_* found at the top of the file. */ static fn_address_ret_t get_address_from_config(const or_options_t *options, int warn_severity, int family, resolved_addr_method_t *method_out, char **hostname_out, tor_addr_t *addr_out) { int ret; bool explicit_ip = false, resolve_failure = false; int num_valid_addr = 0; tor_assert(options); tor_assert(addr_out); tor_assert(method_out); tor_assert(hostname_out); /* Set them to NULL for safety reasons. */ *hostname_out = NULL; *method_out = RESOLVED_ADDR_NONE; log_debug(LD_CONFIG, "Attempting to get address from configuration"); if (!options->Address) { log_info(LD_CONFIG, "No Address option found in configuration."); /* No Address statement, inform caller to try next method. */ return FN_RET_NEXT; } for (const config_line_t *cfg = options->Address; cfg != NULL; cfg = cfg->next) { int af; tor_addr_t addr; af = tor_addr_parse(&addr, cfg->value); if (af == family) { tor_addr_copy(addr_out, &addr); *method_out = RESOLVED_ADDR_CONFIGURED; explicit_ip = true; num_valid_addr++; continue; } else if (af != -1) { /* Parsable address but just not the one from the family we want. Skip * it so we don't attempt a resolve. */ continue; } /* Not an IP address. Considering this value a hostname and attempting to * do a DNS lookup. */ if (!tor_addr_lookup(cfg->value, family, &addr)) { tor_addr_copy(addr_out, &addr); *method_out = RESOLVED_ADDR_RESOLVED; if (*hostname_out) { tor_free(*hostname_out); } *hostname_out = tor_strdup(cfg->value); explicit_ip = false; num_valid_addr++; continue; } else { /* Hostname that can't be resolved, this is a fatal error. */ resolve_failure = true; log_fn(warn_severity, LD_CONFIG, "Could not resolve local Address '%s'. Failing.", cfg->value); continue; } } if (!num_valid_addr) { if (resolve_failure) { /* We found no address but we got a resolution failure. This means we * can know if the hostname given was v4 or v6 so we can't continue. */ return FN_RET_BAIL; } log_info(LD_CONFIG, "No Address option found for family %s in configuration.", fmt_af_family(family)); /* No Address statement for family so move on to try next method. */ return FN_RET_NEXT; } if (num_valid_addr >= MAX_CONFIG_ADDRESS) { /* Too many Address for same family. This is a fatal error. */ log_fn(warn_severity, LD_CONFIG, "Found %d Address statement of address family %s. " "Only one is allowed.", num_valid_addr, fmt_af_family(family)); tor_free(*hostname_out); return FN_RET_BAIL; } /* Great, we found an address. */ ret = address_can_be_used(addr_out, options, warn_severity, explicit_ip); if (ret != 0) { /* One of the requirement of this interface is if an internal Address is * used, custom authorities must be defined else it is a fatal error. * Furthermore, if the Address was resolved to an internal interface, we * stop immediately. */ tor_free(*hostname_out); return FN_RET_BAIL; } /* Address can be used. We are done. */ log_info(LD_CONFIG, "Address found in configuration: %s", fmt_addr(addr_out)); return FN_RET_OK; } /** @brief Get IP address from the local hostname by calling gethostbyname() * and doing a DNS resolution on the hostname. * * @param options Global configuration options. * @param warn_severity Log level that should be used on error. * @param family IP address family. Only AF_INET and AF_INET6 are supported. * @param method_out OUT: Method denoting how the address was found. * This is described in the control-spec.txt as * actions for "STATUS_SERVER". * @param hostname_out OUT: String containing the local hostname. * @param addr_out OUT: Tor address resolved from the local hostname. * * @return Return 0 on success that is an address has been found and resolved * successfully. Return error code ERR_* found at the top of the file. */ static fn_address_ret_t get_address_from_hostname(const or_options_t *options, int warn_severity, int family, resolved_addr_method_t *method_out, char **hostname_out, tor_addr_t *addr_out) { int ret; char hostname[256]; tor_assert(addr_out); tor_assert(method_out); /* Set them to NULL for safety reasons. */ *hostname_out = NULL; *method_out = RESOLVED_ADDR_NONE; log_debug(LD_CONFIG, "Attempting to get address from local hostname"); if (tor_gethostname(hostname, sizeof(hostname)) < 0) { log_fn(warn_severity, LD_NET, "Error obtaining local hostname"); /* Unable to obtain the local hostname is a fatal error. */ return FN_RET_BAIL; } if (tor_addr_lookup(hostname, family, addr_out)) { log_fn(warn_severity, LD_NET, "Could not resolve local hostname '%s'. Failing.", hostname); /* Unable to resolve, inform caller to try next method. */ return FN_RET_NEXT; } ret = address_can_be_used(addr_out, options, warn_severity, false); if (ret == ERR_DEFAULT_DIRAUTH) { /* Non custom authorities, inform caller to try next method. */ return FN_RET_NEXT; } else if (ret == ERR_ADDRESS_IS_INTERNAL) { /* Internal address is a fatal error. */ return FN_RET_BAIL; } /* addr_out contains the address of the local hostname. */ *method_out = RESOLVED_ADDR_GETHOSTNAME; *hostname_out = tor_strdup(hostname); /* Found it! */ log_info(LD_CONFIG, "Address found from local hostname: %s", fmt_addr(addr_out)); return FN_RET_OK; } /** @brief Get IP address from a network interface. * * @param options Global configuration options. * @param warn_severity Log level that should be used on error. * @param family IP address family. Only AF_INET and AF_INET6 are supported. * @param method_out OUT: Always RESOLVED_ADDR_INTERFACE on success which * is detailed in the control-spec.txt as actions * for "STATUS_SERVER". * @param hostname_out OUT: String containing the local hostname. For this * function, it is always set to NULL. * @param addr_out OUT: Tor address found attached to the interface. * * @return Return 0 on success that is an address has been found. Return * error code ERR_* found at the top of the file. */ static fn_address_ret_t get_address_from_interface(const or_options_t *options, int warn_severity, int family, resolved_addr_method_t *method_out, char **hostname_out, tor_addr_t *addr_out) { int ret; tor_assert(method_out); tor_assert(hostname_out); tor_assert(addr_out); /* Set them to NULL for safety reasons. */ *method_out = RESOLVED_ADDR_NONE; *hostname_out = NULL; log_debug(LD_CONFIG, "Attempting to get address from network interface"); if (get_interface_address6(warn_severity, family, addr_out) < 0) { log_fn(warn_severity, LD_CONFIG, "Could not get local interface IP address."); /* Unable to get IP from interface. Inform caller to try next method. */ return FN_RET_NEXT; } ret = address_can_be_used(addr_out, options, warn_severity, false); if (ret < 0) { /* Unable to use address. Inform caller to try next method. */ return FN_RET_NEXT; } *method_out = RESOLVED_ADDR_INTERFACE; /* Found it! */ log_info(LD_CONFIG, "Address found from interface: %s", fmt_addr(addr_out)); return FN_RET_OK; } /** @brief Get IP address from the ORPort (if any). * * @param options Global configuration options. * @param warn_severity Log level that should be used on error. * @param family IP address family. Only AF_INET and AF_INET6 are supported. * @param method_out OUT: Always RESOLVED_ADDR_CONFIGURED_ORPORT on success * which is detailed in the control-spec.txt as actions * for "STATUS_SERVER". * @param hostname_out OUT: String containing the ORPort hostname if any. * @param addr_out OUT: Tor address found if any. * * @return Return 0 on success that is an address has been found. Return * error code ERR_* found at the top of the file. */ static fn_address_ret_t get_address_from_orport(const or_options_t *options, int warn_severity, int family, resolved_addr_method_t *method_out, char **hostname_out, tor_addr_t *addr_out) { int ret; const tor_addr_t *addr; tor_assert(method_out); tor_assert(hostname_out); tor_assert(addr_out); /* Set them to NULL for safety reasons. */ *method_out = RESOLVED_ADDR_NONE; *hostname_out = NULL; log_debug(LD_CONFIG, "Attempting to get address from ORPort"); if (!options->ORPort_set) { log_info(LD_CONFIG, "No ORPort found in configuration."); /* No ORPort statement, inform caller to try next method. */ return FN_RET_NEXT; } /* Get ORPort for requested family. */ addr = get_orport_addr(family); if (!addr) { /* No address configured for the ORPort. Ignore. */ return FN_RET_NEXT; } /* We found the ORPort address. Just make sure it can be used. */ ret = address_can_be_used(addr, options, warn_severity, true); if (ret < 0) { /* Unable to use address. Inform caller to try next method. */ return FN_RET_NEXT; } /* Found it! */ *method_out = RESOLVED_ADDR_CONFIGURED_ORPORT; tor_addr_copy(addr_out, addr); log_fn(warn_severity, LD_CONFIG, "Address found from ORPort: %s", fmt_addr(addr_out)); return FN_RET_OK; } /** @brief Set the last resolved address cache using the given address. * * A log notice is emitted if the given address has changed from before. Not * emitted on first resolve. * * Control port event "STATUS_SERVER" is emitted with the new information if * it has changed. * * Finally, tor is notified that the IP address has changed. * * @param addr IP address to update the cache with. * @param method_used By which method did we resolved it (for logging and * control port). * @param hostname_used Which hostname was used. If none were used, it is * NULL. (for logging and control port). */ void resolved_addr_set_last(const tor_addr_t *addr, const resolved_addr_method_t method_used, const char *hostname_used) { /** Have we done a first resolve. This is used to control logging. */ static bool have_resolved_once[] = { false, false, false }; CTASSERT(ARRAY_LENGTH(have_resolved_once) == IDX_SIZE); bool *done_one_resolve; bool have_hostname = false; tor_addr_t *last_resolved; tor_assert(addr); /* Do we have an hostname. */ have_hostname = (hostname_used != NULL); int idx = af_to_idx(tor_addr_family(addr)); if (idx == IDX_NULL) { /* Not suppose to happen and if it does, af_to_idx() screams loudly. */ return; } /* Get values from cache. */ done_one_resolve = &have_resolved_once[idx]; last_resolved = &last_resolved_addrs[idx]; /* Same address last resolved. Ignore. */ if (tor_addr_eq(last_resolved, addr)) { return; } /* Don't log notice if this is the first resolve we do. */ if (*done_one_resolve) { /* Leave this as a notice, regardless of the requested severity, * at least until dynamic IP address support becomes bulletproof. */ log_notice(LD_NET, "Your IP address seems to have changed to %s " "(METHOD=%s%s%s). Updating.", fmt_addr(addr), resolved_addr_method_to_str(method_used), have_hostname ? " HOSTNAME=" : "", have_hostname ? hostname_used : ""); ip_address_changed(0); } /* Notify control port. */ control_event_server_status(LOG_NOTICE, "EXTERNAL_ADDRESS ADDRESS=%s METHOD=%s%s%s", fmt_addr(addr), resolved_addr_method_to_str(method_used), have_hostname ? " HOSTNAME=" : "", have_hostname ? hostname_used : ""); /* Copy address to cache. */ tor_addr_copy(last_resolved, addr); *done_one_resolve = true; /* Flag true if the address was configured. Else, indicate it was not. */ last_addrs_configured[idx] = false; if (method_used == RESOLVED_ADDR_CONFIGURED || method_used == RESOLVED_ADDR_CONFIGURED_ORPORT) { last_addrs_configured[idx] = true; } } /** Ease our lives. Typedef to the address discovery function signature. */ typedef fn_address_ret_t (*fn_address_t)( const or_options_t *options, int warn_severity, int family, resolved_addr_method_t *method_out, char **hostname_out, tor_addr_t *addr_out); /** Address discovery function table. The order matters as in the first one is * executed first and so on. */ static const fn_address_t fn_address_table[] = { /* These functions are in order for our find address algorithm. */ get_address_from_config, get_address_from_orport, get_address_from_interface, get_address_from_hostname, }; /** Length of address table as in how many functions. */ static const size_t fn_address_table_len = ARRAY_LENGTH(fn_address_table); /* Address discover function table for authorities (bridge or directory). * * They only discover their address from either the configuration file or the * ORPort. They do not query the interface nor do any DNS resolution for * security reasons. */ static const fn_address_t fn_address_table_auth[] = { /* These functions are in order for our find address algorithm. */ get_address_from_config, get_address_from_orport, }; /** Length of address table as in how many functions. */ static const size_t fn_address_table_auth_len = ARRAY_LENGTH(fn_address_table_auth); /** @brief Attempt to find our IP address that can be used as our external * reachable address. * * The following describe the algorithm to find an address. Each have * specific conditions so read carefully. * * On success, true is returned and depending on how the address was found, * the out parameters can have different values. * * On error, false is returned and out parameters are set to NULL. * * 1. Look at the configuration Address option. * If Address is a public address, True is returned and addr_out is set * with it, the method_out is set to RESOLVED_ADDR_CONFIGURED and * hostname_out is set to NULL. * * If Address is an internal address but NO custom authorities are used, * an error is returned. * * If Address is a hostname, that is it can't be converted to an address, * it is resolved. On success, addr_out is set with the address, * method_out is set to RESOLVED_ADDR_RESOLVED and hostname_out is set * to the resolved hostname. On failure to resolve, an error is returned. * * If no given Address, fallback to the local hostname (see section 2). * * 2. Look at the network interface. * * Attempt to find the first public usable address from the list of * network interface returned by the OS. * * On failure, we attempt to look at the local hostname (3). * * On success, addr_out is set with it, method_out is set to * RESOLVED_ADDR_INTERFACE and hostname_out is set to NULL. * * 3. Look at the local hostname. * * If the local hostname resolves to a non internal address, addr_out is * set with it, method_out is set to RESOLVED_ADDR_GETHOSTNAME and * hostname_out is set to the resolved hostname. * * If a local hostname can NOT be found, an error is returned. * * If the local hostname resolves to an internal address, an error is * returned. * * If the local hostname can NOT be resolved, an error is returned. * * @param options Global configuration options. * @param family IP address family. Only AF_INET and AF_INET6 are supported. * @param warn_severity Logging level. * @param addr_out OUT: Set with the IP address found if any. * @param method_out OUT: (optional) Method denoting how the address wa * found. This is described in the control-spec.txt as * actions for "STATUS_SERVER". * @param hostname_out OUT: String containing the hostname if any was used. * Only be set for RESOLVED and GETHOSTNAME methods. * Else it is set to NULL. * * @return True if the address was found for the given family. False if not or * on errors. */ bool find_my_address(const or_options_t *options, int family, int warn_severity, tor_addr_t *addr_out, resolved_addr_method_t *method_out, char **hostname_out) { resolved_addr_method_t method_used = RESOLVED_ADDR_NONE; char *hostname_used = NULL; tor_addr_t my_addr; const fn_address_t *table = fn_address_table; size_t table_len = fn_address_table_len; tor_assert(options); tor_assert(addr_out); /* Set them to NULL for safety reasons. */ tor_addr_make_unspec(addr_out); if (method_out) *method_out = RESOLVED_ADDR_NONE; if (hostname_out) *hostname_out = NULL; /* If an IPv6 is requested, check if IPv6 address discovery is disabled and * if so we always return a failure. It is done here so we don't populate * the resolve cache or do any DNS resolution. */ if (family == AF_INET6 && options->AddressDisableIPv6) { return false; } /* For authorities (bridge and directory), we use a different table. */ if (authdir_mode(options)) { table = fn_address_table_auth; table_len = fn_address_table_auth_len; } /* * Step 1: Discover address by calling methods from the function table. */ /* Go over the function table. They are in order. */ for (size_t idx = 0; idx < table_len; idx++) { fn_address_ret_t ret = table[idx](options, warn_severity, family, &method_used, &hostname_used, &my_addr); if (ret == FN_RET_BAIL) { return false; } else if (ret == FN_RET_OK) { goto found; } tor_assert(ret == FN_RET_NEXT); } /* We've exhausted our attempts. Failure. */ log_fn(warn_severity, LD_CONFIG, "Unable to find our IP address."); return false; found: /* * Step 2: Update last resolved address cache and inform the control port. */ resolved_addr_set_last(&my_addr, method_used, hostname_used); if (method_out) { *method_out = method_used; } if (hostname_out) { *hostname_out = hostname_used; } else { tor_free(hostname_used); } tor_addr_copy(addr_out, &my_addr); return true; } /** @brief: Return true iff the given addr is judged to be local to our * resolved address. * * This function is used to tell whether another address is 'remote' enough * that we can trust it when it tells us that we are reachable, or that we * have a certain address. * * The criterion to learn if the address is local are the following: * * 1. Internal address. * 2. If EnforceDistinctSubnets is set then it is never local. * 3. Network mask is compared. IPv4: /24 and IPv6 /48. This is different * from the path selection that looks at /16 and /32 because we only * want to learn here if the address is considered to come from the * Internet basically. * * @param addr The address to test if local and also test against our resovled * address. * * @return True iff address is considered local or else False. */ MOCK_IMPL(bool, is_local_to_resolve_addr, (const tor_addr_t *addr)) { const int family = tor_addr_family(addr); const tor_addr_t *last_resolved_addr = &last_resolved_addrs[af_to_idx(family)]; /* Internal address is always local. */ if (tor_addr_is_internal(addr, 0)) { return true; } /* Address is not local if we don't enforce subnet distinction. */ if (get_options()->EnforceDistinctSubnets == 0) { return false; } switch (family) { case AF_INET: /* It's possible that this next check will hit before the first time * find_my_address actually succeeds. For clients, it is likely that * find_my_address will never be called at all. In those cases, * last_resolved_addr_v4 will be 0, and so checking to see whether ip is * on the same /24 as last_resolved_addrs[AF_INET] will be the same as * checking whether it was on net 0, which is already done by * tor_addr_is_internal. */ return tor_addr_compare_masked(addr, last_resolved_addr, 24, CMP_SEMANTIC) == 0; case AF_INET6: /* Look at /48 because it is typically the smallest network in the global * IPv6 routing tables, and it was previously the recommended per-customer * network block. (See [RFC 6177: IPv6 End Site Address Assignment].) */ return tor_addr_compare_masked(addr, last_resolved_addr, 48, CMP_SEMANTIC) == 0; break; default: /* Unknown address type so not local. */ return false; } } #ifdef TOR_UNIT_TESTS void resolve_addr_reset_suggested(int family) { tor_addr_make_unspec(&last_suggested_addrs[af_to_idx(family)]); } #endif /* TOR_UNIT_TESTS */