commit 9e85056de9ffe4858e2a07a5fd5e5fd4d144e688 Author: David Goulet dgoulet@torproject.org Date: Thu Jun 18 13:39:20 2020 -0400
addr: New find_my_address() to support multiple address families
resolve_my_address() was beyond repair in terms of refactoring. Way too complex and doing too many things.
This commit implements find_my_address() which in theory does the same as resolve_my_address() but in a more clean, concise and modern way using the tor_addr_t interface and for multiple address family.
The caller needs to pass the address family (IPv4 or IPv6) which this interface supports. For both, a last resolved cache is used as well.
Implements #33233
Signed-off-by: David Goulet dgoulet@torproject.org --- src/app/config/resolve_addr.c | 490 ++++++++++++++++++++++++++++++++++++++++++ src/app/config/resolve_addr.h | 4 + 2 files changed, 494 insertions(+)
diff --git a/src/app/config/resolve_addr.c b/src/app/config/resolve_addr.c index 6b66a7100..5cabb5889 100644 --- a/src/app/config/resolve_addr.c +++ b/src/app/config/resolve_addr.c @@ -19,6 +19,31 @@ #include "lib/net/gethostname.h" #include "lib/net/resolve.h"
+/** Maximum "Address" statement allowed in our configuration. */ +#define MAX_CONFIG_ADDRESS 2 + +/** Errors use when finding our IP address. Some are transient and some are + * persistent, just attach semantic to the values. They are all negative so + * one can do a catch all. */ + +#define ERR_FAIL_RESOLVE -1 /* Hostname resolution failed. */ +#define ERR_GET_HOSTNAME -2 /* Unable to get local hostname. */ +#define ERR_NO_OPT_ADDRESS -3 /* No Address in config. */ +#define ERR_TOO_MANY_ADDRESS -4 /* Too many Address for one family. */ +#define ERR_GET_INTERFACE -5 /* Unable to query network interface. */ +#define ERR_UNUSABLE_ADDRESS -6 /* Unusable address. It is internal. */ +#define ERR_DEFAULT_DIRAUTH -7 /* Using default authorities. */ +#define ERR_ADDRESS_IS_INTERNAL -8 /* IP is internal. */ + +/** 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_IPV4 0 /* Index to AF_INET. */ +#define IDX_IPV6 1 /* Index to AF_INET6. */ +#define IDX_SIZE 2 /* How many indexes do we have. */ + +/** Last resolved addresses. */ +static tor_addr_t last_resolved_addrs[IDX_SIZE]; + /** Last value actually set by resolve_my_address. */ static uint32_t last_resolved_addr_v4 = 0;
@@ -36,6 +61,471 @@ reset_last_resolved_addr_v4(void) last_resolved_addr_v4 = 0; }
+/** @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. + * + * On failure, no out parameters should be used or considered valid. + * + * @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: String denoting by which method 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 int +get_address_from_config(const or_options_t *options, int warn_severity, + int family, const char **method_out, + char **hostname_out, tor_addr_t *addr_out) +{ + bool explicit_ip = false; + int num_valid_addr = 0; + + tor_assert(options); + tor_assert(addr_out); + tor_assert(method_out); + tor_assert(hostname_out); + + log_debug(LD_CONFIG, "Attempting to get address from configuration"); + + if (!options->Address) { + log_info(LD_CONFIG, "No Address option found in configuration."); + return ERR_NO_OPT_ADDRESS; + } + + 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 = "CONFIGURED"; + explicit_ip = true; + num_valid_addr++; + 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"; + *hostname_out = tor_strdup(cfg->value); + explicit_ip = false; + num_valid_addr++; + continue; + } else { + /* If we have hostname we are unable to resolve, it is an persistent + * error and thus we stop right away. */ + log_fn(warn_severity, LD_CONFIG, + "Could not resolve local Address '%s'. Failing.", cfg->value); + return ERR_FAIL_RESOLVE; + } + } + + if (!num_valid_addr) { + log_fn(warn_severity, LD_CONFIG, + "No Address option found for family %s in configuration.", + fmt_af_family(family)); + return ERR_NO_OPT_ADDRESS; + } + + if (num_valid_addr >= MAX_CONFIG_ADDRESS) { + 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)); + return ERR_TOO_MANY_ADDRESS; + } + + /* Great, we found an address. */ + return address_can_be_used(addr_out, options, warn_severity, explicit_ip); +} + +/** @brief Get IP address from the local hostname by calling gethostbyname() + * and doing a DNS resolution on the hostname. + * + * On failure, no out parameters should be used or considered valid. + * + * @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: String denoting by which method 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 int +get_address_from_hostname(const or_options_t *options, int warn_severity, + int family, const char **method_out, + char **hostname_out, tor_addr_t *addr_out) +{ + int ret; + char hostname[256]; + + tor_assert(addr_out); + tor_assert(method_out); + + 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"); + return ERR_GET_HOSTNAME; + } + if (tor_addr_lookup(hostname, family, addr_out)) { + log_fn(warn_severity, LD_NET, + "Could not resolve local hostname '%s'. Failing.", hostname); + return ERR_FAIL_RESOLVE; + } + + ret = address_can_be_used(addr_out, options, warn_severity, false); + if (ret < 0) { + return ret; + } + + /* addr_out contains the address of the local hostname. */ + *method_out = "GETHOSTNAME"; + *hostname_out = tor_strdup(hostname); + + return 0; +} + +/** @brief Get IP address from a network interface. + * + * On failure, no out parameters should be used or considered valid. + * + * @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 "INTERFACE" on success which is detailed in + * the control-spec.txt as actions for "STATUS_SERVER". + * @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 int +get_address_from_interface(const or_options_t *options, int warn_severity, + int family, const char **method_out, + tor_addr_t *addr_out) +{ + int ret; + + tor_assert(method_out); + tor_assert(addr_out); + + 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."); + return ERR_GET_INTERFACE; + } + + ret = address_can_be_used(addr_out, options, warn_severity, false); + if (ret < 0) { + return ret; + } + + *method_out = "INTERFACE"; + + return 0; +} + +/** @brief Update 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 an + * empty string. (for logging and control port). + */ +static void +update_resolved_cache(const tor_addr_t *addr, const char *method_used, + const char *hostname_used) +{ + /** Have we done a first resolve. This is used to control logging. */ + static bool have_resolved_once[IDX_SIZE] = { false, false }; + bool *done_one_resolve; + bool have_hostname = false; + tor_addr_t *last_resolved; + + tor_assert(addr); + tor_assert(method_used); + tor_assert(hostname_used); + + /* Do we have an hostname. */ + have_hostname = strlen(hostname_used) > 0; + + switch (tor_addr_family(addr)) { + case AF_INET: + done_one_resolve = &have_resolved_once[IDX_IPV4]; + last_resolved = &last_resolved_addrs[IDX_IPV4]; + break; + case AF_INET6: + done_one_resolve = &have_resolved_once[IDX_IPV6]; + last_resolved = &last_resolved_addrs[IDX_IPV6]; + break; + default: + tor_assert_nonfatal_unreached(); + return; + } + + /* 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), 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), method_used, + have_hostname ? " HOSTNAME=" : "", + have_hostname ? hostname_used : ""); + /* Copy address to cache. */ + tor_addr_copy(last_resolved, addr); + *done_one_resolve = true; +} + +/** @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 all out parameters are untouched. + * + * 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 "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" 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 local hostname. + * + * If the local hostname resolves to a non internal address, addr_out is + * set with it, method_out is set to "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, fallback to the network + * interface (see section 3). + * + * 3. 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, an error is returned. This error indicates that all + * attempts have failed and thus the address for the given family can not + * be found. + * + * On success, addr_out is set with it, method_out is set to "INTERFACE" + * and hostname_out is set to NULL. + * + * @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) String denoting by which method 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 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, const char **method_out, + char **hostname_out) +{ + int ret; + const char *method_used; + char *hostname_used = tor_strdup(""); + tor_addr_t my_addr; + + tor_assert(options); + tor_assert(addr_out); + + /* + * Step 1: Discover address by attempting 3 different methods consecutively. + */ + + /* Attempt #1: Get address from configuration. */ + ret = get_address_from_config(options, warn_severity, family, &method_used, + &hostname_used, &my_addr); + if (ret == 0) { + log_fn(warn_severity, LD_CONFIG, "Address found in configuration: %s", + fmt_addr(&my_addr)); + } else { + /* Unable to resolve an Address statement is a failure. Also, using + * default dirauth error means that the configured address is internal + * which is only accepted if custom authorities are used. */ + if (ret == ERR_FAIL_RESOLVE || ret == ERR_DEFAULT_DIRAUTH) { + return false; + } + + /* Attempt #2: Get local hostname and resolve it. */ + ret = get_address_from_hostname(options, warn_severity, family, + &method_used, &hostname_used, &my_addr); + if (ret == 0) { + log_fn(warn_severity, LD_CONFIG, "Address found from local hostname: " + "%s", fmt_addr(&my_addr)); + } else if (ret < 0) { + /* Unable to get the hostname results in a failure. If the address is + * internal, we stop right away. */ + if (ret == ERR_GET_HOSTNAME || ret == ERR_ADDRESS_IS_INTERNAL) { + return false; + } + + /* Attempt #3: Get address from interface. */ + ret = get_address_from_interface(options, warn_severity, family, + &method_used, &my_addr); + if (ret == 0) { + log_fn(warn_severity, LD_CONFIG, "Address found from interface: %s", + fmt_addr(&my_addr)); + } else { + /* We've exhausted our attempts. Failure. */ + log_fn(warn_severity, LD_CONFIG, "Unable to find our IP address."); + return false; + } + } + } + tor_assert(method_used); + + /* From here, my_addr is a valid IP address of "family" and can be used as + * our external IP address. */ + + /* + * Step 2: Update last resolved address cache and inform the control port. + */ + update_resolved_cache(&my_addr, method_used, hostname_used); + + if (method_out) { + *method_out = method_used; + } + if (hostname_out) { + *hostname_out = NULL; + if (strlen(hostname_used) > 0) { + *hostname_out = hostname_used; + } else { + tor_free(hostname_used); + } + } else { + tor_free(hostname_used); + } + + tor_addr_copy(addr_out, &my_addr); + return true; +} + /** * Attempt getting our non-local (as judged by tor_addr_is_internal() * function) IP address using following techniques, listed in diff --git a/src/app/config/resolve_addr.h b/src/app/config/resolve_addr.h index 6c94fe06b..2cd27d170 100644 --- a/src/app/config/resolve_addr.h +++ b/src/app/config/resolve_addr.h @@ -15,6 +15,10 @@ int resolve_my_address_v4(int warn_severity, const or_options_t *options, uint32_t *addr_out, const char **method_out, char **hostname_out);
+bool find_my_address(const or_options_t *options, int family, + int warn_severity, tor_addr_t *addr_out, + const char **method_out, char **hostname_out); + uint32_t get_last_resolved_addr_v4(void); void reset_last_resolved_addr_v4(void);