Browse Source

Outbindbindaddress variants for Exit and OR.

Allow separation of exit and relay traffic to different source IP
addresses (Ticket #17975). Written by Michael Sonntag.
Nick Mathewson 7 years ago
parent
commit
81c78ec755

+ 2 - 0
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.

+ 14 - 0
doc/tor.1.txt

@@ -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.

+ 6 - 1
src/config/torrc.sample.in

@@ -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

+ 67 - 41
src/or/config.c

@@ -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,60 +7919,84 @@ getinfo_helper_config(control_connection_t *conn,
   return 0;
   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
 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) {
   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) {
   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)
       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;
       return -1;
     }
     }
-    if (!validate_only)
-      tor_addr_copy(dst_addr, &addr);
     lines = lines->next;
     lines = lines->next;
   }
   }
   return 0;
   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
 /** Load one of the geoip files, <a>family</a> determining which
  * one. <a>default_fname</a> is used if on Windows and
  * one. <a>default_fname</a> is used if on Windows and
  * <a>fname</a> equals "<default>". */
  * <a>fname</a> equals "<default>". */

+ 46 - 14
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 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 &&
-        !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) {
     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,

+ 14 - 4
src/or/or.h

@@ -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. */
-  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
   /** 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;

+ 16 - 12
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.
  * 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) {
-    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
  *  - 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
- *    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
  * 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.

+ 6 - 2
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:*");
   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_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.ExitPolicyRejectPrivate = 1;
   mock_options.ExitPolicyRejectLocalInterfaces = 1;
   mock_options.ExitPolicyRejectLocalInterfaces = 1;