Explorar o código

Merge remote-tracking branch 'public/prop171_v2'

Nick Mathewson %!s(int64=13) %!d(string=hai) anos
pai
achega
195bcb6150
Modificáronse 18 ficheiros con 1358 adicións e 383 borrados
  1. 22 0
      changes/prop171
  2. 79 25
      doc/tor.1.txt
  3. 43 0
      src/common/util.c
  4. 2 0
      src/common/util.h
  5. 4 0
      src/or/circuitlist.c
  6. 125 22
      src/or/circuituse.c
  7. 387 64
      src/or/config.c
  8. 2 0
      src/or/config.h
  9. 258 245
      src/or/connection.c
  10. 1 0
      src/or/connection.h
  11. 227 2
      src/or/connection_edge.c
  12. 12 1
      src/or/connection_edge.h
  13. 7 1
      src/or/directory.c
  14. 22 7
      src/or/dnsserv.c
  15. 12 1
      src/or/main.c
  16. 1 0
      src/or/main.h
  17. 151 10
      src/or/or.h
  18. 3 5
      src/or/router.c

+ 22 - 0
changes/prop171

@@ -0,0 +1,22 @@
+  o Major features:
+    - You can now configure Tor so that streams from different
+      applications are isolated on different circuits, to prevent an
+      attacker who sees your streams leaving an exit node from linking
+      your sessions to one another.  To do this, choose some way to
+      distinguish the applications -- have them connect to different
+      SocksPorts, or have one of them use SOCKS4 while the other uses
+      SOCKS5, or have them pass different authentication strings to
+      the SOCKS proxy.  Then use the new SocksPort syntax to configure
+      the degree of isolation you need. This implements Proposal 171.
+
+  o Minor features:
+    - There's a new syntax for specifying multiple client ports (such as
+      SOCKSPort, TransPort, DNSPort, NATDPort): you can now just declare
+      multiple ...Port entries with full addr:port syntax on each.
+      The old ...ListenAddress format is still supported, but you can't
+      mix it with the new SOCKSPort syntax.
+
+  o Code simplifications and refactoring:
+    - Rewrote the listener-selection logic so that parsing which ports
+      we want to listen on is now separate form binding to the ports
+      we want.

+ 79 - 25
doc/tor.1.txt

@@ -464,7 +464,7 @@ CLIENT OPTIONS
 --------------
 
 The following options are useful only for clients (that is, if
-**SocksPort** is non-zero):
+**SocksPort**, **TransPort**, **DNSPort**, or **NATDPort** is non-zero):
 
 **AllowInvalidNodes** **entry**|**exit**|**middle**|**introduction**|**rendezvous**|**...**::
     If some Tor servers are obviously not working right, the directory
@@ -682,17 +682,49 @@ The following options are useful only for clients (that is, if
     the same circuit. Currently, two addresses are "too close" if they lie in
     the same /16 range. (Default: 1)
 
-**SocksPort** __PORT__|**auto**::
-    Advertise this port to listen for connections from Socks-speaking
+**SOCKSPort** \['address':]__port__|**auto** [_isolation flags_]::
+    Open this port to listen for connections from SOCKS-speaking
     applications. Set this to 0 if you don't want to allow application
     connections via SOCKS. Set it to "auto" to have Tor pick a port for
-    you. (Default: 9050)
-
-**SocksListenAddress** __IP__[:__PORT__]::
+    you. This directive can be specified multiple times to bind
+    to multiple addresses/ports. (Default: 9050) +
+ +
+    The _isolation flags_ arguments give Tor rules for which streams
+    received on this SOCKSPort are allowed to share circuits with one
+    another.  Recognized isolation flags are:
+    **IsolateClientAddr**;;
+        Don't share a circuits with streams from a different
+        client address.  (On by default and strongly recommended;
+        you can disable it with **NoIsolateClientAddr**.)
+    **IsolateSOCKSAuth**;;
+        Don't share a circuits with streams for which different
+        SOCKS authentication was provided. (On by default;
+        you can disable it with **NoIsolateSOCKSAuth**.)
+    **IsolateClientProtocol**;;
+        Don't share circuits with streams using a different protocol.
+        (SOCKS 4, SOCKS 5, TransPort connections, NATDPort connections,
+        and DNSPort requests are all considered to be different protocols.)
+    **IsolateDestPort**;;
+        Don't share a circuits with streams targetting a different
+        destination port.
+    **IsolateDestAddr**;;
+        Don't share a circuits with streams targetting a different
+        destination address.
+    **SessionGroup=**__INT__;;
+        If no other isolation rules would prevent it, allow streams
+        on this port to share circuits with streams from every other
+        port with the same session group.  (By default, streams received
+        on different ports are always isolated from one another.)
+
+**SOCKSListenAddress** __IP__[:__PORT__]::
     Bind to this address to listen for connections from Socks-speaking
     applications. (Default: 127.0.0.1) You can also specify a port (e.g.
     192.168.0.1:9100). This directive can be specified multiple times to bind
-    to multiple addresses/ports.
+    to multiple addresses/ports.  (DEPRECATED: As of 0.2.3.x-alpha, you can
+    now use multiple SOCKSPort entries, and provide addresses for SOCKSPort
+    entries, so SOCKSListenAddress no longer has a purpose.  For backward
+    compatibility, SOCKSListenAddress is only allowed when SOCKSPort is just
+    a port number.)
 
 **SocksPolicy** __policy__,__policy__,__...__::
     Set an entrance policy for this server, to limit who can connect to the
@@ -795,28 +827,44 @@ The following options are useful only for clients (that is, if
     operating as a relay, and it will never use the public key step if it
     doesn't yet know the onion key of the first hop. (Default: 1)
 
-**TransPort** __PORT__|**auto**::
-    If non-zero, enables transparent proxy support on __PORT__ (by convention,
-    9040). Requires OS support for transparent proxies, such as BSDs' pf or
+**TransPort**  \['address':]__port__|**auto** [_isolation flags_]::
+    Open this port to listen for transparent proxy connections.  Set this to
+    0 if you don't want to allow transparent proxy connections.  Set the port
+    to "auto" to have Tor pick a port for you. This directive can be
+    specified multiple times to bind to multiple addresses/ports.  See 
+    SOCKSPort for an explanation of isolation flags. +
+ +
+    TransPort requires OS support for transparent proxies, such as BSDs' pf or
     Linux's IPTables. If you're planning to use Tor as a transparent proxy for
     a network, you'll want to examine and change VirtualAddrNetwork from the
     default setting. You'll also want to set the TransListenAddress option for
-    the network you'd like to proxy.  Set it to "auto" to have Tor pick a
-    port for you.  (Default: 0).
+    the network you'd like to proxy. (Default: 0).
 
 **TransListenAddress** __IP__[:__PORT__]::
     Bind to this address to listen for transparent proxy connections. (Default:
     127.0.0.1). This is useful for exporting a transparent proxy server to an
-    entire network.
-
-**NATDPort** __PORT__|**auto**::
-    Allow old versions of ipfw (as included in old versions of FreeBSD, etc.)
-    to send connections through Tor using the NATD protocol. This option is
-    only for people who cannot use TransPort.  Set it to "auto" to have Tor
-    pick a port for you. (Default: 0)
+    entire network. (DEPRECATED: As of 0.2.3.x-alpha, you can
+    now use multiple TransPort entries, and provide addresses for TransPort
+    entries, so TransListenAddress no longer has a purpose.  For backward
+    compatibility, TransListenAddress is only allowed when TransPort is just
+    a port number.)
+
+**NATDPort** \['address':]__port__|**auto** [_isolation flags_]::
+    Open this port to listen for connections from old versions of ipfw (as
+    included in old versions of FreeBSD, etc) using the NATD protocol.
+    Use 0 if you don't want to allow NATD connections.  Set the port
+    to "auto" to have Tor pick a port for you. This directive can be
+    specified multiple times to bind to multiple addresses/ports.  See
+    SOCKSPort for an explanation of isolation flags. +
+ +
+    This option is only for people who cannot use TransPort. (Default: 0)
 
 **NATDListenAddress** __IP__[:__PORT__]::
-    Bind to this address to listen for NATD connections. (Default: 127.0.0.1).
+    Bind to this address to listen for NATD connections. (DEPRECATED: As of
+    0.2.3.x-alpha, you can now use multiple NATDPort entries, and provide
+    addresses for NATDPort entries, so NATDListenAddress no longer has a
+    purpose.  For backward compatibility, NATDListenAddress is only allowed
+    when NATDPort is just a port number.)
 
 **AutomapHostsOnResolve** **0**|**1**::
     When this option is enabled, and we get a request to resolve an address
@@ -829,13 +877,19 @@ The following options are useful only for clients (that is, if
     A comma-separated list of suffixes to use with **AutomapHostsOnResolve**.
     The "." suffix is equivalent to "all addresses." (Default: .exit,.onion).
 
-**DNSPort** __PORT__|**auto**::
-    If non-zero, Tor listens for UDP DNS requests on this port and resolves
-    them anonymously.  Set it to "auto" to have Tor pick a port for
-    you. (Default: 0).
+**DNSPort** \['address':]__port__|**auto** [_isolation flags_]::
+    If non-zero, open this port to listen for UDP DNS requests, and resolve
+    them anonymously.  Set the port to "auto" to have Tor pick a port for
+    you. This directive can be specified multiple times to bind to multiple
+    addresses/ports. See SOCKSPort for an explanation of isolation
+    flags. (Default: 0).
 
 **DNSListenAddress** __IP__[:__PORT__]::
-    Bind to this address to listen for DNS connections. (Default: 127.0.0.1).
+    Bind to this address to listen for DNS connections. (DEPRECATED: As of
+    0.2.3.x-alpha, you can now use multiple DNSPort entries, and provide
+    addresses for DNSPort entries, so DNSListenAddress no longer has a
+    purpose.  For backward compatibility, DNSListenAddress is only allowed
+    when DNSPort is just a port number.)
 
 **ClientDNSRejectInternalAddresses** **0**|**1**::
     If true, Tor does not believe any anonymously retrieved DNS answer that

+ 43 - 0
src/common/util.c

@@ -412,6 +412,32 @@ round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor)
   return number;
 }
 
+/** Return the number of bits set in <b>v</b>. */
+int
+n_bits_set_u8(uint8_t v)
+{
+  static const int nybble_table[] = {
+    0, /* 0000 */
+    1, /* 0001 */
+    1, /* 0010 */
+    2, /* 0011 */
+    1, /* 0100 */
+    2, /* 0101 */
+    2, /* 0110 */
+    3, /* 0111 */
+    1, /* 1000 */
+    2, /* 1001 */
+    2, /* 1010 */
+    3, /* 1011 */
+    2, /* 1100 */
+    3, /* 1101 */
+    3, /* 1110 */
+    4, /* 1111 */
+  };
+
+  return nybble_table[v & 15] + nybble_table[v>>4];
+}
+
 /* =====
  * String manipulation
  * ===== */
@@ -495,6 +521,23 @@ tor_strisnonupper(const char *s)
   return 1;
 }
 
+/** As strcmp, except that either string may be NULL.  The NULL string is
+ * considered to be before any non-NULL string. */
+int
+strcmp_opt(const char *s1, const char *s2)
+{
+  if (!s1) {
+    if (!s2)
+      return 0;
+    else
+      return -1;
+  } else if (!s2) {
+    return 1;
+  } else {
+    return strcmp(s1, s2);
+  }
+}
+
 /** Compares the first strlen(s2) characters of s1 with s2.  Returns as for
  * strcmp.
  */

+ 2 - 0
src/common/util.h

@@ -160,6 +160,7 @@ uint64_t round_to_power_of_2(uint64_t u64);
 unsigned round_to_next_multiple_of(unsigned number, unsigned divisor);
 uint32_t round_uint32_to_next_multiple_of(uint32_t number, uint32_t divisor);
 uint64_t round_uint64_to_next_multiple_of(uint64_t number, uint64_t divisor);
+int n_bits_set_u8(uint8_t v);
 
 /* Compute the CEIL of <b>a</b> divided by <b>b</b>, for nonnegative <b>a</b>
  * and positive <b>b</b>.  Works on integer types only. Not defined if a+b can
@@ -174,6 +175,7 @@ void tor_strlower(char *s) ATTR_NONNULL((1));
 void tor_strupper(char *s) ATTR_NONNULL((1));
 int tor_strisprint(const char *s) ATTR_PURE ATTR_NONNULL((1));
 int tor_strisnonupper(const char *s) ATTR_PURE ATTR_NONNULL((1));
+int strcmp_opt(const char *s1, const char *s2) ATTR_PURE;
 int strcmpstart(const char *s1, const char *s2) ATTR_PURE ATTR_NONNULL((1,2));
 int strcmp_len(const char *s1, const char *s2, size_t len)
   ATTR_PURE ATTR_NONNULL((1,2));

+ 4 - 0
src/or/circuitlist.c

@@ -550,6 +550,10 @@ circuit_free(circuit_t *circ)
 
     crypto_free_pk_env(ocirc->intro_key);
     rend_data_free(ocirc->rend_data);
+
+    tor_free(ocirc->dest_address);
+    tor_free(ocirc->socks_username);
+    tor_free(ocirc->socks_password);
   } else {
     or_circuit_t *ocirc = TO_OR_CIRCUIT(circ);
     /* Remember cell statistics for this circuit before deallocating. */

