Outbindbindaddress variants for Exit and OR.

Allow separation of exit and relay traffic to different source IP
addresses (Ticket #17975). Written by Michael Sonntag.
This commit is contained in:
Nick Mathewson 2017-01-27 08:05:29 -05:00
parent ad382049ed
commit 81c78ec755
8 changed files with 175 additions and 78 deletions

View File

@ -0,0 +1,2 @@
- Minor features:
- Allow separation of exit and relay traffic to different source IP addresses (Ticket #17975). Written by Michael Sonntag.

View File

@ -640,6 +640,20 @@ GENERAL OPTIONS
This setting will be ignored for connections to the loopback addresses This setting will be ignored for connections to the loopback addresses
(127.0.0.0/8 and ::1). (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__:: [[PidFile]] **PidFile** __FILE__::
On startup, write our PID to FILE. On clean shutdown, remove On startup, write our PID to FILE. On clean shutdown, remove
FILE. Can not be changed while tor is running. FILE. Can not be changed while tor is running.

View File

@ -95,7 +95,12 @@
## If you have multiple network interfaces, you can specify one for ## If you have multiple network interfaces, you can specify one for
## outgoing traffic to use. ## 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. ## 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 ## Nicknames must be between 1 and 19 characters inclusive, and must

View File

@ -411,6 +411,8 @@ static config_var_t option_vars_[] = {
V(ORListenAddress, LINELIST, NULL), V(ORListenAddress, LINELIST, NULL),
VPORT(ORPort), VPORT(ORPort),
V(OutboundBindAddress, LINELIST, NULL), V(OutboundBindAddress, LINELIST, NULL),
V(OutboundBindAddressOR, LINELIST, NULL),
V(OutboundBindAddressExit, LINELIST, NULL),
OBSOLETE("PathBiasDisableRate"), OBSOLETE("PathBiasDisableRate"),
V(PathBiasCircThreshold, INT, "-1"), V(PathBiasCircThreshold, INT, "-1"),
@ -7917,57 +7919,81 @@ getinfo_helper_config(control_connection_t *conn,
return 0; return 0;
} }
/* 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
verify_and_store_outbound_address(sa_family_t family, tor_addr_t *addr,
outbound_addr_t type, or_options_t *options, int validate_only)
{
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) {
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) {
family = tor_addr_parse(&addr, lines->value);
if (verify_and_store_outbound_address(family, &addr, type,
options, validate_only)) {
if (msg)
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;
}
lines = lines->next;
}
return 0;
}
/** Parse outbound bind address option lines. If <b>validate_only</b> /** Parse outbound bind address option lines. If <b>validate_only</b>
* is not 0 update OutboundBindAddressIPv4_ and * is not 0 update OutboundBindAddresses in <b>options</b>.
* OutboundBindAddressIPv6_ in <b>options</b>. On failure, set * Only one address can be set for any of these values.
* <b>msg</b> (if provided) to a newly allocated string containing a * On failure, set <b>msg</b> (if provided) to a newly allocated string
* description of the problem and return -1. */ * containing a description of the problem and return -1.
*/
static int static int
parse_outbound_addresses(or_options_t *options, int validate_only, char **msg) 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;
if (!validate_only) { if (!validate_only) {
memset(&options->OutboundBindAddressIPv4_, 0, memset(&options->OutboundBindAddresses, 0,
sizeof(options->OutboundBindAddressIPv4_)); sizeof(options->OutboundBindAddresses));
memset(&options->OutboundBindAddressIPv6_, 0,
sizeof(options->OutboundBindAddressIPv6_));
}
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:
if (msg)
tor_asprintf(msg, "Outbound bind address '%s' didn't parse.",
lines->value);
return -1;
}
if (!validate_only)
tor_addr_copy(dst_addr, &addr);
lines = lines->next;
} }
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; return 0;
} }

View File

