[tor-dev] Request for comments: patch to mark exit traffic for routing and statistical analysis

René Mayrhofer rm at ins.jku.at
Thu Oct 20 09:19:26 UTC 2016


Hi everybody,

Michael Sonntag has extended the patch below to make it configurable and
tested it on a separate instance here in Linz. Seems to work for our use
case. If that seems a good option, then we'd like to request review for
upstream inclusion.

------------------------------------------------------------------------------------------
Explanation of the patch:
* Two new configuration options, but retain the old option for
compatibility. Old configurations therefore remain fully valid.
Configuration parsing is therefore a bit long, but not complex.
* Ease of update: The old and new options can exist simultaneously, as
long as they are for different protocols (IPv4 or IPv6)
* Resiliency: If one address is missing, the other is substituted.
Things should work at least somehow, even if some configuration is
absent. Use the default only if no configuration for output traffic
exists at all.
* New function introduced for selecting the address to bind to, to
factor out this logic.
* Changes to other places the address was used before (policies.c and
test_policy.c).
* Documentation of new options and sample configuration added
------------------------------------------------------------------------------------------

best regards,
Rene

On 2016-09-26 00:54, teor wrote:
> I'm not sure if we want to tag Tor traffic with QoS values at Exits.
> Any tagging carries some degree of risk, because it makes traffic look
> more unique. I'm not sure how much of a risk QoS tagging represents.
>
> I would prefer to add config options OutboundBindAddressOR and
> OutboundBindAddressExit, which would default to OutboundBindAddress
> when not set. (And could be specified twice, once for IPv4, and once for
> IPv6.)
>
> The one concern I have about this is that Tor-over-Tor would stick out more,
> as it would look like Tor coming out the OutboundBindAddressExit IP.
> But we don't encourage Tor-over-Tor anyway.
>
> I'd recommend a patch that modifies this section in connection_connect to
> use OutboundBindAddressOR and OutboundBindAddressExit, preferably
> with the Exit/OR/(all) and IPv4/IPv6 logic refactored into its own function.
>
>   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_;
>     if (ext_addr) {
>       memset(&bind_addr_ss, 0, sizeof(bind_addr_ss));
>       bind_addr_len = tor_addr_to_sockaddr(ext_addr, 0,
>                                            (struct sockaddr *) &bind_addr_ss,
>                                            sizeof(bind_addr_ss));
>       if (bind_addr_len == 0) {
>         log_warn(LD_NET,
>                  "Error converting OutboundBindAddress %s into sockaddr. "
>                  "Ignoring.", fmt_and_decorate_addr(ext_addr));
>       } else {
>         bind_addr = (struct sockaddr *)&bind_addr_ss;
>       }
>     }
>   }
>
>> Ideally, we would use 2 different providers to even further
>> compartmentalize "incoming" (i.e. encrypted Tor network) from "outgoing"
>> (for our exit node, mostly clearnet) traffic and make traffic
>> correlation harder (doesn't help against a global adversary as we know,
>> but at least a single ISP would not be able to directly correlate both
>> sides of the relay). Although we don't have two different providers at
>> this point, we still use two different network interfaces with
>> associated IP addresses (one advertised as the Tor node for incoming
>> traffic, and the other one with the default route assigned for outgoing
>> traffic).
> This sounds like an interesting setup.
> I'd be keen to see how it works out.
>
> Some Exit providers (typically with their own AS) peer with multiple
> other providers, because this makes it harder for a single network tap to
> capture all their traffic.
>
> Not quite the same as your setup, because OR and Exit traffic goes over
> all the links, rather than each going over a separate link.
>
>> ...
>> [The patch]
>> Currently, both (clearnet) exit traffic as well as encrypted Tor traffic
>> (to other nodes and hidden services) will use the outgoing interfaces,
>> as the Tor daemon simply creates TCP sockets and uses the default route
>> (which points at the outgoing interface). A patch as suggested by
>> grarpamp above could solve that issue. In the meantime, we have created
>> a slightly hacky patch as attached. The simplest way to only record exit
>> traffic and separate that from outgoing Tor traffic seemed to mark those
>> packets with a ToS value - which, as far as we can see, can be done with
>> a minimally invasive patch adding that option at a single point in
>> connection.c. At the moment, we use this ToS value in a filter
>> expression at the monitoring server to make sure that we do not analyze
>> outgoing Tor traffic. We also plan to also use it for policy routing
>> rules at the Linux kernel level to send outgoing Tor traffic back out
>> the "incoming" interface (to distinguish between Tor traffic and clear
>> traffic). When that works, the ToS flag can actually be removed again
>> before the packets leave the Tor node.
> Binding to different IP addresses can also be used for filtering and
> traffic redirection. Does having separate bind addresses for OR and Exit
> traffic work for your use case?
-------------- next part --------------
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 <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. */
+ * is not 0 update OutboundBindAddressORIPv4_, OutboundBindAddressORIPv6_,
+ * OutboundBindAddressExitIPv4_, and OutboundBindAddressExitIPv6_,
+ * in <b>options</b>.
+ * 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 <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)
 {
   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 *<b>socket_error</b>.
@@ -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;
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: OpenPGP digital signature
URL: <http://lists.torproject.org/pipermail/tor-dev/attachments/20161020/3a803b33/attachment-0001.sig>


More information about the tor-dev mailing list