diff --git a/changes/change_separate_exit_and_relay b/changes/change_separate_exit_and_relay new file mode 100644 index 0000000..06fb9c6 --- /dev/null +++ b/changes/change_separate_exit_and_relay @@ -0,0 +1,2 @@ + o Minor features: + - Allow separation of exit and relay traffic to different source IP addresses. Added new configuration options: OutboundBindAddressOR (relay traffic) and OutboundBindAddressExit (exit traffic). The old OutboundBindAddress applies to both. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 9f4eb31..59f432e 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -621,6 +621,22 @@ GENERAL OPTIONS This setting will be ignored for connections to the loopback addresses (127.0.0.0/8 and ::1). +[[OutboundBindAddressOR]] **OutboundBindAddressOR** __IP__:: + Make all outbound non-exit (=relay and other) connections originate from + the IP address specified. This option cannot be used together with + **OutboundBindAddress**, unless they specify a different protocol. This + option may be used twice, once with an IPv4 address and once with an + IPv6 address. This setting will be ignored for connections to the + loopback addresses (127.0.0.0/8 and ::1). + +[[OutboundBindAddressExit]] **OutboundBindAddressExit** __IP__:: + Make all outbound exit connections originate from the IP address + specified. This option cannot be used together with + **OutboundBindAddress**, unless they specify a different protocol. This + option may be used twice, once with an IPv4 address and once with an + IPv6 address. This setting will be ignored for connections to the + loopback addresses (127.0.0.0/8 and ::1). + [[PidFile]] **PidFile** __FILE__:: On startup, write our PID to FILE. On clean shutdown, remove FILE. diff --git a/src/config/torrc.sample.in b/src/config/torrc.sample.in index d4dfd5f..84d5dd7 100644 --- a/src/config/torrc.sample.in +++ b/src/config/torrc.sample.in @@ -95,7 +95,12 @@ ## If you have multiple network interfaces, you can specify one for ## outgoing traffic to use. -# OutboundBindAddress 10.0.0.5 +## OutboundBindAddressExit will be used for all exit traffic, while +## OutboundBindAddressOR will be used for all other connections. +## If you do not wish to differentiate, use OutboundBindAddress to +## specify the same address for both in a single line. +#OutboundBindAddressExit 10.0.0.4 +#OutboundBindAddressOR 10.0.0.5 ## A handle for your relay, so people don't have to refer to it by key. ## Nicknames must be between 1 and 19 characters inclusive, and must diff --git a/src/or/config.c b/src/or/config.c index 18cbe34..8ea6b6e 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -347,6 +347,8 @@ static config_var_t option_vars_[] = { V(ORListenAddress, LINELIST, NULL), VPORT(ORPort, LINELIST, NULL), V(OutboundBindAddress, LINELIST, NULL), + V(OutboundBindAddressOR, LINELIST, NULL), + V(OutboundBindAddressExit, LINELIST, NULL), OBSOLETE("PathBiasDisableRate"), V(PathBiasCircThreshold, INT, "-1"), @@ -7837,45 +7839,63 @@ getinfo_helper_config(control_connection_t *conn, } /** Parse outbound bind address option lines. If validate_only - * is not 0 update OutboundBindAddressIPv4_ and - * OutboundBindAddressIPv6_ in options. On failure, set - * msg (if provided) to a newly allocated string containing a - * description of the problem and return -1. */ + * is not 0 update OutboundBindAddressORIPv4_, OutboundBindAddressORIPv6_, + * OutboundBindAddressExitIPv4_, and OutboundBindAddressExitIPv6_, + * in options. + * Only one address can be set for any of these four values. + * OutBoundBindAddress will set both (OR and Exit), the OR/Exit version + * only the specific one. + * On failure, set msg (if provided) to a newly allocated string + * containing a description of the problem and return -1. + **/ static int parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) { const config_line_t *lines = options->OutboundBindAddress; - int found_v4 = 0, found_v6 = 0; + int adr_found[4] = {0,0,0,0}; // ORv4, ORv6, Exitv4, Exitv6 if (!validate_only) { - memset(&options->OutboundBindAddressIPv4_, 0, - sizeof(options->OutboundBindAddressIPv4_)); - memset(&options->OutboundBindAddressIPv6_, 0, - sizeof(options->OutboundBindAddressIPv6_)); + memset(&options->OutboundBindAddressORIPv4_, 0, + sizeof(options->OutboundBindAddressORIPv4_)); + memset(&options->OutboundBindAddressORIPv6_, 0, + sizeof(options->OutboundBindAddressORIPv6_)); + memset(&options->OutboundBindAddressExitIPv4_, 0, + sizeof(options->OutboundBindAddressExitIPv4_)); + memset(&options->OutboundBindAddressExitIPv6_, 0, + sizeof(options->OutboundBindAddressExitIPv6_)); } + tor_addr_t addr; + int af; while (lines) { - tor_addr_t addr, *dst_addr = NULL; - int af = tor_addr_parse(&addr, lines->value); + af = tor_addr_parse(&addr, lines->value); switch (af) { case AF_INET: - if (found_v4) { + if (adr_found[0] || adr_found[2]) { if (msg) tor_asprintf(msg, "Multiple IPv4 outbound bind addresses " "configured: %s", lines->value); return -1; } - found_v4 = 1; - dst_addr = &options->OutboundBindAddressIPv4_; + adr_found[0]++; + adr_found[2]++; + if (!validate_only) { + tor_addr_copy(&options->OutboundBindAddressORIPv4_, &addr); + tor_addr_copy(&options->OutboundBindAddressExitIPv4_, &addr); + } break; case AF_INET6: - if (found_v6) { + if (adr_found[1] || adr_found[3]) { if (msg) tor_asprintf(msg, "Multiple IPv6 outbound bind addresses " "configured: %s", lines->value); return -1; } - found_v6 = 1; - dst_addr = &options->OutboundBindAddressIPv6_; + adr_found[1]++; + adr_found[3]++; + if (!validate_only) { + tor_addr_copy(&options->OutboundBindAddressORIPv6_, &addr); + tor_addr_copy(&options->OutboundBindAddressExitIPv6_, &addr); + } break; default: if (msg) @@ -7883,8 +7903,76 @@ parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) lines->value); return -1; } - if (!validate_only) - tor_addr_copy(dst_addr, &addr); + lines = lines->next; + } + // Parse OR addresses + lines = options->OutboundBindAddressOR; + while (lines) { + af = tor_addr_parse(&addr, lines->value); + switch (af) { + case AF_INET: + if (adr_found[0]++) { + if (msg) + tor_asprintf(msg, "Multiple IPv4 OR outbound bind addresses " + "configured: %s", lines->value); + return -1; + } + if (!validate_only) { + tor_addr_copy(&options->OutboundBindAddressORIPv4_, &addr); + } + break; + case AF_INET6: + if (adr_found[1]++) { + if (msg) + tor_asprintf(msg, "Multiple IPv6 OR outbound bind addresses " + "configured: %s", lines->value); + return -1; + } + if (!validate_only) { + tor_addr_copy(&options->OutboundBindAddressORIPv6_, &addr); + } + break; + default: + if (msg) + tor_asprintf(msg, "Outbound OR bind address '%s' didn't parse.", + lines->value); + return -1; + } + lines = lines->next; + } + // Parse exit addresses + lines = options->OutboundBindAddressExit; + while (lines) { + af = tor_addr_parse(&addr, lines->value); + switch (af) { + case AF_INET: + if (adr_found[2]++) { + if (msg) + tor_asprintf(msg, "Multiple IPv4 exit outbound bind addresses " + "configured: %s", lines->value); + return -1; + } + if (!validate_only) { + tor_addr_copy(&options->OutboundBindAddressExitIPv4_, &addr); + } + break; + case AF_INET6: + if (adr_found[3]++) { + if (msg) + tor_asprintf(msg, "Multiple IPv6 exit outbound bind addresses " + "configured: %s", lines->value); + return -1; + } + if (!validate_only) { + tor_addr_copy(&options->OutboundBindAddressExitIPv6_, &addr); + } + break; + default: + if (msg) + tor_asprintf(msg, "Outbound exit bind address '%s' didn't parse.", + lines->value); + return -1; + } lines = lines->next; } return 0; diff --git a/src/or/connection.c b/src/or/connection.c index 5ecd1ad..2efaef5 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -88,6 +88,8 @@ static int connection_read_https_proxy_response(connection_t *conn); static void connection_send_socks5_connect(connection_t *conn); static const char *proxy_type_to_string(int proxy_type); static int get_proxy_type(void); +const tor_addr_t *conn_get_outbound_address(const sa_family_t protocol_family, + const or_options_t *options, const unsigned int conn_type); /** The last addresses that our network interface seemed to have been * binding to. We use this as one way to detect when our IP changes. @@ -1836,6 +1838,47 @@ connection_connect_log_client_use_ip_version(const connection_t *conn) } } +/** Retrieve the outbound address depending on the protocol (IPv4 or IPv6) + * and the connection type (relay, exit, ...) + * Return a socket address or NULL in case nothing is configured. + **/ +const tor_addr_t * +conn_get_outbound_address(const sa_family_t protocol_family, + const or_options_t *options, const unsigned int conn_type) +{ + const tor_addr_t *ext_addr = NULL; + + // If an exit connection, use the exit address (if present) + if (conn_type == CONN_TYPE_EXIT) { + if (protocol_family == AF_INET6) { + if (!tor_addr_is_null(&options->OutboundBindAddressExitIPv6_)) + ext_addr = &options->OutboundBindAddressExitIPv6_; + } else { + if (!tor_addr_is_null(&options->OutboundBindAddressExitIPv4_)) + ext_addr = &options->OutboundBindAddressExitIPv4_; + } + } + // Otherwise, or as fallback if no exit address was specified, + // use the OR address. If it is not an exit connection, but we + // only have an exit address, still use this. + if (ext_addr == NULL) { + if (protocol_family == AF_INET6) { + if (!tor_addr_is_null(&options->OutboundBindAddressORIPv6_)) + ext_addr = &options->OutboundBindAddressORIPv6_; + else if (!tor_addr_is_null(&options->OutboundBindAddressExitIPv6_)) + ext_addr = &options->OutboundBindAddressExitIPv6_; + } else { + if (!tor_addr_is_null(&options->OutboundBindAddressORIPv4_)) + ext_addr = &options->OutboundBindAddressORIPv4_; + else if (!tor_addr_is_null(&options->OutboundBindAddressExitIPv4_)) + ext_addr = &options->OutboundBindAddressExitIPv4_; + } + } + log_info(LD_NET, "Selected OutboundBindAddress %s. ", + fmt_and_decorate_addr(ext_addr)); + return ext_addr; +} + /** Take conn, make a nonblocking socket; try to connect to * addr:port (port arrives in *host order*). If fail, return -1 and if * applicable put your best guess about errno into *socket_error. @@ -1857,26 +1900,15 @@ connection_connect(connection_t *conn, const char *address, struct sockaddr *bind_addr = NULL; struct sockaddr *dest_addr; int dest_addr_len, bind_addr_len = 0; - const or_options_t *options = get_options(); - int protocol_family; /* Log if we didn't stick to ClientUseIPv4/6 or ClientPreferIPv6OR/DirPort */ connection_connect_log_client_use_ip_version(conn); - if (tor_addr_family(addr) == AF_INET6) - protocol_family = PF_INET6; - else - protocol_family = PF_INET; - if (!tor_addr_is_loopback(addr)) { const tor_addr_t *ext_addr = NULL; - if (protocol_family == AF_INET && - !tor_addr_is_null(&options->OutboundBindAddressIPv4_)) - ext_addr = &options->OutboundBindAddressIPv4_; - else if (protocol_family == AF_INET6 && - !tor_addr_is_null(&options->OutboundBindAddressIPv6_)) - ext_addr = &options->OutboundBindAddressIPv6_; + ext_addr = conn_get_outbound_address(tor_addr_family(addr), + get_options(), conn->type); if (ext_addr) { memset(&bind_addr_ss, 0, sizeof(bind_addr_ss)); bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0, diff --git a/src/or/or.h b/src/or/or.h index 6671779..e506029 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3565,10 +3565,20 @@ typedef struct { config_line_t *ControlListenAddress; /** Local address to bind outbound sockets */ config_line_t *OutboundBindAddress; - /** IPv4 address derived from OutboundBindAddress. */ - tor_addr_t OutboundBindAddressIPv4_; - /** IPv6 address derived from OutboundBindAddress. */ - tor_addr_t OutboundBindAddressIPv6_; + /** Local address to bind outbound relay sockets */ + config_line_t *OutboundBindAddressOR; + /** Local address to bind outbound exit sockets */ + config_line_t *OutboundBindAddressExit; + /** IPv4 address derived from OutboundBindAddress or OutboundBindAddressOR */ + tor_addr_t OutboundBindAddressORIPv4_; + /** IPv6 address derived from OutboundBindAddress or OutboundBindAddressOR */ + tor_addr_t OutboundBindAddressORIPv6_; + /** IPv4 address derived from OutboundBindAddress + * or OutboundBindAddressExit. */ + tor_addr_t OutboundBindAddressExitIPv4_; + /** IPv6 address derived from OutboundBindAddress + * or OutboundBindAddressExit. */ + tor_addr_t OutboundBindAddressExitIPv6_; /** Directory server only: which versions of * Tor should we tell users to run? */ config_line_t *RecommendedVersions; diff --git a/src/or/policies.c b/src/or/policies.c index 44a46d2..c840b7c 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -1976,9 +1976,10 @@ policies_copy_ipv4h_to_smartlist(smartlist_t *addr_list, uint32_t ipv4h_addr) } /** Helper function that adds copies of - * or_options->OutboundBindAddressIPv[4|6]_ to a smartlist as tor_addr_t *, as - * long as or_options is non-NULL, and the addresses are not - * tor_addr_is_null(), by passing them to policies_add_addr_to_smartlist. + * or_options->OutboundBindAddress[OR|Exit]IPv[4|6]_ to a + * smartlist as tor_addr_t *, as long as or_options is non-NULL, + * and the addresses are not tor_addr_is_null(), by passing them + * to policies_add_addr_to_smartlist. * * The caller is responsible for freeing all the tor_addr_t* in the smartlist. */ @@ -1987,10 +1988,22 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list, const or_options_t *or_options) { if (or_options) { - policies_copy_addr_to_smartlist(addr_list, - &or_options->OutboundBindAddressIPv4_); - policies_copy_addr_to_smartlist(addr_list, - &or_options->OutboundBindAddressIPv6_); + // Only used for exit policy, so use exit address. But use the OR address, + // if no exit address is present (as this will then be used) + if (!tor_addr_is_null(&or_options->OutboundBindAddressExitIPv4_)) { + policies_copy_addr_to_smartlist(addr_list, + &or_options->OutboundBindAddressExitIPv4_); + } else { + policies_copy_addr_to_smartlist(addr_list, + &or_options->OutboundBindAddressORIPv4_); + } + if (!tor_addr_is_null(&or_options->OutboundBindAddressExitIPv6_)) { + policies_copy_addr_to_smartlist(addr_list, + &or_options->OutboundBindAddressExitIPv6_); + } else { + policies_copy_addr_to_smartlist(addr_list, + &or_options->OutboundBindAddressORIPv6_); + } } } diff --git a/src/test/test_policy.c b/src/test/test_policy.c index 0d4a3b1..77ce18b 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -1081,8 +1081,10 @@ test_policies_getinfo_helper_policies(void *arg) append_exit_policy_string(&mock_my_routerinfo.exit_policy, "reject *6:*"); mock_options.IPv6Exit = 1; - tor_addr_from_ipv4h(&mock_options.OutboundBindAddressIPv4_, TEST_IPV4_ADDR); - tor_addr_parse(&mock_options.OutboundBindAddressIPv6_, TEST_IPV6_ADDR); + tor_addr_from_ipv4h(&mock_options.OutboundBindAddressExitIPv4_, + TEST_IPV4_ADDR); + tor_addr_parse(&mock_options.OutboundBindAddressExitIPv6_, + TEST_IPV6_ADDR); mock_options.ExitPolicyRejectPrivate = 1; mock_options.ExitPolicyRejectLocalInterfaces = 1;