diff --git a/src/core/mainloop/connection.c b/src/core/mainloop/connection.c index bb4130640b..22fa3fb795 100644 --- a/src/core/mainloop/connection.c +++ b/src/core/mainloop/connection.c @@ -137,7 +137,11 @@ static connection_t *connection_listener_new( const struct sockaddr *listensockaddr, socklen_t listensocklen, int type, const char *address, - const port_cfg_t *portcfg); + const port_cfg_t *portcfg, + int *addr_in_use); +static connection_t *connection_listener_new_for_port( + const port_cfg_t *port, + int *defer, int *addr_in_use); static void connection_init(time_t now, connection_t *conn, int type, int socket_family); static int connection_handle_listener_read(connection_t *conn, int new_type); @@ -1188,7 +1192,8 @@ static connection_t * connection_listener_new(const struct sockaddr *listensockaddr, socklen_t socklen, int type, const char *address, - const port_cfg_t *port_cfg) + const port_cfg_t *port_cfg, + int *addr_in_use) { listener_connection_t *lis_conn; connection_t *conn = NULL; @@ -1204,6 +1209,8 @@ connection_listener_new(const struct sockaddr *listensockaddr, tor_addr_t addr; int exhaustion = 0; + if (addr_in_use) *addr_in_use = 0; + if (listensockaddr->sa_family == AF_INET || listensockaddr->sa_family == AF_INET6) { int is_stream = (type != CONN_TYPE_AP_DNS_LISTENER); @@ -1282,8 +1289,10 @@ connection_listener_new(const struct sockaddr *listensockaddr, if (bind(s,listensockaddr,socklen) < 0) { const char *helpfulhint = ""; int e = tor_socket_errno(s); - if (ERRNO_IS_EADDRINUSE(e)) + if (ERRNO_IS_EADDRINUSE(e)) { helpfulhint = ". Is Tor already running?"; + if (addr_in_use) *addr_in_use = 1; + } log_warn(LD_NET, "Could not bind to %s:%u: %s%s", address, usePort, tor_socket_strerror(e), helpfulhint); goto err; @@ -1487,8 +1496,15 @@ connection_listener_new(const struct sockaddr *listensockaddr, return NULL; } +/** + * Create a new listener connection for a given port. In case we + * for a reason that is not an error condition, set defer + * to true. If we cannot bind listening socket because address is already + * in use, set addr_in_use to true. + */ static connection_t * -connection_listener_new_for_port(const port_cfg_t *port, int *defer) +connection_listener_new_for_port(const port_cfg_t *port, + int *defer, int *addr_in_use) { connection_t *conn; struct sockaddr *listensockaddr; @@ -1531,7 +1547,8 @@ connection_listener_new_for_port(const port_cfg_t *port, int *defer) if (listensockaddr) { conn = connection_listener_new(listensockaddr, listensocklen, - port->type, address, port); + port->type, address, port, + addr_in_use); tor_free(listensockaddr); tor_free(address); } else { @@ -2651,16 +2668,26 @@ connection_read_proxy_handshake(connection_t *conn) return ret; } +struct replacement_s +{ + connection_t *old_conn; + port_cfg_t *new_port; +}; + /** 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. + * launch. If we may need to perform socket rebind when creating new + * listener that replaces old one, create a replacement_s struct + * for affected pair and add it to replacements. For more + * information, see ticket #17873. * * If control_listeners_only is true, then we only open control - * listeners, and we do not remove any noncontrol listeners from old_conns. + * listeners, and we do not remove any noncontrol listeners from + * old_conns. * * Return 0 on success, -1 on failure. **/ @@ -2668,8 +2695,10 @@ static int retry_listener_ports(smartlist_t *old_conns, const smartlist_t *ports, smartlist_t *new_conns, + smartlist_t *replacements, int control_listeners_only) { + smartlist_t *launch = smartlist_new(); int r = 0; @@ -2705,16 +2734,31 @@ retry_listener_ports(smartlist_t *old_conns, break; } } else { - int port_matches; - if (wanted->port == CFG_AUTO_PORT) { - port_matches = 1; - } else { - port_matches = (wanted->port == conn->port); - } + /* Numeric values of old and new port match exactly. */ + const int port_matches_exact = (wanted->port == conn->port); + /* Ports match semantically - either their specific values + match exactly, or new port is 'auto'. + */ + const int port_matches = (wanted->port == CFG_AUTO_PORT || + port_matches_exact); + if (port_matches && tor_addr_eq(&wanted->addr, &conn->addr)) { found_port = wanted; break; } + const int may_need_rebind = + port_matches_exact && bool_neq(tor_addr_is_null(&wanted->addr), + tor_addr_is_null(&conn->addr)); + if (replacements && may_need_rebind) { + struct replacement_s *replacement = + tor_malloc(sizeof(struct replacement_s)); + + replacement->old_conn = conn; + replacement->new_port = (port_cfg_t *)wanted; + smartlist_add(replacements, replacement); + + SMARTLIST_DEL_CURRENT(launch, wanted); + } } } SMARTLIST_FOREACH_END(wanted); @@ -2731,7 +2775,7 @@ retry_listener_ports(smartlist_t *old_conns, /* Now open all the listeners that are configured but not opened. */ SMARTLIST_FOREACH_BEGIN(launch, const port_cfg_t *, port) { int skip = 0; - connection_t *conn = connection_listener_new_for_port(port, &skip); + connection_t *conn = connection_listener_new_for_port(port, &skip, NULL); if (conn && new_conns) smartlist_add(new_conns, conn); @@ -2759,6 +2803,7 @@ retry_all_listeners(smartlist_t *replaced_conns, smartlist_t *new_conns, int close_all_noncontrol) { smartlist_t *listeners = smartlist_new(); + smartlist_t *replacements = smartlist_new(); const or_options_t *options = get_options(); int retval = 0; const uint16_t old_or_port = router_get_advertised_or_port(options); @@ -2774,9 +2819,42 @@ retry_all_listeners(smartlist_t *replaced_conns, if (retry_listener_ports(listeners, get_configured_ports(), new_conns, + replacements, close_all_noncontrol) < 0) retval = -1; + SMARTLIST_FOREACH_BEGIN(replacements, struct replacement_s *, r) { + int addr_in_use = 0; + int skip = 0; + + tor_assert(r->new_port); + tor_assert(r->old_conn); + + connection_t *new_conn = + connection_listener_new_for_port(r->new_port, &skip, &addr_in_use); + connection_t *old_conn = r->old_conn; + + + if (skip) + continue; + + // XXX: replaced_conns + connection_close_immediate(r->old_conn); + connection_mark_for_close(r->old_conn); + + if (addr_in_use) { + new_conn = connection_listener_new_for_port(r->new_port, + &skip, &addr_in_use); + } + + tor_assert(new_conn); + + smartlist_add(new_conns, new_conn); + + tor_free(r); + SMARTLIST_DEL_CURRENT(replacements, r); + } SMARTLIST_FOREACH_END(r); + /* Any members that were still in 'listeners' don't correspond to * any configured port. Kill 'em. */ SMARTLIST_FOREACH_BEGIN(listeners, connection_t *, conn) {