+ 125 - 22
src/or/circuituse.c

@@ -39,19 +39,19 @@ static void circuit_increment_failure_count(void);
  * Else return 0.
  */
 static int
-circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn,
+circuit_is_acceptable(const origin_circuit_t *origin_circ,
+                      const edge_connection_t *conn,
                       int must_be_open, uint8_t purpose,
                       int need_uptime, int need_internal,
                       time_t now)
 {
+  const circuit_t *circ = TO_CIRCUIT(origin_circ);
   const node_t *exitnode;
   cpath_build_state_t *build_state;
   tor_assert(circ);
   tor_assert(conn);
   tor_assert(conn->socks_request);
 
-  if (!CIRCUIT_IS_ORIGIN(circ))
-    return 0; /* this circ doesn't start at us */
   if (must_be_open && (circ->state != CIRCUIT_STATE_OPEN || !circ->n_conn))
     return 0; /* ignore non-open circs */
   if (circ->marked_for_close)
@@ -86,7 +86,7 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn,
    * circuit, it's the magical extra bob hop. so just check the nickname
    * of the one we meant to finish at.
    */
-  build_state = TO_ORIGIN_CIRCUIT(circ)->build_state;
+  build_state = origin_circ->build_state;
   exitnode = build_state_get_exit_node(build_state);
 
   if (need_uptime && !build_state->need_uptime)
@@ -134,25 +134,37 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn,
       return 0;
     }
   } else { /* not general */
-    origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
-    if ((conn->rend_data && !ocirc->rend_data) ||
-        (!conn->rend_data && ocirc->rend_data) ||
-        (conn->rend_data && ocirc->rend_data &&
+    if ((conn->rend_data && !origin_circ->rend_data) ||
+        (!conn->rend_data && origin_circ->rend_data) ||
+        (conn->rend_data && origin_circ->rend_data &&
          rend_cmp_service_ids(conn->rend_data->onion_address,
-                              ocirc->rend_data->onion_address))) {
+                              origin_circ->rend_data->onion_address))) {
       /* this circ is not for this conn */
       return 0;
     }
   }
+
+  if (!connection_edge_compatible_with_circuit(conn, origin_circ)) {
+    /* conn needs to be isolated from other conns that have already used
+     * origin_circ */
+    return 0;
+  }
+
   return 1;
 }
 
 /** Return 1 if circuit <b>a</b> is better than circuit <b>b</b> for
- * <b>purpose</b>, and return 0 otherwise. Used by circuit_get_best.
+ * <b>conn</b>, and return 0 otherwise. Used by circuit_get_best.
  */
 static int
-circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose)
+circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob,
+                  const edge_connection_t *conn)
 {
+  const circuit_t *a = TO_CIRCUIT(oa);
+  const circuit_t *b = TO_CIRCUIT(ob);
+  const uint8_t purpose = conn->_base.purpose;
+  int a_bits, b_bits;
+
   switch (purpose) {
     case CIRCUIT_PURPOSE_C_GENERAL:
       /* if it's used but less dirty it's best;
@@ -166,8 +178,7 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose)
         if (a->timestamp_dirty ||
             timercmp(&a->timestamp_created, &b->timestamp_created, >))
           return 1;
-        if (CIRCUIT_IS_ORIGIN(b) &&
-            TO_ORIGIN_CIRCUIT(b)->build_state->is_internal)
+        if (ob->build_state->is_internal)
           /* XXX023 what the heck is this internal thing doing here. I
            * think we can get rid of it. circuit_is_acceptable() already
            * makes sure that is_internal is exactly what we need it to
@@ -186,6 +197,29 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose)
         return 1;
       break;
   }
+
+  /* XXXX023 Maybe this check should get a higher priority to avoid
+   *   using up circuits too rapidly. */
+
+  a_bits = connection_edge_update_circuit_isolation(conn,
+                                                    (origin_circuit_t*)oa, 1);
+  b_bits = connection_edge_update_circuit_isolation(conn,
+                                                    (origin_circuit_t*)ob, 1);
+  /* if x_bits < 0, then we have not used x for anything; better not to dirty
+   * a connection if we can help it. */
+  if (a_bits < 0) {
+    return 0;
+  } else if (b_bits < 0) {
+    return 1;
+  }
+  a_bits &= ~ oa->isolation_flags_mixed;
+  a_bits &= ~ ob->isolation_flags_mixed;
+  if (n_bits_set_u8(a_bits) < n_bits_set_u8(b_bits)) {
+    /* The fewer new restrictions we need to make on a circuit for stream
+     * isolation, the better. */
+    return 1;
+  }
+
   return 0;
 }
 
@@ -206,10 +240,12 @@ circuit_is_better(circuit_t *a, circuit_t *b, uint8_t purpose)
  * closest introduce-purposed circuit that you can find.
  */
 static origin_circuit_t *
-circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose,
+circuit_get_best(const edge_connection_t *conn,
+                 int must_be_open, uint8_t purpose,
                  int need_uptime, int need_internal)
 {
-  circuit_t *circ, *best=NULL;
+  circuit_t *circ;
+  origin_circuit_t *best=NULL;
   struct timeval now;
   int intro_going_on_but_too_old = 0;
 
@@ -222,7 +258,11 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose,
   tor_gettimeofday(&now);
 
   for (circ=global_circuitlist;circ;circ = circ->next) {
-    if (!circuit_is_acceptable(circ,conn,must_be_open,purpose,
+    origin_circuit_t *origin_circ;
+    if (!CIRCUIT_IS_ORIGIN(circ))
+      continue;
+    origin_circ = TO_ORIGIN_CIRCUIT(circ);
+    if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose,
                                need_uptime,need_internal,now.tv_sec))
       continue;
 
@@ -236,8 +276,8 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose,
     /* now this is an acceptable circ to hand back. but that doesn't
      * mean it's the *best* circ to hand back. try to decide.
      */
-    if (!best || circuit_is_better(circ,best,purpose))
-      best = circ;
+    if (!best || circuit_is_better(origin_circ,best,conn))
+      best = origin_circ;
   }
 
   if (!best && intro_going_on_but_too_old)
@@ -245,7 +285,28 @@ circuit_get_best(edge_connection_t *conn, int must_be_open, uint8_t purpose,
              "right now, but it has already taken quite a while. Starting "
              "one in parallel.");
 
-  return best ? TO_ORIGIN_CIRCUIT(best) : NULL;
+  return best;
+}
+
+/** Return the number of not-yet-open general-purpose origin circuits. */
+static int
+count_pending_general_client_circuits(void)
+{
+  const circuit_t *circ;
+
+  int count = 0;
+
+  for (circ = global_circuitlist; circ; circ = circ->next) {
+    if (circ->marked_for_close ||
+        circ->state == CIRCUIT_STATE_OPEN ||
+        circ->purpose != CIRCUIT_PURPOSE_C_GENERAL ||
+        !CIRCUIT_IS_ORIGIN(circ))
+      continue;
+
+    ++count;
+  }
+
+  return count;
 }
 
 #if 0
@@ -937,6 +998,7 @@ circuit_testing_failed(origin_circuit_t *circ, int at_last_hop)
 void
 circuit_has_opened(origin_circuit_t *circ)
 {
+  int can_try_clearing_isolation = 0, tried_clearing_isolation = 0;
   control_event_circuit_status(circ, CIRC_EVENT_BUILT, 0);
 
   /* Remember that this circuit has finished building. Now if we start
@@ -944,9 +1006,12 @@ circuit_has_opened(origin_circuit_t *circ)
    * to consider its build time. */
   circ->has_opened = 1;
 
+ again:
+
   switch (TO_CIRCUIT(circ)->purpose) {
     case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
       rend_client_rendcirc_has_opened(circ);
+      can_try_clearing_isolation = 1;
       connection_ap_attach_pending();
       break;
     case CIRCUIT_PURPOSE_C_INTRODUCING:
@@ -955,6 +1020,7 @@ circuit_has_opened(origin_circuit_t *circ)
     case CIRCUIT_PURPOSE_C_GENERAL:
       /* Tell any AP connections that have been waiting for a new
        * circuit that one is ready. */
+      can_try_clearing_isolation = 1;
       connection_ap_attach_pending();
       break;
     case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
@@ -972,6 +1038,17 @@ circuit_has_opened(origin_circuit_t *circ)
      * This won't happen in normal operation, but might happen if the
      * controller did it. Just let it slide. */
   }
+
+  if (can_try_clearing_isolation && !tried_clearing_isolation &&
+      circ->isolation_values_set &&
+      !circ->isolation_any_streams_attached) {
+    /* If we have any isolation information set on this circuit, and
+     * we didn't manage to attach any streams to it, then we can
+     * and should clear it and try again. */
+    circuit_clear_isolation(circ);
+    tried_clearing_isolation = 1;
+    goto again;
+  }
 }
 
 /** Called whenever a circuit could not be successfully built.
@@ -1307,6 +1384,20 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn,
   if (!circ) {
     extend_info_t *extend_info=NULL;
     uint8_t new_circ_purpose;
+    const int n_pending = count_pending_general_client_circuits();
+
+    if (n_pending >= options->MaxClientCircuitsPending) {
+      static ratelim_t delay_limit = RATELIM_INIT(10*60);
+      char *m;
+      if ((m = rate_limit_log(&delay_limit, approx_time()))) {
+        log_notice(LD_APP, "We'd like to launch a circuit to handle a "
+                   "connection, but we already have %d general-purpose client "
+                   "circuits pending. Waiting until some finish.",
+                   n_pending);
+        tor_free(m);
+      }
+      return 0;
+    }
 
     if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
       /* need to pick an intro point */
@@ -1417,12 +1508,20 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn,
           rend_client_rendcirc_has_opened(circ);
       }
     }
-  }
-  if (!circ)
+  } /* endif (!circ) */
+  if (circ) {
+    /* Mark the circuit with the isolation fields for this connection.
+     * When the circuit arrives, we'll clear these flags: this is
+     * just some internal bookkeeping to make sure that we have
+     * launched enough circuits.
+     */
+    connection_edge_update_circuit_isolation(conn, circ, 0);
+  } else {
     log_info(LD_APP,
              "No safe circuit (purpose %d) ready for edge "
              "connection; delaying.",
              desired_circuit_purpose);
+  }
   *circp = circ;
   return 0;
 }
@@ -1468,6 +1567,9 @@ link_apconn_to_circ(edge_connection_t *apconn, origin_circuit_t *circ,
     tor_assert(circ->cpath->prev->state == CPATH_STATE_OPEN);
     apconn->cpath_layer = circ->cpath->prev;
   }
+
+  circ->isolation_any_streams_attached = 1;
+  connection_edge_update_circuit_isolation(apconn, circ, 0);
 }
 
 /** Return true iff <b>address</b> is matched by one of the entries in
@@ -1495,7 +1597,8 @@ hostname_in_track_host_exits(const or_options_t *options, const char *address)
  * <b>conn</b>'s destination.
  */
 static void
