commit 81c78ec7556b4071b4eb1f60c4867d6ba8cf4685 Author: Nick Mathewson nickm@torproject.org Date: Fri Jan 27 08:05:29 2017 -0500
Outbindbindaddress variants for Exit and OR.
Allow separation of exit and relay traffic to different source IP addresses (Ticket #17975). Written by Michael Sonntag. --- changes/change_separate_exit_and_relay.txt | 2 + doc/tor.1.txt | 14 ++++ src/config/torrc.sample.in | 7 +- src/or/config.c | 108 ++++++++++++++++++----------- src/or/connection.c | 60 ++++++++++++---- src/or/or.h | 18 +++-- src/or/policies.c | 28 ++++---- src/test/test_policy.c | 8 ++- 8 files changed, 171 insertions(+), 74 deletions(-)
diff --git a/changes/change_separate_exit_and_relay.txt b/changes/change_separate_exit_and_relay.txt new file mode 100644 index 0000000..28db1d2 --- /dev/null +++ b/changes/change_separate_exit_and_relay.txt @@ -0,0 +1,2 @@ +- Minor features: + - Allow separation of exit and relay traffic to different source IP addresses (Ticket #17975). Written by Michael Sonntag. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index da2a61f..de2e2b4 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -640,6 +640,20 @@ 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 overrides **OutboundBindAddress** for the same + IP version. 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 overrides **OutboundBindAddress** for the same IP version. 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. Can not be changed while tor is running. diff --git a/src/config/torrc.sample.in b/src/config/torrc.sample.in index 5328206..3777744 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 e5078ad..40a6573 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -411,6 +411,8 @@ static config_var_t option_vars_[] = { V(ORListenAddress, LINELIST, NULL), VPORT(ORPort), V(OutboundBindAddress, LINELIST, NULL), + V(OutboundBindAddressOR, LINELIST, NULL), + V(OutboundBindAddressExit, LINELIST, NULL),
OBSOLETE("PathBiasDisableRate"), V(PathBiasCircThreshold, INT, "-1"), @@ -7917,60 +7919,84 @@ getinfo_helper_config(control_connection_t *conn, return 0; }
-/** Parse outbound bind address option lines. If <b>validate_only</b> - * is not 0 update OutboundBindAddressIPv4_ and - * OutboundBindAddressIPv6_ in <b>options</b>. On failure, set - * <b>msg</b> (if provided) to a newly allocated string containing a - * description of the problem and return -1. */ +/* Check whether an address has already been set against the options + * depending on address family and destination type. Any exsting + * value will lead to a fail, even if it is the same value. If not + * set and not only validating, copy it into this location too. + * Returns 0 on success or -1 if this address is already set. + */ static int -parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) +verify_and_store_outbound_address(sa_family_t family, tor_addr_t *addr, + outbound_addr_t type, or_options_t *options, int validate_only) { - const config_line_t *lines = options->OutboundBindAddress; - int found_v4 = 0, found_v6 = 0; - + if (type<0 || type>=OUTBOUND_ADDR_MAX + || (family!=AF_INET && family!=AF_INET6)) { + return -1; + } + int fam_index=0; + if (family==AF_INET6) { + fam_index=1; + } + tor_addr_t *dest=&options->OutboundBindAddresses[type][fam_index]; + if (!tor_addr_is_null(dest)) { + return -1; + } if (!validate_only) { - memset(&options->OutboundBindAddressIPv4_, 0, - sizeof(options->OutboundBindAddressIPv4_)); - memset(&options->OutboundBindAddressIPv6_, 0, - sizeof(options->OutboundBindAddressIPv6_)); + tor_addr_copy(dest, addr); } + return 0; +} + +/* Parse a list of address lines for a specific destination type. + * Will store them into the options if not validate_only. If a + * problem occurs, a suitable error message is store in msg. + * Returns 0 on success or -1 if any address is already set. + */ +static int +parse_outbound_address_lines(const config_line_t *lines, outbound_addr_t type, + or_options_t *options, int validate_only, char **msg) +{ + tor_addr_t addr; + sa_family_t family; while (lines) { - tor_addr_t addr, *dst_addr = NULL; - int af = tor_addr_parse(&addr, lines->value); - switch (af) { - case AF_INET: - if (found_v4) { - if (msg) - tor_asprintf(msg, "Multiple IPv4 outbound bind addresses " - "configured: %s", lines->value); - return -1; - } - found_v4 = 1; - dst_addr = &options->OutboundBindAddressIPv4_; - break; - case AF_INET6: - if (found_v6) { - if (msg) - tor_asprintf(msg, "Multiple IPv6 outbound bind addresses " - "configured: %s", lines->value); - return -1; - } - found_v6 = 1; - dst_addr = &options->OutboundBindAddressIPv6_; - break; - default: + family = tor_addr_parse(&addr, lines->value); + if (verify_and_store_outbound_address(family, &addr, type, + options, validate_only)) { if (msg) - tor_asprintf(msg, "Outbound bind address '%s' didn't parse.", - lines->value); + tor_asprintf(msg, "Multiple%s%s outbound bind addresses " + "configured: %s", + family==AF_INET?" IPv4":(family==AF_INET6?" IPv6":""), + type==OUTBOUND_ADDR_OR?" OR": + (type==OUTBOUND_ADDR_EXIT?" exit":""), lines->value); return -1; } - if (!validate_only) - tor_addr_copy(dst_addr, &addr); lines = lines->next; } return 0; }
+/** Parse outbound bind address option lines. If <b>validate_only</b> + * is not 0 update OutboundBindAddresses in <b>options</b>. + * Only one address can be set for any of these values. + * On failure, set <b>msg</b> (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) +{ + if (!validate_only) { + memset(&options->OutboundBindAddresses, 0, + sizeof(options->OutboundBindAddresses)); + } + parse_outbound_address_lines(options->OutboundBindAddress, + OUTBOUND_ADDR_EXIT_AND_OR, options, validate_only, msg); + parse_outbound_address_lines(options->OutboundBindAddressOR, + OUTBOUND_ADDR_OR, options, validate_only, msg); + parse_outbound_address_lines(options->OutboundBindAddressExit, + OUTBOUND_ADDR_EXIT, options, validate_only, msg); + return 0; +} + /** Load one of the geoip files, <a>family</a> determining which * one. <a>default_fname</a> is used if on Windows and * <a>fname</a> equals "<default>". */ diff --git a/src/or/connection.c b/src/or/connection.c index 7e0ee45..4421534 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -134,6 +134,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(sa_family_t family, + const or_options_t *options, 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. @@ -1771,7 +1773,7 @@ connection_connect_sockaddr,(connection_t *conn,
/* * We've got the socket open; give the OOS handler a chance to check - * against configuured maximum socket number, but tell it no exhaustion + * against configured maximum socket number, but tell it no exhaustion * failure. */ connection_check_oos(get_n_open_sockets(), 0); @@ -1890,6 +1892,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(sa_family_t family, + const or_options_t *options, unsigned int conn_type) +{ + const tor_addr_t *ext_addr = NULL; + + int fam_index=0; + if (family==AF_INET6) { + fam_index=1; + } + // If an exit connection, use the exit address (if present) + if (conn_type == CONN_TYPE_EXIT) { + if (!tor_addr_is_null( + &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT][fam_index])) { + ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT] + [fam_index]; + } else if (!tor_addr_is_null( + &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR] + [fam_index])) { + ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR] + [fam_index]; + } + } else { // All non-exit connections + if (!tor_addr_is_null( + &options->OutboundBindAddresses[OUTBOUND_ADDR_OR][fam_index])) { + ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_OR] + [fam_index]; + } else if (!tor_addr_is_null( + &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR] + [fam_index])) { + ext_addr = &options->OutboundBindAddresses[OUTBOUND_ADDR_EXIT_AND_OR] + [fam_index]; + } + } + 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 *<b>socket_error</b>. @@ -1911,26 +1954,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 80ce704..18fff78 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3545,6 +3545,12 @@ typedef struct routerset_t routerset_t; * to pick its own port. */ #define CFG_AUTO_PORT 0xc4005e
+/** Enumeration of outbound address configuration types: + * Exit-only, OR-only, or both */ +typedef enum {OUTBOUND_ADDR_EXIT, OUTBOUND_ADDR_OR, + OUTBOUND_ADDR_EXIT_AND_OR, + OUTBOUND_ADDR_MAX} outbound_addr_t; + /** Configuration options for a Tor process. */ typedef struct { uint32_t magic_; @@ -3628,10 +3634,14 @@ 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; + /** Addresses derived from the various OutboundBindAddress lines. + * [][0] is IPv4, [][1] is IPv6 + */ + tor_addr_t OutboundBindAddresses[OUTBOUND_ADDR_MAX][2]; /** 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 84600f7..aea1b11 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -2019,10 +2019,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. +/** Helper function that adds copies of or_options->OutboundBindAddresses + * 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. */ @@ -2031,10 +2031,14 @@ 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_); + for (int i=0;i<OUTBOUND_ADDR_MAX;i++) { + for (int j=0;j<2;j++) { + if (!tor_addr_is_null(&or_options->OutboundBindAddresses[i][j])) { + policies_copy_addr_to_smartlist(addr_list, + &or_options->OutboundBindAddresses[i][j]); + } + } + } } }
@@ -2051,10 +2055,10 @@ policies_copy_outbound_addresses_to_smartlist(smartlist_t *addr_list, * - if ipv6_local_address is non-NULL, and not the null tor_addr_t, add it * to the list of configured addresses. * If <b>or_options->ExitPolicyRejectLocalInterfaces</b> is true: - * - if or_options->OutboundBindAddressIPv4_ is not the null tor_addr_t, add - * it to the list of configured addresses. - * - if or_options->OutboundBindAddressIPv6_ is not the null tor_addr_t, add - * it to the list of configured addresses. + * - if or_options->OutboundBindAddresses[][0] (=IPv4) is not the null + * tor_addr_t, add it to the list of configured addresses. + * - if or_options->OutboundBindAddresses[][1] (=IPv6) is not the null + * tor_addr_t, add it to the list of configured addresses. * * If <b>or_options->BridgeRelay</b> is false, append entries of default * Tor exit policy into <b>result</b> smartlist. diff --git a/src/test/test_policy.c b/src/test/test_policy.c index 4df40f6..71a3111 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -1083,8 +1083,12 @@ 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.OutboundBindAddresses[OUTBOUND_ADDR_EXIT][0], + TEST_IPV4_ADDR); + tor_addr_parse( + &mock_options.OutboundBindAddresses[OUTBOUND_ADDR_EXIT][1], + TEST_IPV6_ADDR);
mock_options.ExitPolicyRejectPrivate = 1; mock_options.ExitPolicyRejectLocalInterfaces = 1;