From ddc65e2b3303559ab7b842a176ee6c2eda9e4027 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 30 Jun 2011 14:01:02 -0400 Subject: [PATCH 01/16] Parse prop171 options; refactor listener/port option code Proposal 171 gives us a new syntax for parsing client port options. You can now have as many FooPort options as you want (for Foo in Socks, Trans, DNS, NATD), and they can have address:port arguments, and you can specify the level of isolation on those ports. Additionally, this patch refactors the client port parsing logic to use a new type, port_cfg_t. Previously, ports to be bound were half-parsed in config.c, and later re-parsed in connection.c when we're about to bind them. Now, parsing a port means converting it into a port_cfg_t, and binding it uses only a port_cfg_t, without needing to parse the user-provided strings at all. We should do a related refactoring on other port types. For control ports, that'll be easy enough. For ORPort and DirPort, we'll want to do this when we solve proposal 118 (letting servers bind to and advertise multiple ports). This implements tickets 3514 and 3515. --- changes/prop171 | 11 ++ src/or/config.c | 426 ++++++++++++++++++++++++++++++++++++++------ src/or/config.h | 2 + src/or/connection.c | 424 +++++++++++++++++++++---------------------- src/or/or.h | 52 +++++- src/or/router.c | 8 +- 6 files changed, 631 insertions(+), 292 deletions(-) create mode 100644 changes/prop171 diff --git a/changes/prop171 b/changes/prop171 new file mode 100644 index 0000000000..057556edd9 --- /dev/null +++ b/changes/prop171 @@ -0,0 +1,11 @@ + 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/src/or/config.c b/src/or/config.c index c6dd4673aa..0774b28916 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"), @@ -321,7 +321,7 @@ static config_var_t _option_vars[] = { 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 +374,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 +385,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 +577,8 @@ 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 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 +648,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 +762,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 +3038,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 +3062,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 +3105,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. " @@ -4885,6 +4846,355 @@ parse_dir_server_line(const char *line, dirinfo_type_t required_type, return r; } +/** 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->sessiongroup = -1; + cfg->isolate = 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->sessiongroup = -1; + cfg->isolate = 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 = -1; + 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 = 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->sessiongroup = sessiongroup; + cfg->isolate = 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, tor_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, tor_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..db592c0a44 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -701,48 +701,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. @@ -1741,6 +1699,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; + 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); + 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 +1811,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 +1891,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 diff --git a/src/or/or.h b/src/or/or.h index 7a2bde59fe..1bcfd9fe59 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2542,6 +2542,47 @@ 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) +/**@}*/ + +/** Default isolation level for ports. */ +#define ISO_DEFAULT (ISO_CLIENTADDR|ISO_SOCKSAUTH|ISO_SESSIONGRP) + +/** Configuration for a single port that we're listening on. */ +typedef struct port_cfg_t { + tor_addr_t addr; /**< The configured address to listen on. */ + 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 isolate; /**< Zero or more isolation flags */ + int sessiongroup; /**< 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 +2678,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 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: From d2205ca458e25115287462292087f0f5ed797c02 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 5 Jul 2011 17:11:22 -0400 Subject: [PATCH 02/16] Refactor listener_connection_t into its own type. This will allow us to add more fields to listener_connection_t without bloating the other connection types. --- src/or/connection.c | 46 +++++++++++++++++++++++++++++++-------------- src/or/connection.h | 1 + src/or/dnsserv.c | 13 +++++++++---- src/or/main.c | 2 +- src/or/or.h | 22 +++++++++++++++++----- 5 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/or/connection.c b/src/or/connection.c index db592c0a44..db0d78975a 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -76,6 +76,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 +125,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 +268,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 +299,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 +342,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 +415,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; @@ -3970,13 +3994,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/dnsserv.c b/src/or/dnsserv.c index f2c473dfc5..350b138726 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -305,11 +305,13 @@ 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 = + listener_conn = TO_LISTENER_CONN(conn); + listener_conn->dns_server_port = tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, NULL); } @@ -318,12 +320,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..16d4f0e7aa 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -290,7 +290,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); } } diff --git a/src/or/or.h b/src/or/or.h index 1bcfd9fe59..e284a14ce3 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,18 @@ 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; +} 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. @@ -1321,6 +1325,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 +1349,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. From ea0a9b16b9459c9b45ac320e87392dbcaa799b1d Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 6 Jul 2011 16:03:47 -0400 Subject: [PATCH 03/16] (Unused) backend logic for stream isolation This patch adds fields to track how streams should be isolated, and ensures that those fields are set correctly. It also adds fields to track what streams can go on a circuit, and adds functions to see whether a streams can go on a circuit and update the circuit accordingly. Those functions aren't yet called. --- src/or/circuitlist.c | 2 + src/or/config.c | 24 ++++--- src/or/connection.c | 34 +++++++--- src/or/connection_edge.c | 141 +++++++++++++++++++++++++++++++++++++++ src/or/connection_edge.h | 8 +++ src/or/dnsserv.c | 10 ++- src/or/or.h | 57 ++++++++++++++-- 7 files changed, 253 insertions(+), 23 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 93f5fd3493..9f688801f3 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -550,6 +550,8 @@ circuit_free(circuit_t *circ) crypto_free_pk_env(ocirc->intro_key); rend_data_free(ocirc->rend_data); + + tor_free(ocirc->dest_address); } else { or_circuit_t *ocirc = TO_OR_CIRCUIT(circ); /* Remember cell statistics for this circuit before deallocating. */ diff --git a/src/or/config.c b/src/or/config.c index 0774b28916..2ca9c66999 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -577,6 +577,7 @@ 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); @@ -4846,6 +4847,13 @@ 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) @@ -4955,8 +4963,8 @@ parse_client_port_config(smartlist_t *out, cfg->type = listener_type; cfg->port = port ? port : defaultport; tor_addr_copy(&cfg->addr, &addr); - cfg->sessiongroup = -1; - cfg->isolate = ISO_DEFAULT; + cfg->session_group = -1; + cfg->isolation_flags = ISO_DEFAULT; smartlist_add(out, cfg); } } @@ -4974,8 +4982,8 @@ parse_client_port_config(smartlist_t *out, cfg->type = listener_type; cfg->port = defaultport; tor_addr_from_str(&cfg->addr, defaultaddr); - cfg->sessiongroup = -1; - cfg->isolate = ISO_DEFAULT; + cfg->session_group = -1; + cfg->isolation_flags = ISO_DEFAULT; smartlist_add(out, cfg); } return 0; @@ -5094,8 +5102,8 @@ parse_client_port_config(smartlist_t *out, cfg->type = listener_type; cfg->port = port; tor_addr_copy(&cfg->addr, &addr); - cfg->sessiongroup = sessiongroup; - cfg->isolate = isolation; + cfg->session_group = sessiongroup; + cfg->isolation_flags = isolation; smartlist_add(out, cfg); } SMARTLIST_FOREACH(elts, char *, cp, tor_free(cp)); @@ -5169,7 +5177,7 @@ parse_client_ports(const or_options_t *options, int validate_only, if (!validate_only) { if (configured_client_ports) { SMARTLIST_FOREACH(configured_client_ports, - port_cfg_t *, p, tor_free(p)); + port_cfg_t *, p, port_cfg_free(p)); smartlist_free(configured_client_ports); } configured_client_ports = ports; @@ -5179,7 +5187,7 @@ parse_client_ports(const or_options_t *options, int validate_only, retval = 0; err: if (ports) { - SMARTLIST_FOREACH(ports, port_cfg_t *, p, tor_free(p)); + SMARTLIST_FOREACH(ports, port_cfg_t *, p, port_cfg_free(p)); smartlist_free(ports); } return retval; diff --git a/src/or/connection.c b/src/or/connection.c index db0d78975a..b627dcae16 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, @@ -859,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 = -2; if (get_n_open_sockets() >= get_options()->_ConnLimit-1) { warn_too_many_conns(); @@ -981,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); @@ -1199,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; @@ -1213,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); @@ -1222,7 +1238,9 @@ 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; + switch (TO_CONN(listener)->type) { case CONN_TYPE_AP_LISTENER: conn->state = AP_CONN_STATE_SOCKS_WAIT; break; @@ -1808,7 +1826,7 @@ retry_listener_ports(smartlist_t *old_conns, if (listensockaddr) { conn = connection_create_listener(listensockaddr, listensocklen, - port->type, address); + port->type, address, port); tor_free(listensockaddr); tor_free(address); } else { diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index f2ddfc76dc..6d247dcebd 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3266,3 +3266,144 @@ 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 ((iso & ISO_DESTPORT) && a->socks_request->port != b->socks_request->port) + return 0; + /* XXXX023 Not quite right: we care about addresses that resolve to the same + place */ + if ((iso & ISO_DESTADDR) && + strcasecmp(a->socks_request->address, b->socks_request->address)) + return 0; + /* XXXX023 Waititing for ticket #1666 */ + /* + if ((iso & ISO_SOCKSAUTH) && + strcasecmp(a->socks_request->auth, b->socks_request->auth)) + 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; + + 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 ((iso & ISO_DESTPORT) && conn->socks_request->port != circ->dest_port) + return 0; + /* XXXX023 Not quite right: we care about addresses that resolve to the same + place */ + if ((iso & ISO_DESTADDR) && + strcasecmp(conn->socks_request->address, circ->dest_address)) + return 0; + /* XXXX023 Waititing for ticket #1666 */ + /* + if ((iso & ISO_SOCKSAUTH) && + strcasecmp(a->socks_request->auth, b->socks_request->auth)) + 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; + + 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 (!circ->isolation_values_set) { + if (dry_run) + return -1; + circ->dest_port = conn->socks_request->port; + circ->dest_address = tor_strdup(conn->socks_request->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; + /* XXXX023 auth too, once #1666 is in. */ + + circ->isolation_values_set = 1; + return 0; + } else { + uint8_t mixed = 0; + if (conn->socks_request->port != circ->dest_port) + mixed |= ISO_DESTPORT; + /* XXXX023 Not quite right: we care about addresses that resolve to the + same place */ + if (strcasecmp(conn->socks_request->address, circ->dest_address)) + mixed |= ISO_DESTADDR; + /* XXXX023 auth too, once #1666 is in. */ + 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 (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; + } +} + diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 1cb1281dbd..59865f414b 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -103,5 +103,13 @@ 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); + #endif diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index 350b138726..b1316ecc6a 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,8 @@ 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; if (connection_add(TO_CONN(conn)) < 0) { log_warn(LD_APP, "Couldn't register dummy connection for DNS request"); @@ -312,7 +315,8 @@ dnsserv_configure_listener(connection_t *conn) listener_conn = TO_LISTENER_CONN(conn); listener_conn->dns_server_port = - tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, NULL); + tor_evdns_add_server_port(conn->s, 0, evdns_server_callback, + listener_conn); } /** Free the evdns server port for conn, which must be an diff --git a/src/or/or.h b/src/or/or.h index e284a14ce3..c5e5793720 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1055,6 +1055,19 @@ typedef struct listener_connection_t { * to the evdns_server_port it uses to listen to and answer connections. */ struct evdns_server_port *dns_server_port; + /** @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 @@ -1194,6 +1207,16 @@ 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; + /* Other fields to isolate on already exist. The ClientAddr is addr. The + ClientProtocol is a combination of type and socks_request-> + socks_version. SocksAuth will be added to socks_request by ticket + #1666. DestAddr and DestPort are 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. */ @@ -2436,6 +2459,32 @@ typedef struct origin_circuit_t { /* XXXX NM This can get re-used after 2**32 circuits. */ uint32_t global_identifier; + /** True if we have attached at least one stream to this circuit, thereby + * setting the isolation paramaters for this circuit. */ + unsigned int isolation_values_set : 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 attached to this circuit (isolation_values_set + * == 1), and all streams attached to 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. + * + * @{ + */ + 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; + /* XXXX023 do auth once #1666 is merged */ + /**@}*/ + } origin_circuit_t; /** An or_circuit_t holds information needed to implement a circuit at an @@ -2579,16 +2628,16 @@ typedef enum invalid_router_usage_t { /** Configuration for a single port that we're listening on. */ typedef struct port_cfg_t { - tor_addr_t addr; /**< The configured address to listen on. */ + 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 isolate; /**< Zero or more isolation flags */ - int sessiongroup; /**< A session group, or -1 if this port is not in a - * session group. */ + 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 */ From 461623e7f980e8d599829865caf3b3985117004a Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 6 Jul 2011 16:30:02 -0400 Subject: [PATCH 04/16] Const-ify a few functions --- src/or/circuituse.c | 47 ++++++++++++++++++++++++---------------- src/or/connection_edge.c | 2 +- src/or/connection_edge.h | 2 +- 3 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 460c41f75d..4983b2a8ca 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,12 +134,11 @@ 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; } @@ -151,8 +150,12 @@ circuit_is_acceptable(circuit_t *circ, edge_connection_t *conn, * purpose, 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, + uint8_t purpose) { + const circuit_t *a = TO_CIRCUIT(oa); + const circuit_t *b = TO_CIRCUIT(ob); + switch (purpose) { case CIRCUIT_PURPOSE_C_GENERAL: /* if it's used but less dirty it's best; @@ -166,8 +169,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 @@ -206,10 +208,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 +226,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 +244,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,purpose)) + best = origin_circ; } if (!best && intro_going_on_but_too_old) @@ -245,7 +253,7 @@ 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; } #if 0 @@ -1495,7 +1503,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/connection_edge.c b/src/or/connection_edge.c index 6d247dcebd..426c342852 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3166,7 +3166,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(); diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 59865f414b..ddcf8cc6aa 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -51,7 +51,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); From 1d3c8c1f74e9f80317a70c3b7d9372dee87dd373 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 6 Jul 2011 16:39:54 -0400 Subject: [PATCH 05/16] Add a new isolation type and field: "nym epoch" The "nym epoch" of a stream is defined as the number of times that NEWNYM had been called before the stream was opened. All streams are isolated by nym epoch. This feature should be redundant with existing signewnym stuff, but it provides a good belt-and-suspenders way for us to avoid ever letting any circuit type bypass signewnym. --- src/or/connection.c | 1 + src/or/connection_edge.c | 7 +++++++ src/or/dnsserv.c | 1 + src/or/main.c | 11 +++++++++++ src/or/main.h | 1 + src/or/or.h | 7 ++++++- 6 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/or/connection.c b/src/or/connection.c index b627dcae16..09b45e03c9 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1240,6 +1240,7 @@ connection_init_accepted_conn(connection_t *conn, case CONN_TYPE_AP: 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; diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 426c342852..ce555ed5e2 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3296,6 +3296,8 @@ connection_edge_streams_are_compatible(const edge_connection_t *a, 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; } @@ -3348,6 +3350,8 @@ connection_edge_compatible_with_circuit(const edge_connection_t *conn, 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; } @@ -3374,6 +3378,7 @@ connection_edge_update_circuit_isolation(const edge_connection_t *conn, 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; /* XXXX023 auth too, once #1666 is in. */ circ->isolation_values_set = 1; @@ -3394,6 +3399,8 @@ connection_edge_update_circuit_isolation(const edge_connection_t *conn, 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; diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index b1316ecc6a..8612b4850f 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -134,6 +134,7 @@ evdns_server_callback(struct evdns_server_request *req, void *data_) 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"); diff --git a/src/or/main.c b/src/or/main.c index 16d4f0e7aa..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; @@ -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 c5e5793720..9cf508c2de 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1212,6 +1212,8 @@ typedef struct edge_connection_t { 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; /* Other fields to isolate on already exist. The ClientAddr is addr. The ClientProtocol is a combination of type and socks_request-> socks_version. SocksAuth will be added to socks_request by ticket @@ -2482,6 +2484,7 @@ typedef struct origin_circuit_t { tor_addr_t client_addr; char *dest_address; int session_group; + unsigned nym_epoch; /* XXXX023 do auth once #1666 is merged */ /**@}*/ @@ -2621,10 +2624,12 @@ typedef enum invalid_router_usage_t { #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) /**@}*/ /** Default isolation level for ports. */ -#define ISO_DEFAULT (ISO_CLIENTADDR|ISO_SOCKSAUTH|ISO_SESSIONGRP) +#define ISO_DEFAULT (ISO_CLIENTADDR|ISO_SOCKSAUTH|ISO_SESSIONGRP|ISO_NYM_EPOCH) /** Configuration for a single port that we're listening on. */ typedef struct port_cfg_t { From 773bfaf91ebe1ef80f37d473714a11f962e753fb Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Wed, 6 Jul 2011 17:08:24 -0400 Subject: [PATCH 06/16] Implement stream isolation This is the meat of proposal 171: we change circuit_is_acceptable() to require that the connection is compatible with every connection that has been linked to the circuit; we update circuit_is_better to prefer attaching streams to circuits in the way that decreases the circuits' usefulness the least; and we update link_apconn_to_circ() to do the appropriate bookkeeping. --- changes/prop171 | 11 +++++++++++ src/common/util.c | 26 ++++++++++++++++++++++++++ src/common/util.h | 1 + src/or/circuituse.c | 40 +++++++++++++++++++++++++++++++++++++--- 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/changes/prop171 b/changes/prop171 index 057556edd9..91c463f94d 100644 --- a/changes/prop171 +++ b/changes/prop171 @@ -1,3 +1,14 @@ + 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 diff --git a/src/common/util.c b/src/common/util.c index b95ee3a612..bf0bbe0603 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 * ===== */ diff --git a/src/common/util.h b/src/common/util.h index 6496c42db8..de06c3c5fa 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 diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 4983b2a8ca..19a62344f3 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -143,18 +143,27 @@ circuit_is_acceptable(const origin_circuit_t *origin_circ, 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(const origin_circuit_t *oa, const origin_circuit_t *ob, - uint8_t purpose) + 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: @@ -188,6 +197,29 @@ circuit_is_better(const origin_circuit_t *oa, const origin_circuit_t *ob, 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; } @@ -244,7 +276,7 @@ circuit_get_best(const edge_connection_t *conn, /* 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(origin_circ,best,purpose)) + if (!best || circuit_is_better(origin_circ,best,conn)) best = origin_circ; } @@ -1476,6 +1508,8 @@ 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; } + + connection_edge_update_circuit_isolation(apconn, circ, 0); } /** Return true iff address is matched by one of the entries in From 20c0581a7935369fecb6c62b7cf5c7c244cdb533 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 7 Jul 2011 10:40:23 -0400 Subject: [PATCH 07/16] Launch sufficient circuits to satisfy pending isolated streams Our old "do we need to launch a circuit for stream S" logic was, more or less, that if we had a pending circuit that could handle S, we didn't need to launch a new one. But now that we have streams isolated from one another, we need something stronger here: It's possible that some pending C can handle either S1 or S2, but not both. This patch reuses the existing isolation logic for a simple solution: when we decide during circuit launching that some pending C would satisfy stream S1, we "hypothetically" mark C as though S1 had been connected to it. Now if S2 is incompatible with S1, it won't be something that can attach to C, and so we'll launch a new stream. When the circuit becomes OPEN for the first time (with no streams attached to it), we reset the circuit's isolation status. I'm not too sure about this part: I wanted some way to be sure that, if all streams that would have used a circuit die before the circuit is done, the circuit can still get used. But I worry that this approach could also lead to us launching too many circuits. Careful thought needed here. --- src/or/circuitlist.c | 18 ++++++++++++++++-- src/or/circuituse.c | 13 +++++++++++-- src/or/connection_edge.c | 35 +++++++++++++++++++++++++++++++++++ src/or/connection_edge.h | 1 + src/or/or.h | 24 +++++++++++++++++++----- 5 files changed, 82 insertions(+), 9 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 9f688801f3..6f17697b21 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -210,9 +210,23 @@ circuit_set_state(circuit_t *circ, uint8_t state) /* add to waiting-circuit list. */ smartlist_add(circuits_pending_or_conns, circ); } - if (state == CIRCUIT_STATE_OPEN) - tor_assert(!circ->n_conn_onionskin); + circ->state = state; + + if (state == CIRCUIT_STATE_OPEN) { + tor_assert(!circ->n_conn_onionskin); + if (CIRCUIT_IS_ORIGIN(circ)) { + origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); + if (origin_circ->isolation_values_set && + !origin_circ->isolation_any_streams_attached) { + /* If we have any isolation information set on this circuit, + * but we never attached any streams to it, then all of the + * isolation information was hypothetical. Clear it. + */ + circuit_clear_isolation(origin_circ); + } + } + } } /** Add circ to the global list of circuits. This is called only from diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 19a62344f3..93098e527d 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -1457,12 +1457,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; } @@ -1509,6 +1517,7 @@ link_apconn_to_circ(edge_connection_t *apconn, origin_circuit_t *circ, apconn->cpath_layer = circ->cpath->prev; } + circ->isolation_any_streams_attached = 1; connection_edge_update_circuit_isolation(apconn, circ, 0); } diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index ce555ed5e2..20f01b15f6 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3414,3 +3414,38 @@ connection_edge_update_circuit_isolation(const edge_connection_t *conn, } } +/** + * 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. We clear the flags once the circuits are open, + * in case the streams that made us launch the circuits have closed + * since we began launching the circuits.) + */ +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; +} + diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index ddcf8cc6aa..8bd308d5ba 100644 --- a/src/or/connection_edge.h +++ b/src/or/connection_edge.h @@ -110,6 +110,7 @@ int connection_edge_compatible_with_circuit(const edge_connection_t *conn, 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/or.h b/src/or/or.h index 9cf508c2de..97418f574a 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2461,9 +2461,19 @@ typedef struct origin_circuit_t { /* XXXX NM This can get re-used after 2**32 circuits. */ uint32_t global_identifier; - /** True if we have attached at least one stream to this circuit, thereby - * setting the isolation paramaters for this circuit. */ + /** 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. */ @@ -2471,11 +2481,15 @@ typedef struct origin_circuit_t { /** @name Isolation parameters * - * If any streams have been attached to this circuit (isolation_values_set - * == 1), and all streams attached to the circuit have had the same value - * for some field ((isolation_flags_mixed & ISO_FOO) == 0), then these + * 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; From aef30547dc4aa77fc79517a6dcad7712b59af371 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Thu, 7 Jul 2011 14:54:54 -0400 Subject: [PATCH 08/16] Add an option to limit the number of non-open client circuits. This is mainly meant as a way to keep clients from accidentally DOSing themselves by (e.g.) enabling IsolateDestAddr or IsolateDestPort on a port that they use for HTTP. --- src/or/circuituse.c | 35 +++++++++++++++++++++++++++++++++++ src/or/config.c | 10 ++++++++++ src/or/or.h | 5 +++++ 3 files changed, 50 insertions(+) diff --git a/src/or/circuituse.c b/src/or/circuituse.c index 93098e527d..dcb6bfa501 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -288,6 +288,27 @@ circuit_get_best(const edge_connection_t *conn, 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 /** Check whether, according to the policies in options, the * circuit circ makes sense. */ @@ -1347,6 +1368,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 */ diff --git a/src/or/config.c b/src/or/config.c index 2ca9c66999..14acf59340 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -315,6 +315,7 @@ 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), @@ -3215,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; diff --git a/src/or/or.h b/src/or/or.h index 97418f574a..09907c3a15 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3230,6 +3230,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. */ From 424063e3b2b882d72943bda41279bd29a711ec55 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 8 Jul 2011 15:15:59 -0400 Subject: [PATCH 09/16] Implement destaddr-based isolation The new candidate rule, which arma suggested and I like, is that the original address as received from the client connection or as rewritten by the controller is the address that counts. --- src/or/connection.c | 2 +- src/or/connection_edge.c | 45 +++++++++++++++++++++++++++++++--------- src/or/dnsserv.c | 1 + src/or/or.h | 4 +++- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/or/connection.c b/src/or/connection.c index 09b45e03c9..5e5abca7aa 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -467,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) { diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 20f01b15f6..cfa6a3deb9 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) { @@ -2512,6 +2515,7 @@ connection_ap_make_link(connection_t *partner, conn->socks_request->has_finished = 0; /* waiting for 'connected' */ strlcpy(conn->socks_request->address, address, sizeof(conn->socks_request->address)); + conn->original_dest_address = tor_strdup(address); conn->socks_request->port = port; conn->socks_request->command = SOCKS_COMMAND_CONNECT; conn->want_onehop = want_onehop; @@ -3274,12 +3278,23 @@ connection_edge_streams_are_compatible(const edge_connection_t *a, { 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_DESTPORT) && a->socks_request->port != b->socks_request->port) return 0; - /* XXXX023 Not quite right: we care about addresses that resolve to the same - place */ if ((iso & ISO_DESTADDR) && - strcasecmp(a->socks_request->address, b->socks_request->address)) + strcasecmp(a->original_dest_address, b->original_dest_address)) return 0; /* XXXX023 Waititing for ticket #1666 */ /* @@ -3328,12 +3343,17 @@ connection_edge_compatible_with_circuit(const edge_connection_t *conn, 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 ((iso & ISO_DESTPORT) && conn->socks_request->port != circ->dest_port) return 0; - /* XXXX023 Not quite right: we care about addresses that resolve to the same - place */ if ((iso & ISO_DESTADDR) && - strcasecmp(conn->socks_request->address, circ->dest_address)) + strcasecmp(conn->original_dest_address, circ->dest_address)) return 0; /* XXXX023 Waititing for ticket #1666 */ /* @@ -3369,11 +3389,18 @@ 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->socks_request->address); + 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); @@ -3387,9 +3414,7 @@ connection_edge_update_circuit_isolation(const edge_connection_t *conn, uint8_t mixed = 0; if (conn->socks_request->port != circ->dest_port) mixed |= ISO_DESTPORT; - /* XXXX023 Not quite right: we care about addresses that resolve to the - same place */ - if (strcasecmp(conn->socks_request->address, circ->dest_address)) + if (strcasecmp(conn->original_dest_address, circ->dest_address)) mixed |= ISO_DESTADDR; /* XXXX023 auth too, once #1666 is in. */ if ((TO_CONN(conn)->type != circ->client_proto_type || diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index 8612b4850f..c81d72f687 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -184,6 +184,7 @@ 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); if (connection_add(TO_CONN(conn))<0) { log_warn(LD_APP, "Couldn't register dummy connection for RESOLVE request"); diff --git a/src/or/or.h b/src/or/or.h index 09907c3a15..ace92ce1a7 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1214,10 +1214,12 @@ typedef struct edge_connection_t { 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 will be added to socks_request by ticket - #1666. DestAddr and DestPort are in socks_request->address. */ + #1666. 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 From 8314fa5e5c8d300323589ff97599f8f93b847b78 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 8 Jul 2011 15:54:30 -0400 Subject: [PATCH 10/16] Implement sensible isolation for tunneled directory conns One-hop dirconn streams all share a session group, and get the ISO_SESSIONGRP flag: they may share circuits with each other and nothing else. Anonymized dirconn streams get a new internal-use-only ISO_STREAM flag: they may not share circuits with anything, including each other. --- src/or/config.c | 6 +++--- src/or/connection.c | 2 +- src/or/connection_edge.c | 18 ++++++++++++++++-- src/or/connection_edge.h | 2 ++ src/or/directory.c | 8 +++++++- src/or/or.h | 9 +++++++++ 6 files changed, 38 insertions(+), 7 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index 14acf59340..86ccb92965 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -4973,7 +4973,7 @@ parse_client_port_config(smartlist_t *out, cfg->type = listener_type; cfg->port = port ? port : defaultport; tor_addr_copy(&cfg->addr, &addr); - cfg->session_group = -1; + cfg->session_group = SESSION_GROUP_UNSET; cfg->isolation_flags = ISO_DEFAULT; smartlist_add(out, cfg); } @@ -4992,7 +4992,7 @@ parse_client_port_config(smartlist_t *out, cfg->type = listener_type; cfg->port = defaultport; tor_addr_from_str(&cfg->addr, defaultaddr); - cfg->session_group = -1; + cfg->session_group = SESSION_GROUP_UNSET; cfg->isolation_flags = ISO_DEFAULT; smartlist_add(out, cfg); } @@ -5006,7 +5006,7 @@ parse_client_port_config(smartlist_t *out, for (; ports; ports = ports->next) { tor_addr_t addr; int port; - int sessiongroup = -1; + int sessiongroup = SESSION_GROUP_UNSET; unsigned isolation = ISO_DEFAULT; char *addrport; diff --git a/src/or/connection.c b/src/or/connection.c index 5e5abca7aa..0fae11e1d4 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -868,7 +868,7 @@ connection_create_listener(const struct sockaddr *listensockaddr, 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 = -2; + static int global_next_session_group = SESSION_GROUP_FIRST_AUTO; if (get_n_open_sockets() >= get_options()->_ConnLimit-1) { warn_too_many_conns(); diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index cfa6a3deb9..42f74b7ecc 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -2497,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; @@ -2515,7 +2517,6 @@ connection_ap_make_link(connection_t *partner, conn->socks_request->has_finished = 0; /* waiting for 'connected' */ strlcpy(conn->socks_request->address, address, sizeof(conn->socks_request->address)); - conn->original_dest_address = tor_strdup(address); conn->socks_request->port = port; conn->socks_request->command = SOCKS_COMMAND_CONNECT; conn->want_onehop = want_onehop; @@ -2528,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; @@ -3291,6 +3297,9 @@ connection_edge_streams_are_compatible(const edge_connection_t *a, 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) && @@ -3350,6 +3359,11 @@ connection_edge_compatible_with_circuit(const edge_connection_t *conn, 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) && diff --git a/src/or/connection_edge.h b/src/or/connection_edge.h index 8bd308d5ba..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, 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/or.h b/src/or/or.h index ace92ce1a7..26e9970209 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2642,11 +2642,20 @@ typedef enum invalid_router_usage_t { #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 +/** First automatically allocated session group number */ +#define SESSION_GROUP_FIRST_AUTO -3 + /** 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. */ From 172f8acbe7fcdf6b025ad87c81760ef45bbe1d53 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 8 Jul 2011 16:00:19 -0400 Subject: [PATCH 11/16] Stick controller-originated resolves in their own session group --- src/or/dnsserv.c | 4 ++++ src/or/or.h | 4 +++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/or/dnsserv.c b/src/or/dnsserv.c index c81d72f687..35279c4702 100644 --- a/src/or/dnsserv.c +++ b/src/or/dnsserv.c @@ -184,7 +184,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"); diff --git a/src/or/or.h b/src/or/or.h index 26e9970209..835f279d42 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2653,8 +2653,10 @@ typedef enum invalid_router_usage_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 -3 +#define SESSION_GROUP_FIRST_AUTO -4 /** Configuration for a single port that we're listening on. */ typedef struct port_cfg_t { From 891ccd3cd0690e83f1dc4dde7698c3bd9d7fe98d Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Fri, 8 Jul 2011 16:37:29 -0400 Subject: [PATCH 12/16] Manpage updates for proposal 171 (isolated streams) --- doc/tor.1.txt | 101 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 78 insertions(+), 23 deletions(-) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 9607632e1d..8241eeb3c3 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,50 @@ 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**.) + [NOT YET IMPLEMENTED.] + **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 +828,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 +878,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 From 94f85f216ae4b6196d2a3438bfaf328375ebaad6 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Jul 2011 02:36:11 -0400 Subject: [PATCH 13/16] Turn streq_opt into a generic strcmp_opt. --- src/common/util.c | 17 +++++++++++++++++ src/common/util.h | 1 + src/or/config.c | 7 +------ 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/common/util.c b/src/common/util.c index bf0bbe0603..15b6e7130e 100644 --- a/src/common/util.c +++ b/src/common/util.c @@ -521,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 de06c3c5fa..99355871f6 100644 --- a/src/common/util.h +++ b/src/common/util.h @@ -175,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/config.c b/src/or/config.c index 86ccb92965..cc1561bca6 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -3864,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. */ From 12dfb4f5d8cfb0f244b4a1ae3cc3af237a3034e7 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Jul 2011 02:36:59 -0400 Subject: [PATCH 14/16] Use socks username/password information in stream isolation --- doc/tor.1.txt | 1 - src/or/circuitlist.c | 2 ++ src/or/connection.c | 2 +- src/or/connection_edge.c | 23 +++++++++++++---------- src/or/or.h | 7 ++++--- 5 files changed, 20 insertions(+), 15 deletions(-) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 8241eeb3c3..821098b56e 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -700,7 +700,6 @@ The following options are useful only for clients (that is, if Don't share a circuits with streams for which different SOCKS authentication was provided. (On by default; you can disable it with **NoIsolateSOCKSAuth**.) - [NOT YET IMPLEMENTED.] **IsolateClientProtocol**;; Don't share circuits with streams using a different protocol. (SOCKS 4, SOCKS 5, TransPort connections, NATDPort connections, diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 6f17697b21..28a7181f26 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -566,6 +566,8 @@ circuit_free(circuit_t *circ) 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/connection.c b/src/or/connection.c index 0fae11e1d4..59a7b80deb 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1809,7 +1809,7 @@ retry_listener_ports(smartlist_t *old_conns, SMARTLIST_FOREACH_BEGIN(launch, const port_cfg_t *, port) { struct sockaddr *listensockaddr; socklen_t listensocklen = 0; - char *address; + char *address=NULL; connection_t *conn; if (port->is_unix_addr) { diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 42f74b7ecc..63779f25cd 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3305,12 +3305,10 @@ connection_edge_streams_are_compatible(const edge_connection_t *a, if ((iso & ISO_DESTADDR) && strcasecmp(a->original_dest_address, b->original_dest_address)) return 0; - /* XXXX023 Waititing for ticket #1666 */ - /* if ((iso & ISO_SOCKSAUTH) && - strcasecmp(a->socks_request->auth, b->socks_request->auth)) + (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)) @@ -3369,12 +3367,10 @@ connection_edge_compatible_with_circuit(const edge_connection_t *conn, if ((iso & ISO_DESTADDR) && strcasecmp(conn->original_dest_address, circ->dest_address)) return 0; - /* XXXX023 Waititing for ticket #1666 */ - /* if ((iso & ISO_SOCKSAUTH) && - strcasecmp(a->socks_request->auth, b->socks_request->auth)) + (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)) @@ -3420,7 +3416,10 @@ connection_edge_update_circuit_isolation(const edge_connection_t *conn, tor_addr_copy(&circ->client_addr, &TO_CONN(conn)->addr); circ->session_group = conn->session_group; circ->nym_epoch = conn->nym_epoch; - /* XXXX023 auth too, once #1666 is in. */ + 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; @@ -3430,7 +3429,9 @@ connection_edge_update_circuit_isolation(const edge_connection_t *conn, mixed |= ISO_DESTPORT; if (strcasecmp(conn->original_dest_address, circ->dest_address)) mixed |= ISO_DESTADDR; - /* XXXX023 auth too, once #1666 is in. */ + 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; @@ -3486,5 +3487,7 @@ circuit_clear_isolation(origin_circuit_t *circ) 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/or.h b/src/or/or.h index 835f279d42..47cee35e5b 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1218,8 +1218,8 @@ typedef struct edge_connection_t { 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 will be added to socks_request by ticket - #1666. DestAddr is in socks_request->address. */ + 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 @@ -2501,7 +2501,8 @@ typedef struct origin_circuit_t { char *dest_address; int session_group; unsigned nym_epoch; - /* XXXX023 do auth once #1666 is merged */ + char *socks_username; + char *socks_password; /**@}*/ } origin_circuit_t; From e8b9815711e7cd1ef0b83153aefcc0d05e817f4e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Jul 2011 13:51:43 -0400 Subject: [PATCH 15/16] Take a smarter approach to clearing isolation info Back when I added this logic in 20c0581a79, the rule was that whenever a circuit finished building, we cleared its isolation info. I did that so that we would still use the circuit even if all the streams that had previously led us to tentatively set its isolation info had closed. But there were problems with that approach: We could pretty easily get into a case where S1 had led us to launch C1 and S2 had led us to launch C2, but when C1 finished, we cleared its isolation and attached S2 first. Since C2 was still marked in a way that made S1 unattachable to it, we'd then launch another circuit needlessly. So instead, we try the following approach now: when a circuit is done building, we try to attach streams to it. If it remains unused after we try attaching streams, then we clear its isolation info, and try again to attach streams. Thanks to Sebastian for helping me figure this out. --- src/or/circuitlist.c | 18 ++---------------- src/or/circuituse.c | 16 ++++++++++++++++ src/or/connection_edge.c | 6 +++--- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c index 28a7181f26..0fefe9871e 100644 --- a/src/or/circuitlist.c +++ b/src/or/circuitlist.c @@ -210,23 +210,9 @@ circuit_set_state(circuit_t *circ, uint8_t state) /* add to waiting-circuit list. */ smartlist_add(circuits_pending_or_conns, circ); } - - circ->state = state; - - if (state == CIRCUIT_STATE_OPEN) { + if (state == CIRCUIT_STATE_OPEN) tor_assert(!circ->n_conn_onionskin); - if (CIRCUIT_IS_ORIGIN(circ)) { - origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ); - if (origin_circ->isolation_values_set && - !origin_circ->isolation_any_streams_attached) { - /* If we have any isolation information set on this circuit, - * but we never attached any streams to it, then all of the - * isolation information was hypothetical. Clear it. - */ - circuit_clear_isolation(origin_circ); - } - } - } + circ->state = state; } /** Add circ to the global list of circuits. This is called only from diff --git a/src/or/circuituse.c b/src/or/circuituse.c index dcb6bfa501..b4860440cb 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -998,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 @@ -1005,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: @@ -1016,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: @@ -1033,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. diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c index 63779f25cd..4bbb080124 100644 --- a/src/or/connection_edge.c +++ b/src/or/connection_edge.c @@ -3461,9 +3461,9 @@ connection_edge_update_circuit_isolation(const edge_connection_t *conn, * 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. We clear the flags once the circuits are open, - * in case the streams that made us launch the circuits have closed - * since we began launching the circuits.) + * 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) From 1017322b59c5c722b1db4e2f040db5cceb41dea9 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 19 Jul 2011 14:04:55 -0400 Subject: [PATCH 16/16] Fix a compile warning in config.c reported by sebastian --- src/or/config.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index cc1561bca6..efee014894 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -5057,8 +5057,8 @@ parse_client_port_config(smartlist_t *out, if (elt_sl_idx == 0) continue; /* Skip addr:port */ if (!strcasecmpstart(elt, "SessionGroup=")) { - int group = tor_parse_long(elt+strlen("SessionGroup="), - 10, 0, INT_MAX, &ok, NULL); + 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));