[tor-commits] [tor/master] addr: New find_my_address() to support multiple address families

dgoulet at torproject.org dgoulet at torproject.org
Wed Jun 24 17:58:40 UTC 2020


commit 9e85056de9ffe4858e2a07a5fd5e5fd4d144e688
Author: David Goulet <dgoulet at 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 at 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);
 





More information about the tor-commits mailing list