diff --git a/changes/prop171 b/changes/prop171 new file mode 100644 index 0000000000..91c463f94d --- /dev/null +++ b/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. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 9607632e1d..821098b56e 100644 --- a/doc/tor.1.txt +++ b/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) + 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__]:: +**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. + 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** __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) +**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 diff --git a/src/common/util.c b/src/common/util.c index b95ee3a612..15b6e7130e 100644 --- a/src/common/util.c +++ b/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 v. */ +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. */ diff --git a/src/common/util.h b/src/common/util.h index 6496c42db8..99355871f6 100644 --- a/src/common/util.h +++ b/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 a divided by b, for nonnegative a * and positive 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)); diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 93f5fd3493..0fefe9871e 100644 --- a/src/or/circuitlist.c +++ b/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. */ diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 460c41f75d..b4860440cb 100644 --- a/src/or/circuituse.c +++ b/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 a is better than circuit b for - * purpose, and return 0 otherwise. Used by circuit_get_best. + * conn, 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 address 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) * conn'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; diff --git a/src/or/config.c b/src/or/config.c index c6dd4673aa..efee014894 100644 --- a/src/or/config.c +++ b/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 port */ +static void +port_cfg_free(port_cfg_t *port) +{ + tor_free(port); +} + +/** Warn for every port in ports 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 ports, and + * entries of the "FooListenAddress" type from the list + * listenaddrs. 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 portname. + * + * If no address is specified, default to defaultaddr. If no + * FooPort is given, default to defaultport (if 0, there is no default). + * + * If CL_PORT_NO_OPTIONS is set in flags, do not allow stream + * isolation options in the FooPort entries. + * + * If CL_PORT_WARN_NONLOCAL is set in flags, warn if any of the + * ports are not on a local address. + * + * Unless CL_PORT_ALLOW_EXTRA_LISTENADDR is set in flags, warn + * if FooListenAddress is set but FooPort is 0. + * + * On success, if out is given, add a new port_cfg_t entry to + * out 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 + * options. On success, set *n_ports_out to the number of + * ports that are listed and return 0. On failure, set *msg to a + * description of the problem and return -1. + * + * If validate_only is false, set configured_client_ports to the + * new list of ports parsed from options. + **/ +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 diff --git a/src/or/config.h b/src/or/config.h index 8a06f4443a..4a5afdf178 100644 --- a/src/or/config.h +++ b/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, diff --git a/src/or/connection.c b/src/or/connection.c index 8b9fb126d3..59a7b80deb 100644 --- a/src/or/connection.c +++ b/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 type * to make or receive connections of address family socket_family. 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. - * listenaddress provides the host and optionally the port information - * for the new structure. If no port is provided in listenaddress then - * listenport is used. - * - * If not NULL readable_address will contain a copy of the host part of - * listenaddress. - * - * 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. * listenaddress 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,6 +1742,111 @@ connection_read_proxy_handshake(connection_t *conn) return ret; } +/** Given a list of listener connections in old_conns, and list of + * port_cfg_t entries in ports, open a new listener for every port in + * ports that does not already have a listener in old_conns. + * + * Remove from old_conns every connection that has a corresponding + * entry in ports. Add to new_conns new every connection we + * launch. + * + * Return 0 on success, -1 on failure. + **/ +static int +retry_listener_ports(smartlist_t *old_conns, + const smartlist_t *ports, + smartlist_t *new_conns) +{ + smartlist_t *launch = smartlist_create(); + int r = 0; + + smartlist_add_all(launch, ports); + + /* 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; + + /* Okay, so this is a listener. Is it configured? */ + 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; + } + } else { + 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; + } + } + } 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, 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. */ + 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 type. (A * listener is configured if port_option is non-zero. If any @@ -1748,168 +1854,73 @@ connection_read_proxy_handshake(connection_t *conn) * connection binding to each one. Otherwise, create a single * connection binding to the address default_addr.) * - * 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. + * We assume that we're starting with a list of existing listener connection_t + * pointers in old_conns: we do not launch listeners that are already + * in that list. Instead, we just remove them from the list. * - * If disable_all_conns is set, then never open new conns, and - * close the existing ones. - * - * Add all old conns that should be closed to replaced_conns. - * Add all new connections to new_conns. + * All new connections we launch are added to new_conns. */ static int -retry_listeners(int type, config_line_t *cfg, +retry_listeners(smartlist_t *old_conns, + int type, const 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) + int is_sockaddr_un) { - smartlist_t *launch = smartlist_create(), *conns; - int free_launch_elts = 1; - int r; - config_line_t *c; - connection_t *conn; - config_line_t *line; + smartlist_t *ports = smartlist_create(); + tor_addr_t dflt_addr; + int retval = 0; - tor_assert(socket_family == AF_INET || socket_family == AF_UNIX); - - 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); + if (default_addr) { + tor_addr_from_str(&dflt_addr, default_addr); + } else { + tor_addr_make_unspec(&dflt_addr); } - /* - SMARTLIST_FOREACH(launch, config_line_t *, l, - log_fn(LOG_NOTICE, "#%s#%s", l->key, l->value)); - */ - - 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); - } - }); - 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); - } + 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 { - /* It's configured; 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); - } - }); - - /* 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); - } - - if (listensockaddr) { - conn = connection_create_listener(listensockaddr, listensocklen, - type, address); - tor_free(listensockaddr); - tor_free(address); - } else - conn = NULL; - - if (!conn) { - r = -1; + 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) + 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_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) - 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: diff --git a/src/or/connection.h b/src/or/connection.h index fedeba4b3a..d97729b446 100644 --- a/src/or/connection.h +++ b/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); diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index f2ddfc76dc..4bbb080124 100644 --- a/src/or/connection_edge.c +++ b/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 a and 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 conn + * should prevent it from being attached to circ. + */ +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 dry_run is false, update circ's isolation flags and fields + * to reflect having had conn attached to it, and return 0. Otherwise, + * if dry_run is true, then make no changes to circ, and return + * a bitfield of isolation flags that we would have to set in + * isolation_flags_mixed to add conn to circ, or -1 if + * circ 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 circ. + * + * 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); +} + diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 1cb1281dbd..85293a0913 100644 --- a/src/or/connection_edge.h +++ b/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 diff --git a/src/or/directory.c b/src/or/directory.c index 2667f1212f..744bc120fb 100644 --- a/src/or/directory.c +++ b/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)); diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index f2c473dfc5..35279c4702 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -29,8 +29,9 @@ * DNSPort. We need to eventually answer the request req. */ 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 conn, 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; } } diff --git a/src/or/main.c b/src/or/main.c index 1baefc71bd..54e6451e6a 100644 --- a/src/or/main.c +++ b/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(). */ diff --git a/src/or/main.h b/src/or/main.h index db251356fd..c8903642de 100644 --- a/src/or/main.h +++ b/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); diff --git a/src/or/or.h b/src/or/or.h index 7a2bde59fe..47cee35e5b 100644 --- a/src/or/or.h +++ b/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 attached 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 ever 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. */ diff --git a/src/or/router.c b/src/or/router.c index eaad57bb99..531d3fb40f 100644 --- a/src/or/router.c +++ b/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: