mirror of
https://gitlab.torproject.org/tpo/core/tor.git
synced 2024-11-10 21:23:58 +01:00
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>
This commit is contained in:
parent
d08d7e1535
commit
9e85056de9
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user