-consider_recording_trackhost(edge_connection_t *conn, origin_circuit_t *circ)
+consider_recording_trackhost(const edge_connection_t *conn,
+                             const origin_circuit_t *circ)
 {
   const or_options_t *options = get_options();
   char *new_address = NULL;

+ 387 - 64
src/or/config.c

@@ -240,7 +240,7 @@ static config_var_t _option_vars[] = {
   VAR("DirServer",               LINELIST, DirServers, NULL),
   V(DisableAllSwap,              BOOL,     "0"),
   V(DisableIOCP,                 BOOL,     "1"),
-  V(DNSPort,                     PORT,     "0"),
+  V(DNSPort,                     LINELIST, NULL),
   V(DNSListenAddress,            LINELIST, NULL),
   V(DownloadExtraInfo,           BOOL,     "0"),
   V(EnforceDistinctSubnets,      BOOL,     "1"),
@@ -315,13 +315,14 @@ static config_var_t _option_vars[] = {
   VAR("MapAddress",              LINELIST, AddressMap,           NULL),
   V(MaxAdvertisedBandwidth,      MEMUNIT,  "1 GB"),
   V(MaxCircuitDirtiness,         INTERVAL, "10 minutes"),
+  V(MaxClientCircuitsPending,    UINT,     "32"),
   V(MaxOnionsPending,            UINT,     "100"),
   OBSOLETE("MonthlyAccountingStart"),
   V(MyFamily,                    STRING,   NULL),
   V(NewCircuitPeriod,            INTERVAL, "30 seconds"),
   VAR("NamingAuthoritativeDirectory",BOOL, NamingAuthoritativeDir, "0"),
   V(NATDListenAddress,           LINELIST, NULL),
-  V(NATDPort,                    PORT,     "0"),
+  V(NATDPort,                    LINELIST, NULL),
   V(Nickname,                    STRING,   NULL),
   V(WarnUnsafeSocks,              BOOL,     "1"),
   OBSOLETE("NoPublish"),
@@ -374,7 +375,7 @@ static config_var_t _option_vars[] = {
   V(ShutdownWaitLength,          INTERVAL, "30 seconds"),
   V(SocksListenAddress,          LINELIST, NULL),
   V(SocksPolicy,                 LINELIST, NULL),
-  V(SocksPort,                   PORT,     "9050"),
+  V(SocksPort,                   LINELIST, NULL),
   V(SocksTimeout,                INTERVAL, "2 minutes"),
   OBSOLETE("StatusFetchPeriod"),
   V(StrictNodes,                 BOOL,     "0"),
@@ -385,7 +386,7 @@ static config_var_t _option_vars[] = {
   V(TrackHostExitsExpire,        INTERVAL, "30 minutes"),
   OBSOLETE("TrafficShaping"),
   V(TransListenAddress,          LINELIST, NULL),
-  V(TransPort,                   PORT,     "0"),
+  V(TransPort,                   LINELIST, NULL),
   V(TunnelDirConns,              BOOL,     "1"),
   V(UpdateBridgesFromAuthority,  BOOL,     "0"),
   V(UseBridges,                  BOOL,     "0"),
@@ -577,6 +578,9 @@ static int parse_client_transport_line(const char *line, int validate_only);
 static int parse_dir_server_line(const char *line,
                                  dirinfo_type_t required_type,
                                  int validate_only);
+static void port_cfg_free(port_cfg_t *port);
+static int parse_client_ports(const or_options_t *options, int validate_only,
+                              char **msg_out, int *n_ports_out);
 static int validate_data_directory(or_options_t *options);
 static int write_configuration_file(const char *fname,
                                     const or_options_t *options);
@@ -646,6 +650,8 @@ static or_state_t *global_state = NULL;
 static config_line_t *global_cmdline_options = NULL;
 /** Contents of most recently read DirPortFrontPage file. */
 static char *global_dirfrontpagecontents = NULL;
+/** List of port_cfg_t for client-level (SOCKS, DNS, Trans, NATD) ports. */
+static smartlist_t *configured_client_ports = NULL;
 
 /** Return the contents of our frontpage string, or NULL if not configured. */
 const char *
@@ -758,6 +764,13 @@ config_free_all(void)
   config_free_lines(global_cmdline_options);
   global_cmdline_options = NULL;
 
+  if (configured_client_ports) {
+    SMARTLIST_FOREACH(configured_client_ports,
+                      port_cfg_t *, p, tor_free(p));
+    smartlist_free(configured_client_ports);
+    configured_client_ports = NULL;
+  }
+
   tor_free(torrc_fname);
   tor_free(_version);
   tor_free(global_dirfrontpagecontents);
@@ -3027,6 +3040,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
   int i;
   config_line_t *cl;
   const char *uname = get_uname();
+  int n_client_ports=0;
 #define REJECT(arg) \
   STMT_BEGIN *msg = tor_strdup(arg); return -1; STMT_END
 #define COMPLAIN(arg) STMT_BEGIN log(LOG_WARN, LD_CONFIG, arg); STMT_END
@@ -3050,57 +3064,8 @@ options_validate(or_options_t *old_options, or_options_t *options,
   if (options->DirPort == 0 && options->DirListenAddress != NULL)
     REJECT("DirPort must be defined if DirListenAddress is defined.");
 
-  if (options->DNSPort == 0 && options->DNSListenAddress != NULL)
-    REJECT("DNSPort must be defined if DNSListenAddress is defined.");
-
-  if (options->ControlPort == 0 && options->ControlListenAddress != NULL)
-    REJECT("ControlPort must be defined if ControlListenAddress is defined.");
-
-  if (options->TransPort == 0 && options->TransListenAddress != NULL)
-    REJECT("TransPort must be defined if TransListenAddress is defined.");
-
-  if (options->NATDPort == 0 && options->NATDListenAddress != NULL)
-    REJECT("NATDPort must be defined if NATDListenAddress is defined.");
-
-  /* Don't gripe about SocksPort 0 with SocksListenAddress set; a standard
-   * configuration does this. */
-
-  for (i = 0; i < 3; ++i) {
-    int is_socks = i==0;
-    int is_trans = i==1;
-    config_line_t *line, *opt, *old;
-    const char *tp;
-    if (is_socks) {
-      opt = options->SocksListenAddress;
-      old = old_options ? old_options->SocksListenAddress : NULL;
-      tp = "SOCKS proxy";
-    } else if (is_trans) {
-      opt = options->TransListenAddress;
-      old = old_options ? old_options->TransListenAddress : NULL;
-      tp = "transparent proxy";
-    } else {
-      opt = options->NATDListenAddress;
-      old = old_options ? old_options->NATDListenAddress : NULL;
-      tp = "natd proxy";
-    }
-
-    for (line = opt; line; line = line->next) {
-      char *address = NULL;
-      uint16_t port;
-      uint32_t addr;
-      if (parse_addr_port(LOG_WARN, line->value, &address, &addr, &port)<0)
-        continue; /* We'll warn about this later. */
-      if (!is_internal_IP(addr, 1) &&
-          (!old_options || !config_lines_eq(old, opt))) {
-        log_warn(LD_CONFIG,
-             "You specified a public address '%s' for a %s. Other "
-             "people on the Internet might find your computer and use it as "
-             "an open %s. Please don't allow this unless you have "
-             "a good reason.", address, tp, tp);
-      }
-      tor_free(address);
-    }
-  }
+  if (parse_client_ports(options, 1, msg, &n_client_ports) < 0)
+    return -1;
 
   if (validate_data_directory(options)<0)
     REJECT("Invalid DataDirectory");
@@ -3142,9 +3107,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
     REJECT("Can't use a relative path to torrc when RunAsDaemon is set.");
 #endif
 
-  if (options->SocksPort == 0 && options->TransPort == 0 &&
-      options->NATDPort == 0 && options->ORPort == 0 &&
-      options->DNSPort == 0 && !options->RendConfigLines)
+  if (n_client_ports == 0 && options->ORPort == 0 && !options->RendConfigLines)
     log(LOG_WARN, LD_CONFIG,
         "SocksPort, TransPort, NATDPort, DNSPort, and ORPort are all "
         "undefined, and there aren't any hidden services configured.  "
@@ -3253,6 +3216,15 @@ options_validate(or_options_t *old_options, or_options_t *options,
     return -1;
   }
 
+  if (options->MaxClientCircuitsPending <= 0 ||
+      options->MaxClientCircuitsPending > MAX_MAX_CLIENT_CIRCUITS_PENDING) {
+    tor_asprintf(msg,
+                 "MaxClientCircuitsPending must be between 1 and %d, but "
+                 "was set to %d", MAX_MAX_CLIENT_CIRCUITS_PENDING,
+                 options->MaxClientCircuitsPending);
+    return -1;
+  }
+
   if (validate_ports_csv(options->FirewallPorts, "FirewallPorts", msg) < 0)
     return -1;
 
@@ -3892,12 +3864,7 @@ options_validate(or_options_t *old_options, or_options_t *options,
 static int
 opt_streq(const char *s1, const char *s2)
 {
-  if (!s1 && !s2)
-    return 1;
-  else if (s1 && s2 && !strcmp(s1,s2))
-    return 1;
-  else
-    return 0;
+  return 0 == strcmp_opt(s1, s2);
 }
 
 /** Check if any of the previous options have changed but aren't allowed to. */
@@ -4885,6 +4852,362 @@ parse_dir_server_line(const char *line, dirinfo_type_t required_type,
   return r;
 }
 
+/** Free all storage held in <b>port</b> */
+static void
+port_cfg_free(port_cfg_t *port)
+{
+  tor_free(port);
+}
+
+/** Warn for every port in <b>ports</b> that is not on a loopback address. */
+static void
+warn_nonlocal_client_ports(const smartlist_t *ports, const char *portname)
+{
+  SMARTLIST_FOREACH_BEGIN(ports, const port_cfg_t *, port) {
+    if (!tor_addr_is_loopback(&port->addr)) {
+      log_warn(LD_CONFIG, "You specified a public address for %sPort. "
+               "Other people on the Internet might find your computer and "
+               "use it as an open proxy. Please don't allow this unless you "
+               "have a good reason.", portname);
+    }
+  } SMARTLIST_FOREACH_END(port);
+}
+
+#define CL_PORT_NO_OPTIONS    (1u<<0)
+#define CL_PORT_WARN_NONLOCAL (1u<<1)
+#define CL_PORT_ALLOW_EXTRA_LISTENADDR (1u<<2)
+
+/**
+ * Parse port configuration for a single client port type.
+ *
+ * Read entries of the "FooPort" type from the list <b>ports</b>, and
+ * entries of the "FooListenAddress" type from the list
+ * <b>listenaddrs</b>.  Two syntaxes are supported: a legacy syntax
+ * where FooPort is at most a single entry containing a port number and
+ * where FooListenAddress has any number of address:port combinations;
+ * and a new syntax where there are no FooListenAddress entries and
+ * where FooPort can have any number of entries of the format
+ * "[Address:][Port] IsolationOptions".
+ *
+ * In log messages, describe the port type as <b>portname</b>.
+ *
+ * If no address is specified, default to <b>defaultaddr</b>.  If no
+ * FooPort is given, default to defaultport (if 0, there is no default).
+ *
+ * If CL_PORT_NO_OPTIONS is set in <b>flags</b>, do not allow stream
+ * isolation options in the FooPort entries.
+ *
+ * If CL_PORT_WARN_NONLOCAL is set in <b>flags</b>, warn if any of the
+ * ports are not on a local address.
+ *
+ * Unless CL_PORT_ALLOW_EXTRA_LISTENADDR is set in <b>flags</b>, warn
+ * if FooListenAddress is set but FooPort is 0.
+ *
+ * On success, if <b>out</b> is given, add a new port_cfg_t entry to
+ * <b>out</b> for every port that the client should listen on.  Return 0
+ * on success, -1 on failure.
+ */
+static int
+parse_client_port_config(smartlist_t *out,
+                         const config_line_t *ports,
+                         const config_line_t *listenaddrs,
+                         const char *portname,
+                         int listener_type,
+                         const char *defaultaddr,
+                         int defaultport,
+                         unsigned flags)
+{
+  smartlist_t *elts;
+  int retval = -1;
+  const unsigned allow_client_options = !(flags & CL_PORT_NO_OPTIONS);
+  const unsigned warn_nonlocal = flags & CL_PORT_WARN_NONLOCAL;
+  const unsigned allow_spurious_listenaddr =
+    flags & CL_PORT_ALLOW_EXTRA_LISTENADDR;
+
+  /* FooListenAddress is deprecated; let's make it work like it used to work,
+   * though. */
+  if (listenaddrs) {
+    int mainport = defaultport;
+
+   if (ports && ports->next) {
+      log_warn(LD_CONFIG, "%sListenAddress can't be used when there are "
+               "multiple %sPort lines", portname, portname);
+      return -1;
+   } else if (ports) {
+     if (!strcmp(ports->value, "auto")) {
+       mainport = CFG_AUTO_PORT;
+     } else {
+       int ok;
+       mainport = (int)tor_parse_long(ports->value, 10, 0, 65535, &ok, NULL);
+       if (!ok) {
+         log_warn(LD_CONFIG, "%sListenAddress can only be used with a single "
+                  "%sPort with value \"auto\" or 65535.", portname, portname);
+         return -1;
+       }
+     }
+   }
+
+   if (mainport == 0) {
+     if (allow_spurious_listenaddr)
+       return 1;
+     log_warn(LD_CONFIG, "%sPort must be defined if %sListenAddress is used",
+              portname, portname);
+     return -1;
+   }
+
+   for (; listenaddrs; listenaddrs = listenaddrs->next) {
+     tor_addr_t addr;
+     uint16_t port = 0;
+     if (tor_addr_port_parse(listenaddrs->value, &addr, &port) < 0) {
+       log_warn(LD_CONFIG, "Unable to parse %sListenAddress '%s'",
+                portname, listenaddrs->value);
+       return -1;
+     }
+     if (out) {
+       port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t));
+       cfg->type = listener_type;
+       cfg->port = port ? port : defaultport;
+       tor_addr_copy(&cfg->addr, &addr);
+       cfg->session_group = SESSION_GROUP_UNSET;
+       cfg->isolation_flags = ISO_DEFAULT;
+       smartlist_add(out, cfg);
+     }
+   }
+
+   if (warn_nonlocal && out)
+     warn_nonlocal_client_ports(out, portname);
+   return 0;
+  } /* end if (listenaddrs) */
+
+  /* No ListenAddress lines. If there's no FooPort, then maybe make a default
+   * one. */
+  if (! ports) {
+    if (defaultport && out) {
+       port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t));
+       cfg->type = listener_type;
+       cfg->port = defaultport;
+       tor_addr_from_str(&cfg->addr, defaultaddr);
+       cfg->session_group = SESSION_GROUP_UNSET;
+       cfg->isolation_flags = ISO_DEFAULT;
+       smartlist_add(out, cfg);
+    }
+    return 0;
+  }
+
+  /* At last we can actually parse the FooPort lines.  The syntax is:
+   * [Addr:](Port|auto) [Options].*/
+  elts = smartlist_create();
+
+  for (; ports; ports = ports->next) {
+    tor_addr_t addr;
+    int port;
+    int sessiongroup = SESSION_GROUP_UNSET;
+    unsigned isolation = ISO_DEFAULT;
+
+    char *addrport;
+    uint16_t ptmp=0;
+    int ok;
+
+    smartlist_split_string(elts, ports->value, NULL,
+                           SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0);
+    if (smartlist_len(elts) == 0) {
+      log_warn(LD_CONFIG, "Invalid %sPort line with no value", portname);
+      goto err;
+    }
+
+    if (!allow_client_options && smartlist_len(elts) > 1) {
+      log_warn(LD_CONFIG, "Too many options on %sPort line", portname);
+      goto err;
+    }
+
+    /* Now parse the addr/port value */
+    addrport = smartlist_get(elts, 0);
+    if (!strcmp(addrport, "auto")) {
+      port = CFG_AUTO_PORT;
+      tor_addr_from_str(&addr, defaultaddr);
+    } else if (!strcasecmpend(addrport, ":auto")) {
+      char *addrtmp = tor_strndup(addrport, strlen(addrport)-5);
+      port = CFG_AUTO_PORT;
+      if (tor_addr_port_parse(addrtmp, &addr, &ptmp)<0 || ptmp) {
+        log_warn(LD_CONFIG, "Invalid address '%s' for %sPort",
+                 escaped(addrport), portname);
+        tor_free(addrtmp);
+        goto err;
+      }
+    } else if (tor_addr_port_parse(addrport, &addr, &ptmp) == 0) {
+      if (ptmp == 0) {
+        log_warn(LD_CONFIG, "%sPort line has address but no port", portname);
+        goto err;
+      }
+      port = ptmp;
+    } else {
+      port = (int) tor_parse_long(addrport, 10, 0, 65535, &ok, NULL);
+      if (!ok) {
+        log_warn(LD_CONFIG, "Couldn't parse address '%s' for %sPort",
+                 escaped(addrport), portname);
+        goto err;
+      }
+      tor_addr_from_str(&addr, defaultaddr);
+    }
+
+    /* Now parse the rest of the options, if any. */
+    SMARTLIST_FOREACH_BEGIN(elts, char *, elt) {
+      int no = 0, isoflag = 0;
+      const char *elt_orig = elt;
+      if (elt_sl_idx == 0)
+        continue; /* Skip addr:port */
+      if (!strcasecmpstart(elt, "SessionGroup=")) {
+        int group = (int)tor_parse_long(elt+strlen("SessionGroup="),
+                                        10, 0, INT_MAX, &ok, NULL);
+        if (!ok) {
+          log_warn(LD_CONFIG, "Invalid %sPort option '%s'",
+                   portname, escaped(elt));
+          goto err;
+        }
+        if (sessiongroup >= 0) {
+          log_warn(LD_CONFIG, "Multiple SessionGroup options on %sPort",
+                   portname);
+          goto err;
+        }
+        sessiongroup = group;
+        continue;
+      }
+
+      if (!strcasecmpstart(elt, "No")) {
+        no = 1;
+        elt += 2;
+      }
+      if (!strcasecmpend(elt, "s"))
+        elt[strlen(elt)-1] = '\0'; /* kill plurals. */
+
+      if (!strcasecmp(elt, "IsolateDestPort")) {
+        isoflag = ISO_DESTPORT;
+      } else if (!strcasecmp(elt, "IsolateDestAddr")) {
+        isoflag = ISO_DESTADDR;
+      } else if (!strcasecmp(elt, "IsolateSOCKSAuth")) {
+        isoflag = ISO_SOCKSAUTH;
+      } else if (!strcasecmp(elt, "IsolateClientProtocol")) {
+        isoflag = ISO_CLIENTPROTO;
+      } else if (!strcasecmp(elt, "IsolateClientAddr")) {
+        isoflag = ISO_CLIENTADDR;
+      } else {
+        log_warn(LD_CONFIG, "Unrecognized %sPort option '%s'",
+                 portname, escaped(elt_orig));
+      }
+
+      if (no) {
+        isolation &= ~isoflag;
+      } else {
+        isolation |= isoflag;
+      }
+    } SMARTLIST_FOREACH_END(elt);
+
+    if (out) {
+      port_cfg_t *cfg = tor_malloc_zero(sizeof(port_cfg_t));
+      cfg->type = listener_type;
+      cfg->port = port;
+      tor_addr_copy(&cfg->addr, &addr);
+      cfg->session_group = sessiongroup;
+      cfg->isolation_flags = isolation;
+      smartlist_add(out, cfg);
+    }
+    SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
+    smartlist_clear(elts);
+  }
+
+  if (warn_nonlocal && out)
+    warn_nonlocal_client_ports(out, portname);
+
+  retval = 0;
+ err:
+  SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp));
+  smartlist_free(elts);
+  return retval;
+}
+
+/** Parse all client port types (Socks, DNS, Trans, NATD) from
+ * <b>options</b>. On success, set *<b>n_ports_out</b> to the number of
+ * ports that are listed and return 0.  On failure, set *<b>msg</b> to a
+ * description of the problem and return -1.
+ *
+ * If <b>validate_only</b> is false, set configured_client_ports to the
+ * new list of ports parsed from <b>options</b>.
+ **/
+static int
+parse_client_ports(const or_options_t *options, int validate_only,
+                   char **msg, int *n_ports_out)
+{
+  smartlist_t *ports;
+  int retval = -1;
+
+  ports = smartlist_create();
+
+  *n_ports_out = 0;
+
+  if (parse_client_port_config(ports,
+             options->SocksPort, options->SocksListenAddress,
+             "Socks", CONN_TYPE_AP_LISTENER,
+             "127.0.0.1", 9050,
+             CL_PORT_WARN_NONLOCAL|CL_PORT_ALLOW_EXTRA_LISTENADDR) < 0) {
+    *msg = tor_strdup("Invalid SocksPort/SocksListenAddress configuration");
+    goto err;
+  }
+  if (parse_client_port_config(ports,
+                               options->DNSPort, options->DNSListenAddress,
+                               "DNS", CONN_TYPE_AP_DNS_LISTENER,
+                               "127.0.0.1", 0,
+                               CL_PORT_WARN_NONLOCAL) < 0) {
+    *msg = tor_strdup("Invalid DNSPort/DNSListenAddress configuration");
+    goto err;
+  }
+  if (parse_client_port_config(ports,
+                               options->TransPort, options->TransListenAddress,
+                               "Trans", CONN_TYPE_AP_TRANS_LISTENER,
+                               "127.0.0.1", 0,
+                               CL_PORT_WARN_NONLOCAL) < 0) {
+    *msg = tor_strdup("Invalid TransPort/TransListenAddress configuration");
+    goto err;
+  }
+  if (parse_client_port_config(ports,
+                               options->NATDPort, options->NATDListenAddress,
+                               "NATD", CONN_TYPE_AP_NATD_LISTENER,
+                               "127.0.0.1", 0,
+                               CL_PORT_WARN_NONLOCAL) < 0) {
+    *msg = tor_strdup("Invalid NatdPort/NatdListenAddress configuration");
+    goto err;
+  }
+
+  *n_ports_out = smartlist_len(ports);
+
+  if (!validate_only) {
+    if (configured_client_ports) {
+      SMARTLIST_FOREACH(configured_client_ports,
+                        port_cfg_t *, p, port_cfg_free(p));
+      smartlist_free(configured_client_ports);
+    }
+    configured_client_ports = ports;
+    ports = NULL; /* prevent free below. */
+  }
+
+  retval = 0;
+ err:
+  if (ports) {
+    SMARTLIST_FOREACH(ports, port_cfg_t *, p, port_cfg_free(p));
+    smartlist_free(ports);
+  }
+  return retval;
+}
+
+/** Return a list of port_cfg_t for client ports parsed from the
+ * options. */
+const smartlist_t *
+get_configured_client_ports(void)
+{
+  if (!configured_client_ports)
+    configured_client_ports = smartlist_create();
+  return configured_client_ports;
+}
+
 /** Adjust the value of options->DataDirectory, or fill it in if it's
  * absent. Return 0 on success, -1 on failure. */
 static int

+ 2 - 0
src/or/config.h

@@ -64,6 +64,8 @@ or_state_t *get_or_state(void);
 int did_last_state_file_write_fail(void);
 int or_state_save(time_t now);
 
+const smartlist_t *get_configured_client_ports(void);
+
 int options_need_geoip_info(const or_options_t *options,
                             const char **reason_out);
 int getinfo_helper_config(control_connection_t *conn,

+ 258 - 245
src/or/connection.c

@@ -43,11 +43,12 @@
 static connection_t *connection_create_listener(
                                const struct sockaddr *listensockaddr,
                                socklen_t listensocklen, int type,
-                               char* address);
+                               const char *address,
+                               const port_cfg_t *portcfg);
 static void connection_init(time_t now, connection_t *conn, int type,
                             int socket_family);
 static int connection_init_accepted_conn(connection_t *conn,
-                                         uint8_t listener_type);
+                          const listener_connection_t *listener);
 static int connection_handle_listener_read(connection_t *conn, int new_type);
 #ifndef USE_BUFFEREVENTS
 static int connection_bucket_should_increase(int bucket,
@@ -76,6 +77,15 @@ static uint32_t last_interface_ip = 0;
  * Used to detect IP address changes. */
 static smartlist_t *outgoing_addrs = NULL;
 
+#define CASE_ANY_LISTENER_TYPE \
+    case CONN_TYPE_OR_LISTENER: \
+    case CONN_TYPE_AP_LISTENER: \
+    case CONN_TYPE_DIR_LISTENER: \
+    case CONN_TYPE_CONTROL_LISTENER: \
+    case CONN_TYPE_AP_TRANS_LISTENER: \
+    case CONN_TYPE_AP_NATD_LISTENER: \
+    case CONN_TYPE_AP_DNS_LISTENER
+
 /**************************************************************/
 
 /**
@@ -116,13 +126,7 @@ conn_state_to_string(int type, int state)
 {
   static char buf[96];
   switch (type) {
-    case CONN_TYPE_OR_LISTENER:
-    case CONN_TYPE_AP_LISTENER:
-    case CONN_TYPE_AP_TRANS_LISTENER:
-    case CONN_TYPE_AP_NATD_LISTENER:
-    case CONN_TYPE_AP_DNS_LISTENER:
-    case CONN_TYPE_DIR_LISTENER:
-    case CONN_TYPE_CONTROL_LISTENER:
+    CASE_ANY_LISTENER_TYPE:
       if (state == LISTENER_STATE_READY)
         return "ready";
       break;
@@ -265,6 +269,17 @@ control_connection_new(int socket_family)
   return control_conn;
 }
 
+/** Allocate and return a new listener_connection_t, initialized as by
+ * connection_init(). */
+listener_connection_t *
+listener_connection_new(int type, int socket_family)
+{
+  listener_connection_t *listener_conn =
+    tor_malloc_zero(sizeof(listener_connection_t));
+  connection_init(time(NULL), TO_CONN(listener_conn), type, socket_family);
+  return listener_conn;
+}
+
 /** Allocate, initialize, and return a new connection_t subtype of <b>type</b>
  * to make or receive connections of address family <b>socket_family</b>.  The
  * type should be one of the CONN_TYPE_* constants. */
@@ -285,6 +300,9 @@ connection_new(int type, int socket_family)
     case CONN_TYPE_CONTROL:
       return TO_CONN(control_connection_new(socket_family));
 
+    CASE_ANY_LISTENER_TYPE:
+      return TO_CONN(listener_connection_new(type, socket_family));
+
     default: {
       connection_t *conn = tor_malloc_zero(sizeof(connection_t));
       connection_init(time(NULL), conn, type, socket_family);
@@ -325,6 +343,8 @@ connection_init(time_t now, connection_t *conn, int type, int socket_family)
     case CONN_TYPE_CONTROL:
       conn->magic = CONTROL_CONNECTION_MAGIC;
       break;
+    CASE_ANY_LISTENER_TYPE:
+      conn->magic = LISTENER_CONNECTION_MAGIC;
     default:
       conn->magic = BASE_CONNECTION_MAGIC;
       break;
@@ -396,6 +416,11 @@ _connection_free(connection_t *conn)
       mem = TO_CONTROL_CONN(conn);
       memlen = sizeof(control_connection_t);
       break;
+    CASE_ANY_LISTENER_TYPE:
+      tor_assert(conn->magic == LISTENER_CONNECTION_MAGIC);
+      mem = TO_LISTENER_CONN(conn);
+      memlen = sizeof(listener_connection_t);
+      break;
     default:
       tor_assert(conn->magic == BASE_CONNECTION_MAGIC);
       mem = conn;
@@ -442,9 +467,9 @@ _connection_free(connection_t *conn)
   if (CONN_IS_EDGE(conn)) {
     edge_connection_t *edge_conn = TO_EDGE_CONN(conn);
     tor_free(edge_conn->chosen_exit_name);
+    tor_free(edge_conn->original_dest_address);
     if (edge_conn->socks_request)
       socks_request_free(edge_conn->socks_request);
-
     rend_data_free(edge_conn->rend_data);
   }
   if (conn->type == CONN_TYPE_CONTROL) {
@@ -701,48 +726,6 @@ connection_expire_held_open(void)
   });
 }
 
-/** Create an AF_INET listenaddr struct.
- * <b>listenaddress</b> provides the host and optionally the port information
- * for the new structure.  If no port is provided in <b>listenaddress</b> then
- * <b>listenport</b> is used.
- *
- * If not NULL <b>readable_address</b> will contain a copy of the host part of
- * <b>listenaddress</b>.
- *
- * The listenaddr struct has to be freed by the caller.
- */
-static struct sockaddr_in *
-create_inet_sockaddr(const char *listenaddress, int listenport,
-                     char **readable_address, socklen_t *socklen_out) {
-  struct sockaddr_in *listenaddr = NULL;
-  uint32_t addr;
-  uint16_t usePort = 0;
-
-  if (parse_addr_port(LOG_WARN,
-                      listenaddress, readable_address, &addr, &usePort)<0) {
-    log_warn(LD_CONFIG,
-             "Error parsing/resolving ListenAddress %s", listenaddress);
-    goto err;
-  }
-  if (usePort==0) {
-    if (listenport != CFG_AUTO_PORT)
-      usePort = listenport;
-  }
-
-  listenaddr = tor_malloc_zero(sizeof(struct sockaddr_in));
-  listenaddr->sin_addr.s_addr = htonl(addr);
-  listenaddr->sin_family = AF_INET;
-  listenaddr->sin_port = htons((uint16_t) usePort);
-
-  *socklen_out = sizeof(struct sockaddr_in);
-
-  return listenaddr;
-
- err:
-  tor_free(listenaddr);
-  return NULL;
-}
-
 #ifdef HAVE_SYS_UN_H
 /** Create an AF_UNIX listenaddr struct.
  * <b>listenaddress</b> provides the path to the Unix socket.
@@ -877,12 +860,15 @@ make_socket_reuseable(tor_socket_t sock)
 static connection_t *
 connection_create_listener(const struct sockaddr *listensockaddr,
                            socklen_t socklen,
-                           int type, char* address)
+                           int type, const char *address,
+                           const port_cfg_t *port_cfg)
 {
+  listener_connection_t *lis_conn;
   connection_t *conn;
   tor_socket_t s; /* the socket we're going to make */
   uint16_t usePort = 0, gotPort = 0;
   int start_reading = 0;
+  static int global_next_session_group = SESSION_GROUP_FIRST_AUTO;
 
   if (get_n_open_sockets() >= get_options()->_ConnLimit-1) {
     warn_too_many_conns();
@@ -999,12 +985,23 @@ connection_create_listener(const struct sockaddr *listensockaddr,
 
   set_socket_nonblocking(s);
 
-  conn = connection_new(type, listensockaddr->sa_family);
+  lis_conn = listener_connection_new(type, listensockaddr->sa_family);
+  conn = TO_CONN(lis_conn);
   conn->socket_family = listensockaddr->sa_family;
   conn->s = s;
   conn->address = tor_strdup(address);
   conn->port = gotPort;
 
+  if (port_cfg->isolation_flags) {
+    lis_conn->isolation_flags = port_cfg->isolation_flags;
+    if (port_cfg->session_group >= 0) {
+      lis_conn->session_group = port_cfg->session_group;
+    } else {
+      /* XXXX023 This can wrap after ~INT_MAX ports are opened. */
+      lis_conn->session_group = global_next_session_group--;
+    }
+  }
+
   if (connection_add(conn) < 0) { /* no space, forget it */
     log_warn(LD_NET,"connection_add for listener failed. Giving up.");
     connection_free(conn);
@@ -1217,7 +1214,7 @@ connection_handle_listener_read(connection_t *conn, int new_type)
     return 0; /* no need to tear down the parent */
   }
 
-  if (connection_init_accepted_conn(newconn, conn->type) < 0) {
+  if (connection_init_accepted_conn(newconn, TO_LISTENER_CONN(conn)) < 0) {
     if (! newconn->marked_for_close)
       connection_mark_for_close(newconn);
     return 0;
@@ -1231,7 +1228,8 @@ connection_handle_listener_read(connection_t *conn, int new_type)
  * and place it in circuit_wait.
  */
 static int
-connection_init_accepted_conn(connection_t *conn, uint8_t listener_type)
+connection_init_accepted_conn(connection_t *conn,
+                              const listener_connection_t *listener)
 {
   connection_start_reading(conn);
 
@@ -1240,7 +1238,10 @@ connection_init_accepted_conn(connection_t *conn, uint8_t listener_type)
       control_event_or_conn_status(TO_OR_CONN(conn), OR_CONN_EVENT_NEW, 0);
       return connection_tls_start_handshake(TO_OR_CONN(conn), 1);
     case CONN_TYPE_AP:
-      switch (listener_type) {
+      TO_EDGE_CONN(conn)->isolation_flags = listener->isolation_flags;
+      TO_EDGE_CONN(conn)->session_group = listener->session_group;
+      TO_EDGE_CONN(conn)->nym_epoch = get_signewnym_epoch();
+      switch (TO_CONN(listener)->type) {
         case CONN_TYPE_AP_LISTENER:
           conn->state = AP_CONN_STATE_SOCKS_WAIT;
           break;
@@ -1741,175 +1742,185 @@ connection_read_proxy_handshake(connection_t *conn)
   return ret;
 }
 
-/**
- * Launch any configured listener connections of type <b>type</b>.  (A
- * listener is configured if <b>port_option</b> is non-zero.  If any
- * ListenAddress configuration options are given in <b>cfg</b>, create a
- * connection binding to each one.  Otherwise, create a single
- * connection binding to the address <b>default_addr</b>.)
- *
- * Only launch the listeners of this type that are not already open, and
- * only close listeners that are no longer wanted.  Existing listeners
- * that are still configured are not touched.
+/** Given a list of listener connections in <b>old_conns</b>, and list of
+ * port_cfg_t entries in <b>ports</b>, open a new listener for every port in
+ * <b>ports</b> that does not already have a listener in <b>old_conns</b>.
  *
- * If <b>disable_all_conns</b> is set, then never open new conns, and
- * close the existing ones.
+ * Remove from <b>old_conns</b> every connection that has a corresponding
+ * entry in <b>ports</b>.  Add to <b>new_conns</b> new every connection we
+ * launch.
  *
- * Add all old conns that should be closed to <b>replaced_conns</b>.
- * Add all new connections to <b>new_conns</b>.
- */
+ * Return 0 on success, -1 on failure.
+ **/
 static int
-retry_listeners(int type, config_line_t *cfg,
-                int port_option, const char *default_addr,
-                smartlist_t *replaced_conns,
-                smartlist_t *new_conns,
-                int disable_all_conns,
-                int socket_family)
+retry_listener_ports(smartlist_t *old_conns,
+                     const smartlist_t *ports,
+                     smartlist_t *new_conns)
 {
-  smartlist_t *launch = smartlist_create(), *conns;
-  int free_launch_elts = 1;
-  int r;
-  config_line_t *c;
-  connection_t *conn;
-  config_line_t *line;
-
-  tor_assert(socket_family == AF_INET || socket_family == AF_UNIX);
+  smartlist_t *launch = smartlist_create();
+  int r = 0;
 
-  if (cfg && port_option) {
-    for (c = cfg; c; c = c->next) {
-      smartlist_add(launch, c);
-    }
-    free_launch_elts = 0;
-  } else if (port_option) {
-    line = tor_malloc_zero(sizeof(config_line_t));
-    line->key = tor_strdup("");
-    line->value = tor_strdup(default_addr);
-    smartlist_add(launch, line);
-  }
+  smartlist_add_all(launch, ports);
 
-  /*
-  SMARTLIST_FOREACH(launch, config_line_t *, l,
-                    log_fn(LOG_NOTICE, "#%s#%s", l->key, l->value));
-  */
+  /* Iterate through old_conns, comparing it to launch: remove from both lists
+   * each pair of elements that corresponds to the same port. */
+  SMARTLIST_FOREACH_BEGIN(old_conns, connection_t *, conn) {
+    const port_cfg_t *found_port = NULL;
 
-  conns = get_connection_array();
-  SMARTLIST_FOREACH(conns, connection_t *, conn,
-  {
-    if (conn->type != type ||
-        conn->socket_family != socket_family ||
-        conn->marked_for_close)
-      continue;
     /* Okay, so this is a listener.  Is it configured? */
-    line = NULL;
-    SMARTLIST_FOREACH(launch, config_line_t *, wanted,
-      {
-        char *address=NULL;
-        uint16_t port;
-        switch (socket_family) {
-          case AF_INET:
-            if (!parse_addr_port(LOG_WARN,
-                                 wanted->value, &address, NULL, &port)) {
-              int addr_matches = !strcasecmp(address, conn->address);
-              int port_matches;
-              tor_free(address);
-              if (port) {
-                /* The Listener line has a port */
-                port_matches = (port == conn->port);
-              } else if (port_option == CFG_AUTO_PORT) {
-                /* The Listener line has no port, and the Port line is "auto".
-                 * "auto" matches anything; transitions from any port to
-                 * "auto" succeed. */
-                port_matches = 1;
-              } else {
-                /*  The Listener line has no port, and the Port line is "auto".
-                 * "auto" matches anything; transitions from any port to
-                 * "auto" succeed. */
-                port_matches = (port_option == conn->port);
-              }
-              if (port_matches  && addr_matches) {
-                line = wanted;
-                break;
-              }
-            }
-            break;
-          case AF_UNIX:
-            if (!strcasecmp(wanted->value, conn->address)) {
-              line = wanted;
-              break;
-            }
-            break;
-          default:
-            tor_assert(0);
+    SMARTLIST_FOREACH_BEGIN(launch, const port_cfg_t *, wanted) {
+      if (conn->type != wanted->type)
+        continue;
+      if ((conn->socket_family != AF_UNIX && wanted->is_unix_addr) ||
+          (conn->socket_family == AF_UNIX && ! wanted->is_unix_addr))
+        continue;
+
+      if (wanted->is_unix_addr) {
+        if (conn->socket_family == AF_UNIX &&
+            !strcmp(wanted->unix_addr, conn->address)) {
+          found_port = wanted;
+          break;
         }
-      });
-    if (!line || disable_all_conns) {
-      /* This one isn't configured. Close it. */
-      log_notice(LD_NET, "Closing no-longer-configured %s on %s:%d",
-                 conn_type_to_string(type), conn->address, conn->port);
-      if (replaced_conns) {
-        smartlist_add(replaced_conns, conn);
       } else {
-        connection_close_immediate(conn);
-        connection_mark_for_close(conn);
+        int port_matches;
+        if (wanted->port == CFG_AUTO_PORT) {
+          port_matches = 1;
+        } else {
+          port_matches = (wanted->port == conn->port);
+        }
+        if (port_matches && tor_addr_eq(&wanted->addr, &conn->addr)) {
+          found_port = wanted;
+          break;
+        }
       }
-    } else {
-      /* It's configured; we don't need to launch it. */
+    } SMARTLIST_FOREACH_END(wanted);
+
+    if (found_port) {
+      /* This listener is already running; we don't need to launch it. */
 //      log_debug(LD_NET, "Already have %s on %s:%d",
 //                conn_type_to_string(type), conn->address, conn->port);
-      smartlist_remove(launch, line);
-      if (free_launch_elts)
-        config_free_lines(line);
+      smartlist_remove(launch, found_port);
+      /* And we can remove the connection from old_conns too. */
+      SMARTLIST_DEL_CURRENT(old_conns, conn);
     }
-  });
+  } SMARTLIST_FOREACH_END(conn);
 
   /* Now open all the listeners that are configured but not opened. */
-  r = 0;
-  if (!disable_all_conns) {
-    SMARTLIST_FOREACH_BEGIN(launch, config_line_t *, cfg_line) {
-        char *address = NULL;
-        struct sockaddr *listensockaddr;
-        socklen_t listensocklen = 0;
-
-        switch (socket_family) {
-          case AF_INET:
-            listensockaddr = (struct sockaddr *)
-                             create_inet_sockaddr(cfg_line->value,
-                                                  port_option,
-                                                  &address, &listensocklen);
-            break;
-          case AF_UNIX:
-            listensockaddr = (struct sockaddr *)
-                             create_unix_sockaddr(cfg_line->value,
-                                                  &address, &listensocklen);
-            break;
-          default:
-            tor_assert(0);
-        }
+  SMARTLIST_FOREACH_BEGIN(launch, const port_cfg_t *, port) {
+    struct sockaddr *listensockaddr;
+    socklen_t listensocklen = 0;
+    char *address=NULL;
+    connection_t *conn;
+
+    if (port->is_unix_addr) {
+      listensockaddr = (struct sockaddr *)
+        create_unix_sockaddr(port->unix_addr,
+                             &address, &listensocklen);
+    } else {
+      listensockaddr = tor_malloc(sizeof(struct sockaddr_storage));
+      listensocklen = tor_addr_to_sockaddr(&port->addr,
+                                           port->port,
+                                           listensockaddr,
+                                           sizeof(struct sockaddr_storage));
+      address = tor_dup_addr(&port->addr);
+    }
+
+    if (listensockaddr) {
+      conn = connection_create_listener(listensockaddr, listensocklen,
+                                        port->type, address, port);
+      tor_free(listensockaddr);
+      tor_free(address);
+    } else {
+      conn = NULL;
+    }
+
+    if (!conn) {
+      r = -1;
+    } else {
+      if (new_conns)
+        smartlist_add(new_conns, conn);
+    }
+  } SMARTLIST_FOREACH_END(port);
+
+  smartlist_free(launch);
+
+  return r;
+}
+
+/**
+ * Launch any configured listener connections of type <b>type</b>.  (A
+ * listener is configured if <b>port_option</b> is non-zero.  If any
+ * ListenAddress configuration options are given in <b>cfg</b>, create a
+ * connection binding to each one.  Otherwise, create a single
+ * connection binding to the address <b>default_addr</b>.)
+ *
+ * We assume that we're starting with a list of existing listener connection_t
+ * pointers in <b>old_conns</b>: we do not launch listeners that are already
+ * in that list.  Instead, we just remove them from the list.
+ *
+ * All new connections we launch are added to <b>new_conns</b>.
+ */
+static int
+retry_listeners(smartlist_t *old_conns,
+                int type, const config_line_t *cfg,
+                int port_option, const char *default_addr,
+                smartlist_t *new_conns,
+                int is_sockaddr_un)
+{
+  smartlist_t *ports = smartlist_create();
+  tor_addr_t dflt_addr;
+  int retval = 0;
 
-        if (listensockaddr) {
-          conn = connection_create_listener(listensockaddr, listensocklen,
-                                            type, address);
-          tor_free(listensockaddr);
-          tor_free(address);
-        } else
-          conn = NULL;
+  if (default_addr) {
+    tor_addr_from_str(&dflt_addr, default_addr);
+  } else {
+    tor_addr_make_unspec(&dflt_addr);
+  }
 
-        if (!conn) {
-          r = -1;
+  if (port_option) {
+    if (!cfg) {
+      port_cfg_t *port = tor_malloc_zero(sizeof(port_cfg_t));
+      tor_addr_copy(&port->addr, &dflt_addr);
+      port->port = port_option;
+      port->type = type;
+      smartlist_add(ports, port);
+    } else {
+      const config_line_t *c;
+      for (c = cfg; c; c = c->next) {
+        port_cfg_t *port;
+        tor_addr_t addr;
+        uint16_t portval = 0;
+        if (is_sockaddr_un) {
+          size_t len = strlen(c->value);
+          port = tor_malloc_zero(sizeof(port_cfg_t) + len + 1);
+          port->is_unix_addr = 1;
+          memcpy(port->unix_addr, c->value, len+1);
         } else {
-          if (new_conns)
-            smartlist_add(new_conns, conn);
+          if (tor_addr_port_parse(c->value, &addr, &portval) < 0) {
+            log_warn(LD_CONFIG, "Can't parse/resolve %s %s",
+                     c->key, c->value);
+            retval = -1;
+            continue;
+          }
+          port = tor_malloc_zero(sizeof(port_cfg_t));
+          tor_addr_copy(&port->addr, &addr);
         }
-    } SMARTLIST_FOREACH_END(cfg_line);
+        port->type = type;
+        port->port = portval ? portval : port_option;
+        smartlist_add(ports, port);
+      }
+    }
   }
 
-  if (free_launch_elts) {
-    SMARTLIST_FOREACH(launch, config_line_t *, cfg_line,
-                      config_free_lines(cfg_line));
-  }
-  smartlist_free(launch);
+  if (retval == -1)
+    goto cleanup;
 
-  return r;
+  retval = retry_listener_ports(old_conns, ports, new_conns);
+
+ cleanup:
+  SMARTLIST_FOREACH(ports, port_cfg_t *, p, tor_free(p));
+  smartlist_free(ports);
+  return retval;
 }
 
 /** Launch listeners for each port you should have open.  Only launch
@@ -1923,54 +1934,62 @@ int
 retry_all_listeners(smartlist_t *replaced_conns,
                     smartlist_t *new_conns)
 {
+  smartlist_t *listeners = smartlist_create();
   const or_options_t *options = get_options();
   int retval = 0;
   const uint16_t old_or_port = router_get_advertised_or_port(options);
   const uint16_t old_dir_port = router_get_advertised_dir_port(options, 0);
 
-  if (retry_listeners(CONN_TYPE_OR_LISTENER, options->ORListenAddress,
-                      options->ORPort, "0.0.0.0",
-                      replaced_conns, new_conns, options->ClientOnly,
-                      AF_INET)<0)
-    retval = -1;
-  if (retry_listeners(CONN_TYPE_DIR_LISTENER, options->DirListenAddress,
-                      options->DirPort, "0.0.0.0",
-                      replaced_conns, new_conns, options->ClientOnly,
-                      AF_INET)<0)
-    retval = -1;
-  if (retry_listeners(CONN_TYPE_AP_LISTENER, options->SocksListenAddress,
-                      options->SocksPort, "127.0.0.1",
-                      replaced_conns, new_conns, 0,
-                      AF_INET)<0)
-    retval = -1;
-  if (retry_listeners(CONN_TYPE_AP_TRANS_LISTENER, options->TransListenAddress,
-                      options->TransPort, "127.0.0.1",
-                      replaced_conns, new_conns, 0,
-                      AF_INET)<0)
-    retval = -1;
-  if (retry_listeners(CONN_TYPE_AP_NATD_LISTENER, options->NATDListenAddress,
-                      options->NATDPort, "127.0.0.1",
-                      replaced_conns, new_conns, 0,
-                      AF_INET)<0)
-    retval = -1;
-  if (retry_listeners(CONN_TYPE_AP_DNS_LISTENER, options->DNSListenAddress,
-                      options->DNSPort, "127.0.0.1",
-                      replaced_conns, new_conns, 0,
-                      AF_INET)<0)
+  SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) {
+    if (connection_is_listener(conn) && !conn->marked_for_close)
+      smartlist_add(listeners, conn);
+  } SMARTLIST_FOREACH_END(conn);
+
+  if (! options->ClientOnly) {
+    if (retry_listeners(listeners,
+                        CONN_TYPE_OR_LISTENER, options->ORListenAddress,
+                        options->ORPort, "0.0.0.0",
+                        new_conns, 0) < 0)
+      retval = -1;
+    if (retry_listeners(listeners,
+                        CONN_TYPE_DIR_LISTENER, options->DirListenAddress,
+                        options->DirPort, "0.0.0.0",
+                        new_conns, 0) < 0)
+      retval = -1;
+  }
+
+  if (retry_listener_ports(listeners,
+                           get_configured_client_ports(),
+                           new_conns) < 0)
     retval = -1;
-  if (retry_listeners(CONN_TYPE_CONTROL_LISTENER,
+  if (retry_listeners(listeners,
+                      CONN_TYPE_CONTROL_LISTENER,
                       options->ControlListenAddress,
                       options->ControlPort, "127.0.0.1",
-                      replaced_conns, new_conns, 0,
-                      AF_INET)<0)
+                      new_conns, 0) < 0)
     return -1;
-  if (retry_listeners(CONN_TYPE_CONTROL_LISTENER,
+  if (retry_listeners(listeners,
+                      CONN_TYPE_CONTROL_LISTENER,
                       options->ControlSocket,
                       options->ControlSocket ? 1 : 0, NULL,
-                      replaced_conns, new_conns, 0,
-                      AF_UNIX)<0)
+                      new_conns, 1) < 0)
     return -1;
 
+  /* Any members that were still in 'listeners' don't correspond to
+   * any configured port.  Kill 'em. */
+  SMARTLIST_FOREACH_BEGIN(listeners, connection_t *, conn) {
+    log_notice(LD_NET, "Closing no-longer-configured %s on %s:%d",
+               conn_type_to_string(conn->type), conn->address, conn->port);
+    if (replaced_conns) {
+      smartlist_add(replaced_conns, conn);
+    } else {
+      connection_close_immediate(conn);
+      connection_mark_for_close(conn);
+    }
+  } SMARTLIST_FOREACH_END(conn);
+
+  smartlist_free(listeners);
+
   if (old_or_port != router_get_advertised_or_port(options) ||
       old_dir_port != router_get_advertised_dir_port(options, 0)) {
     /* Our chosen ORPort or DirPort is not what it used to be: the
@@ -3994,13 +4013,7 @@ assert_connection_ok(connection_t *conn, time_t now)
 
   switch (conn->type)
     {
-    case CONN_TYPE_OR_LISTENER:
-    case CONN_TYPE_AP_LISTENER:
-    case CONN_TYPE_AP_TRANS_LISTENER:
-    case CONN_TYPE_AP_NATD_LISTENER:
-    case CONN_TYPE_DIR_LISTENER:
-    case CONN_TYPE_CONTROL_LISTENER:
-    case CONN_TYPE_AP_DNS_LISTENER:
+    CASE_ANY_LISTENER_TYPE:
       tor_assert(conn->state == LISTENER_STATE_READY);
       break;
     case CONN_TYPE_OR:

+ 1 - 0
src/or/connection.h

@@ -22,6 +22,7 @@ dir_connection_t *dir_connection_new(int socket_family);
 or_connection_t *or_connection_new(int socket_family);
 edge_connection_t *edge_connection_new(int type, int socket_family);
 control_connection_t *control_connection_new(int socket_family);
+listener_connection_t *listener_connection_new(int type, int socket_family);
 connection_t *connection_new(int type, int socket_family);
 
 void connection_link_connections(connection_t *conn_a, connection_t *conn_b);

+ 227 - 2
src/or/connection_edge.c

@@ -1671,6 +1671,9 @@ connection_ap_handshake_rewrite_and_attach(edge_connection_t *conn,
             safe_str_client(socks->address),
             socks->port);
 
+  if (! conn->original_dest_address)
+    conn->original_dest_address = tor_strdup(conn->socks_request->address);
+
   if (socks->command == SOCKS_COMMAND_RESOLVE &&
       !tor_inet_aton(socks->address, &addr_tmp) &&
       options->AutomapHostsOnResolve && options->AutomapHostsSuffixes) {
@@ -2494,7 +2497,9 @@ connection_ap_handshake_send_resolve(edge_connection_t *ap_conn)
 edge_connection_t *
 connection_ap_make_link(connection_t *partner,
                         char *address, uint16_t port,
-                        const char *digest, int use_begindir, int want_onehop)
+                        const char *digest,
+                        int session_group, int isolation_flags,
+                        int use_begindir, int want_onehop)
 {
   edge_connection_t *conn;
 
@@ -2524,6 +2529,11 @@ connection_ap_make_link(connection_t *partner,
                   digest, DIGEST_LEN);
   }
 
+  /* Populate isolation fields. */
+  conn->original_dest_address = tor_strdup(address);
+  conn->session_group = session_group;
+  conn->isolation_flags = isolation_flags;
+
   conn->_base.address = tor_strdup("(Tor_internal)");
   tor_addr_make_unspec(&conn->_base.addr);
   conn->_base.port = 0;
@@ -3166,7 +3176,7 @@ connection_edge_is_rendezvous_stream(edge_connection_t *conn)
  * resolved.)
  */
 int
-connection_ap_can_use_exit(edge_connection_t *conn, const node_t *exit)
+connection_ap_can_use_exit(const edge_connection_t *conn, const node_t *exit)
 {
   const or_options_t *options = get_options();
 
@@ -3266,3 +3276,218 @@ parse_extended_hostname(char *address, int allowdotexit)
     return BAD_HOSTNAME;
 }
 
+/** Return true iff <b>a</b> and <b>b</b> have isolation rules and fields that
+ * make it permissible to put them on the same circuit.*/
+int
+connection_edge_streams_are_compatible(const edge_connection_t *a,
+                                       const edge_connection_t *b)
+{
+  const uint8_t iso = a->isolation_flags | b->isolation_flags;
+
+  if (! a->original_dest_address) {
+    log_warn(LD_BUG, "Reached connection_edge_streams_are_compatible without "
+             "having set a->original_dest_address");
+    ((edge_connection_t*)a)->original_dest_address =
+      tor_strdup(a->socks_request->address);
+  }
+  if (! b->original_dest_address) {
+    log_warn(LD_BUG, "Reached connection_edge_streams_are_compatible without "
+             "having set b->original_dest_address");
+    ((edge_connection_t*)b)->original_dest_address =
+      tor_strdup(a->socks_request->address);
+  }
+
+  if (iso & ISO_STREAM)
+    return 0;
+
+  if ((iso & ISO_DESTPORT) && a->socks_request->port != b->socks_request->port)
+    return 0;
+  if ((iso & ISO_DESTADDR) &&
+      strcasecmp(a->original_dest_address, b->original_dest_address))
+    return 0;
+  if ((iso & ISO_SOCKSAUTH) &&
+      (strcmp_opt(a->socks_request->username, b->socks_request->username) ||
+       strcmp_opt(a->socks_request->password, b->socks_request->password)))
+    return 0;
+  if ((iso & ISO_CLIENTPROTO) &&
+      (TO_CONN(a)->type != TO_CONN(b)->type ||
+       a->socks_request->socks_version != b->socks_request->socks_version))
+    return 0;
+  if ((iso & ISO_CLIENTADDR) &&
+      !tor_addr_eq(&TO_CONN(a)->addr, &TO_CONN(b)->addr))
+    return 0;
+  if ((iso & ISO_SESSIONGRP) && a->session_group != b->session_group)
+    return 0;
+  if ((iso & ISO_NYM_EPOCH) && a->nym_epoch != b->nym_epoch)
+    return 0;
+
+  return 1;
+}
+
+/**
+ * Return true iff none of the isolation flags and fields in <b>conn</b>
+ * should prevent it from being attached to <b>circ</b>.
+ */
+int
+connection_edge_compatible_with_circuit(const edge_connection_t *conn,
+                                        const origin_circuit_t *circ)
+{
+  const uint8_t iso = conn->isolation_flags;
+
+  /* If circ has never been used for an isolated connection, we can
+   * totally use it for this one. */
+  if (!circ->isolation_values_set)
+    return 1;
+
+  /* If circ has been used for connections having more than one value
+   * for some field f, it will have the corresponding bit set in
+   * isolation_flags_mixed.  If isolation_flags_mixed has any bits
+   * in common with iso, then conn must be isolated from at least
+   * one stream that has been attached to circ. */
+  if ((iso & circ->isolation_flags_mixed) != 0) {
+    /* For at least one field where conn is isolated, the circuit
+     * already has mixed streams. */
+    return 0;
+  }
+
+  if (! conn->original_dest_address) {
+    log_warn(LD_BUG, "Reached connection_edge_compatible_with_circuit without "
+             "having set conn->original_dest_address");
+    ((edge_connection_t*)conn)->original_dest_address =
+      tor_strdup(conn->socks_request->address);
+  }
+
+  /* If isolation_values_set, then the circuit is not compatible with
+   * any new ISO_STREAM stream. */
+  if (iso & ISO_STREAM)
+    return 0;
+
+  if ((iso & ISO_DESTPORT) && conn->socks_request->port != circ->dest_port)
+    return 0;
+  if ((iso & ISO_DESTADDR) &&
+      strcasecmp(conn->original_dest_address, circ->dest_address))
+    return 0;
+  if ((iso & ISO_SOCKSAUTH) &&
+      (strcmp_opt(conn->socks_request->username, circ->socks_username) ||
+       strcmp_opt(conn->socks_request->password, circ->socks_password)))
+    return 0;
+  if ((iso & ISO_CLIENTPROTO) &&
+      (TO_CONN(conn)->type != circ->client_proto_type ||
+       conn->socks_request->socks_version != circ->client_proto_socksver))
+    return 0;
+  if ((iso & ISO_CLIENTADDR) &&
+      !tor_addr_eq(&TO_CONN(conn)->addr, &circ->client_addr))
+    return 0;
+  if ((iso & ISO_SESSIONGRP) && conn->session_group != circ->session_group)
+    return 0;
+  if ((iso & ISO_NYM_EPOCH) && conn->nym_epoch != circ->nym_epoch)
+    return 0;
+
+  return 1;
+}
+
+/**
+ * If <b>dry_run</b> is false, update <b>circ</b>'s isolation flags and fields
+ * to reflect having had <b>conn</b> attached to it, and return 0.  Otherwise,
+ * if <b>dry_run</b> is true, then make no changes to <b>circ</b>, and return
+ * a bitfield of isolation flags that we would have to set in
+ * isolation_flags_mixed to add <b>conn</b> to <b>circ</b>, or -1 if
+ * <b>circ</b> has had no streams attached to it.
+ */
+int
+connection_edge_update_circuit_isolation(const edge_connection_t *conn,
+                                         origin_circuit_t *circ,
+                                         int dry_run)
+{
+  if (! conn->original_dest_address) {
+    log_warn(LD_BUG, "Reached connection_update_circuit_isolation without "
+             "having set conn->original_dest_address");
+    ((edge_connection_t*)conn)->original_dest_address =
+      tor_strdup(conn->socks_request->address);
+  }
+
+  if (!circ->isolation_values_set) {
+    if (dry_run)
+      return -1;
+    circ->dest_port = conn->socks_request->port;
+    circ->dest_address = tor_strdup(conn->original_dest_address);
+    circ->client_proto_type = TO_CONN(conn)->type;
+    circ->client_proto_socksver = conn->socks_request->socks_version;
+    tor_addr_copy(&circ->client_addr, &TO_CONN(conn)->addr);
+    circ->session_group = conn->session_group;
+    circ->nym_epoch = conn->nym_epoch;
+    circ->socks_username = conn->socks_request->username ?
+      tor_strdup(conn->socks_request->username) : NULL;
+    circ->socks_password = conn->socks_request->password ?
+      tor_strdup(conn->socks_request->password) : NULL;
+
+    circ->isolation_values_set = 1;
+    return 0;
+  } else {
+    uint8_t mixed = 0;
+    if (conn->socks_request->port != circ->dest_port)
+      mixed |= ISO_DESTPORT;
+    if (strcasecmp(conn->original_dest_address, circ->dest_address))
+      mixed |= ISO_DESTADDR;
+    if (strcmp_opt(conn->socks_request->username, circ->socks_username) ||
+        strcmp_opt(conn->socks_request->password, circ->socks_password))
+      mixed |= ISO_SOCKSAUTH;
+    if ((TO_CONN(conn)->type != circ->client_proto_type ||
+         conn->socks_request->socks_version != circ->client_proto_socksver))
+      mixed |= ISO_CLIENTPROTO;
+    if (!tor_addr_eq(&TO_CONN(conn)->addr, &circ->client_addr))
+      mixed |= ISO_CLIENTADDR;
+    if (conn->session_group != circ->session_group)
+      mixed |= ISO_SESSIONGRP;
+    if (conn->nym_epoch != circ->nym_epoch)
+      mixed |= ISO_NYM_EPOCH;
+
+    if (dry_run)
+      return mixed;
+
+    if ((mixed & conn->isolation_flags) != 0) {
+      log_warn(LD_BUG, "Updating a circuit with seemingly incomaptible "
+               "isolation flags.");
+    }
+    circ->isolation_flags_mixed |= mixed;
+    return 0;
+  }
+}
+
+/**
+ * Clear the isolation settings on <b>circ</b>.
+ *
+ * This only works on an open circuit that has never had a stream attached to
+ * it, and whose isolation settings are hypothetical.  (We set hypothetical
+ * isolation settings on circuits as we're launching them, so that we
+ * know whether they can handle more streams or whether we need to launch
+ * even more circuits.  Once the circuit is open, if it turns out that
+ * we no longer have any streams to attach to it, we clear the isolation flags
+ * and data so that other streams can have a chance.)
+ */
+void
+circuit_clear_isolation(origin_circuit_t *circ)
+{
+  if (circ->isolation_any_streams_attached) {
+    log_warn(LD_BUG, "Tried to clear the isolation status of a dirty circuit");
+    return;
+  }
+  if (TO_CIRCUIT(circ)->state != CIRCUIT_STATE_OPEN) {
+    log_warn(LD_BUG, "Tried to clear the isolation status of a non-open "
+             "circuit");
+    return;
+  }
+
+  circ->isolation_values_set = 0;
+  circ->isolation_flags_mixed = 0;
+  circ->client_proto_type = 0;
+  circ->client_proto_socksver = 0;
+  circ->dest_port = 0;
+  tor_addr_make_unspec(&circ->client_addr);
+  tor_free(circ->dest_address);
+  circ->session_group = -1;
+  circ->nym_epoch = 0;
+  tor_free(circ->socks_username);
+  tor_free(circ->socks_password);
+}
+

+ 12 - 1
src/or/connection_edge.h

@@ -36,6 +36,8 @@ int connection_ap_handshake_send_resolve(edge_connection_t *ap_conn);
 edge_connection_t  *connection_ap_make_link(connection_t *partner,
                                             char *address, uint16_t port,
                                             const char *digest,
+                                            int session_group,
+                                            int isolation_flags,
                                             int use_begindir, int want_onehop);
 void connection_ap_handshake_socks_reply(edge_connection_t *conn, char *reply,
                                          size_t replylen,
@@ -51,7 +53,7 @@ int connection_exit_begin_conn(cell_t *cell, circuit_t *circ);
 int connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ);
 void connection_exit_connect(edge_connection_t *conn);
 int connection_edge_is_rendezvous_stream(edge_connection_t *conn);
-int connection_ap_can_use_exit(edge_connection_t *conn,
+int connection_ap_can_use_exit(const edge_connection_t *conn,
                                const node_t *exit);
 void connection_ap_expire_beginning(void);
 void connection_ap_attach_pending(void);
@@ -103,5 +105,14 @@ hostname_type_t parse_extended_hostname(char *address, int allowdotexit);
 int get_pf_socket(void);
 #endif
 
+int connection_edge_streams_are_compatible(const edge_connection_t *a,
+                                           const edge_connection_t *b);
+int connection_edge_compatible_with_circuit(const edge_connection_t *conn,
+                                            const origin_circuit_t *circ);
+int connection_edge_update_circuit_isolation(const edge_connection_t *conn,
+                                             origin_circuit_t *circ,
+                                             int dry_run);
+void circuit_clear_isolation(origin_circuit_t *circ);
+
 #endif
 

+ 7 - 1
src/or/directory.c

@@ -973,6 +973,10 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr,
     }
   } else { /* we want to connect via a tor connection */
     edge_connection_t *linked_conn;
+    /* Anonymized tunneled connections can never share a circuit.
+     * One-hop directory connections can share circuits with each other
+     * but nothing else. */
+    int iso_flags = anonymized_connection ? ISO_STREAM : ISO_SESSIONGRP;
 
     /* If it's an anonymized connection, remember the fact that we
      * wanted it for later: maybe we'll want it again soon. */
@@ -988,7 +992,9 @@ directory_initiate_command_rend(const char *address, const tor_addr_t *_addr,
     linked_conn =
       connection_ap_make_link(TO_CONN(conn),
                               conn->_base.address, conn->_base.port,
-                              digest, use_begindir, conn->dirconn_direct);
+                              digest,
+                              SESSION_GROUP_DIRCONN, iso_flags,
+                              use_begindir, conn->dirconn_direct);
     if (!linked_conn) {
       log_warn(LD_NET,"Making tunnel to dirserver failed.");
       connection_mark_for_close(TO_CONN(conn));

+ 22 - 7
src/or/dnsserv.c

@@ -29,8 +29,9 @@
  * DNSPort.  We need to eventually answer the request <b>req</b>.
  */
 static void
-evdns_server_callback(struct evdns_server_request *req, void *_data)
+evdns_server_callback(struct evdns_server_request *req, void *data_)
 {
+  const listener_connection_t *listener = data_;
   edge_connection_t *conn;
   int i = 0;
   struct evdns_server_question *q = NULL;
@@ -43,7 +44,7 @@ evdns_server_callback(struct evdns_server_request *req, void *_data)
   char *q_name;
 
   tor_assert(req);
-  tor_assert(_data == NULL);
+
   log_info(LD_APP, "Got a new DNS request!");
 
   req->flags |= 0x80; /* set RA */
@@ -131,6 +132,9 @@ evdns_server_callback(struct evdns_server_request *req, void *_data)
           sizeof(conn->socks_request->address));
 
   conn->dns_server_request = req;
+  conn->isolation_flags = listener->isolation_flags;
+  conn->session_group = listener->session_group;
+  conn->nym_epoch = get_signewnym_epoch();
 
   if (connection_add(TO_CONN(conn)) < 0) {
     log_warn(LD_APP, "Couldn't register dummy connection for DNS request");
@@ -181,6 +185,11 @@ dnsserv_launch_request(const char *name, int reverse)
   strlcpy(conn->socks_request->address, name,
           sizeof(conn->socks_request->address));
 
+  conn->original_dest_address = tor_strdup(name);
+  conn->session_group = SESSION_GROUP_CONTROL_RESOLVE;
+  conn->nym_epoch = get_signewnym_epoch();
+  conn->isolation_flags = ISO_DEFAULT;
+
   if (connection_add(TO_CONN(conn))<0) {
     log_warn(LD_APP, "Couldn't register dummy connection for RESOLVE request");
     connection_free(TO_CONN(conn));
@@ -305,12 +314,15 @@ dnsserv_resolved(edge_connection_t *conn,
 void
 dnsserv_configure_listener(connection_t *conn)
 {
+  listener_connection_t *listener_conn;
   tor_assert(conn);
   tor_assert(SOCKET_OK(conn->s));
   tor_assert(conn->type == CONN_TYPE_AP_DNS_LISTENER);
 
-  conn->dns_server_port =
-    tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, NULL);
+  listener_conn = TO_LISTENER_CONN(conn);
+  listener_conn->dns_server_port =
+    tor_evdns_add_server_port(conn->s, 0, evdns_server_callback,
+                              listener_conn);
 }
 
 /** Free the evdns server port for <b>conn</b>, which must be an
@@ -318,12 +330,15 @@ dnsserv_configure_listener(connection_t *conn)
 void
 dnsserv_close_listener(connection_t *conn)
 {
+  listener_connection_t *listener_conn;
   tor_assert(conn);
   tor_assert(conn->type == CONN_TYPE_AP_DNS_LISTENER);
 
-  if (conn->dns_server_port) {
-    evdns_close_server_port(conn->dns_server_port);
-    conn->dns_server_port = NULL;
+  listener_conn = TO_LISTENER_CONN(conn);
+
+  if (listener_conn->dns_server_port) {
+    evdns_close_server_port(listener_conn->dns_server_port);
+    listener_conn->dns_server_port = NULL;
   }
 }
 

+ 12 - 1
src/or/main.c

@@ -114,6 +114,8 @@ static time_t time_to_check_for_correct_dns = 0;
 static time_t time_of_last_signewnym = 0;
 /** Is there a signewnym request we're currently waiting to handle? */
 static int signewnym_is_pending = 0;
+/** How many times have we called newnym? */
+static unsigned newnym_epoch = 0;
 
 /** Smartlist of all open connections. */
 static smartlist_t *connection_array = NULL;
@@ -290,7 +292,7 @@ connection_unregister_events(connection_t *conn)
     conn->bufev = NULL;
   }
 #endif
-  if (conn->dns_server_port) {
+  if (conn->type == CONN_TYPE_AP_DNS_LISTENER) {
     dnsserv_close_listener(conn);
   }
 }
@@ -1038,9 +1040,18 @@ signewnym_impl(time_t now)
   time_of_last_signewnym = now;
   signewnym_is_pending = 0;
 
+  ++newnym_epoch;
+
   control_event_signal(SIGNEWNYM);
 }
 
+/** Return the number of times that signewnym has been called. */
+unsigned
+get_signewnym_epoch(void)
+{
+  return newnym_epoch;
+}
+
 /** Perform regular maintenance tasks.  This function gets run once per
  * second by second_elapsed_callback().
  */

+ 1 - 0
src/or/main.h

@@ -52,6 +52,7 @@ void ip_address_changed(int at_interface);
 void dns_servers_relaunch_checks(void);
 
 long get_uptime(void);
+unsigned get_signewnym_epoch(void);
 
 void handle_signals(int is_parent);
 void process_signal(uintptr_t sig);

+ 151 - 10
src/or/or.h

@@ -938,6 +938,7 @@ typedef struct socks_request_t socks_request_t;
 #define EDGE_CONNECTION_MAGIC 0xF0374013u
 #define DIR_CONNECTION_MAGIC 0x9988ffeeu
 #define CONTROL_CONNECTION_MAGIC 0x8abc765du
+#define LISTENER_CONNECTION_MAGIC 0x1a1ac741u
 
 /** Description of a connection to another host or process, and associated
  * data.
@@ -1043,15 +1044,31 @@ typedef struct connection_t {
   /** Unique identifier for this connection on this Tor instance. */
   uint64_t global_identifier;
 
-  /* XXXX023 move this field, and all the listener-only fields (just
-     socket_family, I think), into a new listener_connection_t subtype. */
+  /** Unique ID for measuring tunneled network status requests. */
+  uint64_t dirreq_id;
+} connection_t;
+
+typedef struct listener_connection_t {
+  connection_t _base;
+
   /** If the connection is a CONN_TYPE_AP_DNS_LISTENER, this field points
    * to the evdns_server_port it uses to listen to and answer connections. */
   struct evdns_server_port *dns_server_port;
 
-  /** Unique ID for measuring tunneled network status requests. */
-  uint64_t dirreq_id;
-} connection_t;
+  /** @name Isolation parameters
+   *
+   * For an AP listener, these fields describe how to isolate streams that
+   * arrive on the listener.
+   *
+   * @{
+   */
+  /** The session group for this listener. */
+  int session_group;
+  /** One or more ISO_ flags to describe how to isolate streams. */
+  uint8_t isolation_flags;
+  /**@}*/
+
+} listener_connection_t;
 
 /** Stores flags and information related to the portion of a v2 Tor OR
  * connection handshake that happens after the TLS handshake is finished.
@@ -1190,6 +1207,20 @@ typedef struct edge_connection_t {
   /** What rendezvous service are we querying for? (AP only) */
   rend_data_t *rend_data;
 
+  /* === Isolation related, AP only. === */
+  /** AP only: based on which factors do we isolate this stream? */
+  uint8_t isolation_flags;
+  /** AP only: what session group is this stream in? */
+  int session_group;
+  /** AP only: The newnym epoch in which we created this connection. */
+  unsigned nym_epoch;
+  /** AP only: The original requested address before we rewrote it. */
+  char *original_dest_address;
+  /* Other fields to isolate on already exist.  The ClientAddr is addr.  The
+     ClientProtocol is a combination of type and socks_request->
+     socks_version.  SocksAuth is socks_request->username/password.
+     DestAddr is in socks_request->address. */
+
   /** Number of times we've reassigned this application connection to
    * a new circuit. We keep track because the timeout is longer if we've
    * already retried several times. */
@@ -1321,6 +1352,9 @@ static edge_connection_t *TO_EDGE_CONN(connection_t *);
 /** Convert a connection_t* to an control_connection_t*; assert if the cast is
  * invalid. */
 static control_connection_t *TO_CONTROL_CONN(connection_t *);
+/** Convert a connection_t* to an listener_connection_t*; assert if the cast is
+ * invalid. */
+static listener_connection_t *TO_LISTENER_CONN(connection_t *);
 
 static INLINE or_connection_t *TO_OR_CONN(connection_t *c)
 {
@@ -1342,6 +1376,11 @@ static INLINE control_connection_t *TO_CONTROL_CONN(connection_t *c)
   tor_assert(c->magic == CONTROL_CONNECTION_MAGIC);
   return DOWNCAST(control_connection_t, c);
 }
+static INLINE listener_connection_t *TO_LISTENER_CONN(connection_t *c)
+{
+  tor_assert(c->magic == LISTENER_CONNECTION_MAGIC);
+  return DOWNCAST(listener_connection_t, c);
+}
 
 /* Conditional macros to help write code that works whether bufferevents are
    disabled or not.
@@ -2424,6 +2463,48 @@ typedef struct origin_circuit_t {
   /* XXXX NM This can get re-used after 2**32 circuits. */
   uint32_t global_identifier;
 
+  /** True if we have associated one stream to this circuit, thereby setting
+   * the isolation paramaters for this circuit.  Note that this doesn't
+   * necessarily mean that we've <em>attached</em> any streams to the circuit:
+   * we may only have marked up this circuit during the launch process.
+   */
+  unsigned int isolation_values_set : 1;
+  /** True iff any stream has <em>ever</em> been attached to this circuit.
+   *
+   * In a better world we could use timestamp_dirty for this, but
+   * timestamp_dirty is far too overloaded at the moment.
+   */
+  unsigned int isolation_any_streams_attached : 1;
+
+  /** A bitfield of ISO_* flags for every isolation field such that this
+   * circuit has had streams with more than one value for that field
+   * attached to it. */
+  uint8_t isolation_flags_mixed;
+
+  /** @name Isolation parameters
+   *
+   * If any streams have been associated with this circ (isolation_values_set
+   * == 1), and all streams associated with the circuit have had the same
+   * value for some field ((isolation_flags_mixed & ISO_FOO) == 0), then these
+   * elements hold the value for that field.
+   *
+   * Note again that "associated" is not the same as "attached": we
+   * preliminarily associate streams with a circuit while the circuit is being
+   * launched, so that we can tell whether we need to launch more circuits.
+   *
+   * @{
+   */
+  uint8_t client_proto_type;
+  uint8_t client_proto_socksver;
+  uint16_t dest_port;
+  tor_addr_t client_addr;
+  char *dest_address;
+  int session_group;
+  unsigned nym_epoch;
+  char *socks_username;
+  char *socks_password;
+  /**@}*/
+
 } origin_circuit_t;
 
 /** An or_circuit_t holds information needed to implement a circuit at an
@@ -2542,6 +2623,60 @@ typedef enum invalid_router_usage_t {
 #define MIN_CONSTRAINED_TCP_BUFFER 2048
 #define MAX_CONSTRAINED_TCP_BUFFER 262144  /* 256k */
 
+/** @name Isolation flags
+
+    Ways to isolate client streams
+
+    @{
+*/
+/** Isolate based on destination port */
+#define ISO_DESTPORT    (1u<<0)
+/** Isolate based on destination address */
+#define ISO_DESTADDR    (1u<<1)
+/** Isolate based on SOCKS authentication */
+#define ISO_SOCKSAUTH   (1u<<2)
+/** Isolate based on client protocol choice */
+#define ISO_CLIENTPROTO (1u<<3)
+/** Isolate based on client address */
+#define ISO_CLIENTADDR  (1u<<4)
+/** Isolate based on session group (always on). */
+#define ISO_SESSIONGRP  (1u<<5)
+/** Isolate based on newnym epoch (always on). */
+#define ISO_NYM_EPOCH   (1u<<6)
+/** Isolate all streams (Internal only). */
+#define ISO_STREAM      (1u<<7)
+/**@}*/
+
+/** Default isolation level for ports. */
+#define ISO_DEFAULT (ISO_CLIENTADDR|ISO_SOCKSAUTH|ISO_SESSIONGRP|ISO_NYM_EPOCH)
+
+/** Indicates that we haven't yet set a session group on a port_cfg_t. */
+#define SESSION_GROUP_UNSET -1
+/** Session group reserved for directory connections */
+#define SESSION_GROUP_DIRCONN -2
+/** Session group reserved for resolve requests launched by a controller */
+#define SESSION_GROUP_CONTROL_RESOLVE -3
+/** First automatically allocated session group number */
+#define SESSION_GROUP_FIRST_AUTO -4
+
+/** Configuration for a single port that we're listening on. */
+typedef struct port_cfg_t {
+  tor_addr_t addr; /**< The actual IP to listen on, if !is_unix_addr. */
+  int port; /**< The configured port, or CFG_AUTO_PORT to tell Tor to pick its
+             * own port. */
+  uint8_t type; /**< One of CONN_TYPE_*_LISTENER */
+  unsigned is_unix_addr : 1; /**< True iff this is an AF_UNIX address. */
+
+  /* Client port types (socks, dns, trans, natd) only: */
+  uint8_t isolation_flags; /**< Zero or more isolation flags */
+  int session_group; /**< A session group, or -1 if this port is not in a
+                      * session group. */
+
+  /* Unix sockets only: */
+  /** Path for an AF_UNIX address */
+  char unix_addr[FLEXIBLE_ARRAY_MEMBER];
+} port_cfg_t;
+
 /** A linked list of lines in a config file. */
 typedef struct config_line_t {
   char *key;
@@ -2637,16 +2772,17 @@ typedef struct {
   char *User; /**< Name of user to run Tor as. */
   char *Group; /**< Name of group to run Tor as. */
   int ORPort; /**< Port to listen on for OR connections. */
-  int SocksPort; /**< Port to listen on for SOCKS connections. */
-  /** Port to listen on for transparent pf/netfilter connections. */
-  int TransPort;
-  int NATDPort; /**< Port to listen on for transparent natd connections. */
+  config_line_t *SocksPort; /**< Ports to listen on for SOCKS connections. */
+  /** Ports to listen on for transparent pf/netfilter connections. */
+  config_line_t *TransPort;
+  config_line_t *NATDPort; /**< Ports to listen on for transparent natd
+                            * connections. */
   int ControlPort; /**< Port to listen on for control connections. */
   config_line_t *ControlSocket; /**< List of Unix Domain Sockets to listen on
                                  * for control connections. */
   int ControlSocketsGroupWritable; /**< Boolean: Are control sockets g+rw? */
   int DirPort; /**< Port to listen on for directory connections. */
-  int DNSPort; /**< Port to listen on for DNS requests. */
+  config_line_t *DNSPort; /**< Port to listen on for DNS requests. */
   int AssumeReachable; /**< Whether to publish our descriptor regardless. */
   int AuthoritativeDir; /**< Boolean: is this an authoritative directory? */
   int V1AuthoritativeDir; /**< Boolean: is this an authoritative directory
@@ -3108,6 +3244,11 @@ typedef struct {
   /** Should that file be group-readable? */
   int ControlPortFileGroupReadable;
 
+#define MAX_MAX_CLIENT_CIRCUITS_PENDING 1024
+  /** Maximum number of non-open general-purpose origin circuits to allow at
+   * once. */
+  int MaxClientCircuitsPending;
+
 } or_options_t;
 
 /** Persistent state for an onion router, as saved to disk. */

+ 3 - 5
src/or/router.c

@@ -1116,14 +1116,12 @@ set_server_advertised(int s)
   server_is_advertised = s;
 }
 
-/** Return true iff we are trying to be a socks proxy. */
+/** Return true iff we are trying to proxy client connections. */
 int
 proxy_mode(const or_options_t *options)
 {
-  return (options->SocksPort != 0 ||
-          options->TransPort != 0 ||
-          options->NATDPort != 0 ||
-          options->DNSPort != 0);
+  (void)options;
+  return smartlist_len(get_configured_client_ports()) > 0;
 }
 
 /** Decide if we're a publishable server. We are a publishable server if: