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;