@ -134,6 +134,8 @@ static int connection_read_https_proxy_response(connection_t *conn);
static void connection_send_socks5_connect(connection_t *conn); static void connection_send_socks5_connect(connection_t *conn);
static const char *proxy_type_to_string(int proxy_type); static const char *proxy_type_to_string(int proxy_type);
static int get_proxy_type(void); 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 /** 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. * 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 * 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. * failure.
*/ */
connection_check_oos(get_n_open_sockets(), 0); 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 /** Take conn, make a nonblocking socket; try to connect to
* addr:port (port arrives in *host order*). If fail, return -1 and if * 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>. * 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 *bind_addr = NULL;
struct sockaddr *dest_addr; struct sockaddr *dest_addr;
int dest_addr_len, bind_addr_len = 0; 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 /* Log if we didn't stick to ClientUseIPv4/6 or ClientPreferIPv6OR/DirPort
*/ */
connection_connect_log_client_use_ip_version(conn); 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)) { if (!tor_addr_is_loopback(addr)) {
const tor_addr_t *ext_addr = NULL; const tor_addr_t *ext_addr = NULL;
if (protocol_family == AF_INET && ext_addr = conn_get_outbound_address(tor_addr_family(addr), get_options(),
!tor_addr_is_null(&options->OutboundBindAddressIPv4_)) conn->type);
ext_addr = &options->OutboundBindAddressIPv4_;
else if (protocol_family == AF_INET6 &&
!tor_addr_is_null(&options->OutboundBindAddressIPv6_))
ext_addr = &options->OutboundBindAddressIPv6_;
if (ext_addr) { if (ext_addr) {
memset(&bind_addr_ss, 0, sizeof(bind_addr_ss)); memset(&bind_addr_ss, 0, sizeof(bind_addr_ss));
bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0, bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0,

View File

@ -3545,6 +3545,12 @@ typedef struct routerset_t routerset_t;
* to pick its own port. */ * to pick its own port. */
#define CFG_AUTO_PORT 0xc4005e #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. */ /** Configuration options for a Tor process. */
typedef struct { typedef struct {
uint32_t magic_; uint32_t magic_;
@ -3628,10 +3634,14 @@ typedef struct {
config_line_t *ControlListenAddress; config_line_t *ControlListenAddress;
/** Local address to bind outbound sockets */ /** Local address to bind outbound sockets */
config_line_t *OutboundBindAddress; config_line_t *OutboundBindAddress;
/** IPv4 address derived from OutboundBindAddress. */ /** Local address to bind outbound relay sockets */
tor_addr_t OutboundBindAddressIPv4_; config_line_t *OutboundBindAddressOR;
/** IPv6 address derived from OutboundBindAddress. */ /** Local address to bind outbound exit sockets */
tor_addr_t OutboundBindAddressIPv6_; 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 /** Directory server only: which versions of
* Tor should we tell users to run? */ * Tor should we tell users to run? */
config_line_t *RecommendedVersions; config_line_t *RecommendedVersions;

View File

@ -2019,10 +2019,10 @@ policies_copy_ipv4h_to_smartlist(smartlist_t *addr_list, uint32_t ipv4h_addr)
} }
} }
/** Helper function that adds copies of /** Helper function that adds copies of or_options->OutboundBindAddresses
* or_options->OutboundBindAddressIPv[4|6]_ to a smartlist as tor_addr_t *, as * to a smartlist as tor_addr_t *, as long as or_options is non-NULL, and
* long as or_options is non-NULL, and the addresses are not * the addresses are not tor_addr_is_null(), by passing them to
* tor_addr_is_null(), by passing them to policies_add_addr_to_smartlist. * policies_add_addr_to_smartlist.
* *
* The caller is responsible for freeing all the tor_addr_t* in the 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) const or_options_t *or_options)
{ {
if (or_options) { if (or_options) {
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, policies_copy_addr_to_smartlist(addr_list,
&or_options->OutboundBindAddressIPv4_); &or_options->OutboundBindAddresses[i][j]);
policies_copy_addr_to_smartlist(addr_list, }
&or_options->OutboundBindAddressIPv6_); }
}
} }
} }
@ -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 * - if ipv6_local_address is non-NULL, and not the null tor_addr_t, add it
* to the list of configured addresses. * to the list of configured addresses.
* If <b>or_options->ExitPolicyRejectLocalInterfaces</b> is true: * If <b>or_options->ExitPolicyRejectLocalInterfaces</b> is true:
* - if or_options->OutboundBindAddressIPv4_ is not the null tor_addr_t, add * - if or_options->OutboundBindAddresses[][0] (=IPv4) is not the null
* it to the list of configured addresses. * tor_addr_t, add it to the list of configured addresses.
* - if or_options->OutboundBindAddressIPv6_ is not the null tor_addr_t, add * - if or_options->OutboundBindAddresses[][1] (=IPv6) is not the null
* it to the list of configured addresses. * tor_addr_t, add it to the list of configured addresses.
* *
* If <b>or_options->BridgeRelay</b> is false, append entries of default * If <b>or_options->BridgeRelay</b> is false, append entries of default
* Tor exit policy into <b>result</b> smartlist. * Tor exit policy into <b>result</b> smartlist.

View File

@ -1083,8 +1083,12 @@ test_policies_getinfo_helper_policies(void *arg)
append_exit_policy_string(&mock_my_routerinfo.exit_policy, "reject *6:*"); append_exit_policy_string(&mock_my_routerinfo.exit_policy, "reject *6:*");
mock_options.IPv6Exit = 1; mock_options.IPv6Exit = 1;
tor_addr_from_ipv4h(&mock_options.OutboundBindAddressIPv4_, TEST_IPV4_ADDR); tor_addr_from_ipv4h(
tor_addr_parse(&mock_options.OutboundBindAddressIPv6_, TEST_IPV6_ADDR); &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.ExitPolicyRejectPrivate = 1;
mock_options.ExitPolicyRejectLocalInterfaces = 1; mock_options.ExitPolicyRejectLocalInterfaces = 1;