From df9b76460c38936b67ef42f5b261b39e2ec7144e Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Nov 2011 15:44:10 -0500 Subject: New 'DisableNetwork' option to prevent Tor from using the network Some controllers want this so they can mess with Tor's configuration for a while via the control port before actually letting Tor out of the house. We do this with a new DisableNetwork option, that prevents Tor from making any outbound connections or binding any non-control listeners. Additionally, it shuts down the same functionality as shuts down when we are hibernating, plus the code that launches directory downloads. To make sure I didn't miss anything, I added a clause straight to connection_connect, so that we won't even try to open an outbound socket when the network is disabled. In my testing, I made this an assert, but since I probably missed something, I've turned it into a BUG warning for testing. --- changes/disable_network | 9 +++++++ src/or/config.c | 10 +++++++- src/or/connection.c | 68 +++++++++++++++++++++++++++++++++++++++++++++---- src/or/connection.h | 3 +++ src/or/hibernate.c | 11 +------- src/or/main.c | 29 +++++++++++---------- src/or/or.h | 4 +++ src/or/router.c | 12 +++++++-- src/or/router.h | 2 ++ src/or/routerlist.c | 6 ++++- 10 files changed, 122 insertions(+), 32 deletions(-) create mode 100644 changes/disable_network diff --git a/changes/disable_network b/changes/disable_network new file mode 100644 index 000000000..e6e7259ea --- /dev/null +++ b/changes/disable_network @@ -0,0 +1,9 @@ + o Minor features: + + - New "DisableNetwork" option to prevent Tor from launching any + connections or accepting any connections except on a control + port. Some bundles and controllers want to use this so they can + configure Tor before letting Tor talk to the rest of the + network--for example, to prevent any connections from being made + to a non-bridge address. + diff --git a/src/or/config.c b/src/or/config.c index c0ce404f9..20ade03f0 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -231,6 +231,7 @@ static config_var_t _option_vars[] = { V(CountPrivateBandwidth, BOOL, "0"), V(DataDirectory, FILENAME, NULL), OBSOLETE("DebugLogFile"), + V(DisableNetwork, BOOL, "0"), V(DirAllowPrivateAddresses, BOOL, NULL), V(TestingAuthDirTimeToLearnReachability, INTERVAL, "30 minutes"), V(DirListenAddress, LINELIST, NULL), @@ -1093,13 +1094,19 @@ options_act_reversible(const or_options_t *old_options, char **msg) consider_hibernation(time(NULL)); /* Launch the listeners. (We do this before we setuid, so we can bind to - * ports under 1024.) We don't want to rebind if we're hibernating. */ + * ports under 1024.) We don't want to rebind if we're hibernating. If + * networking is disabled, this will close all but the control listeners, + * but disable those. */ if (!we_are_hibernating()) { if (retry_all_listeners(replaced_listeners, new_listeners) < 0) { *msg = tor_strdup("Failed to bind one of the listener ports."); goto rollback; } } + if (options->DisableNetwork) { + /* Aggressively close non-controller stuff, NOW */ + connection_mark_all_noncontrol_connections(); + } } #if defined(HAVE_NET_IF_H) && defined(HAVE_NET_PFVAR_H) @@ -4094,6 +4101,7 @@ options_transition_affects_descriptor(const or_options_t *old_options, old_options->ORPort != new_options->ORPort || old_options->DirPort != new_options->DirPort || old_options->ClientOnly != new_options->ClientOnly || + old_options->DisableNetwork != new_options->DisableNetwork || old_options->_PublishServerDescriptor != new_options->_PublishServerDescriptor || get_effective_bwrate(old_options) != get_effective_bwrate(new_options) || diff --git a/src/or/connection.c b/src/or/connection.c index a52bf4807..9c30d8ef7 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1318,6 +1318,24 @@ connection_connect(connection_t *conn, const char *address, else protocol_family = PF_INET; + if (get_options()->DisableNetwork) { + /* We should never even try to connect anyplace if DisableNetwork is set. + * Warn if we do, and refuse to make the connection. */ + static ratelim_t disablenet_violated = RATELIM_INIT(30*60); + char *m; +#ifdef MS_WINDOWS + *socket_error = WSAENETUNREACH; +#else + *socket_error = ENETUNREACH; +#endif + if ((m = rate_limit_log(&disablenet_violated, approx_time()))) { + log_warn(LD_BUG, "Tried to open a socket with DisableNetwork set.%s", m); + tor_free(m); + } + tor_fragile_assert(); + return -1; + } + s = tor_open_socket(protocol_family,SOCK_STREAM,IPPROTO_TCP); if (s < 0) { *socket_error = tor_socket_errno(-1); @@ -1968,7 +1986,7 @@ retry_all_listeners(smartlist_t *replaced_conns, smartlist_add(listeners, conn); } SMARTLIST_FOREACH_END(conn); - if (! options->ClientOnly) { + if (! options->ClientOnly && ! options->DisableNetwork) { if (retry_listeners(listeners, CONN_TYPE_OR_LISTENER, options->ORListenAddress, options->ORPort, "0.0.0.0", @@ -1981,10 +1999,13 @@ retry_all_listeners(smartlist_t *replaced_conns, retval = -1; } - if (retry_listener_ports(listeners, - get_configured_client_ports(), - new_conns) < 0) - retval = -1; + if (!options->DisableNetwork) { + if (retry_listener_ports(listeners, + get_configured_client_ports(), + new_conns) < 0) + retval = -1; + } + if (retry_listeners(listeners, CONN_TYPE_CONTROL_LISTENER, options->ControlListenAddress, @@ -2025,6 +2046,43 @@ retry_all_listeners(smartlist_t *replaced_conns, return retval; } +/** DOCDOC */ +void +connection_mark_all_noncontrol_listeners(void) +{ + SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { + if (conn->marked_for_close) + continue; + if (conn->type == CONN_TYPE_CONTROL_LISTENER) + continue; + if (connection_is_listener(conn)) + connection_mark_for_close(conn); + } SMARTLIST_FOREACH_END(conn); +} + +/** DOCDOC */ +void +connection_mark_all_noncontrol_connections(void) +{ + SMARTLIST_FOREACH_BEGIN(get_connection_array(), connection_t *, conn) { + if (conn->marked_for_close) + continue; + switch (conn->type) { + case CONN_TYPE_CPUWORKER: + case CONN_TYPE_CONTROL_LISTENER: + case CONN_TYPE_CONTROL: + break; + case CONN_TYPE_AP: + connection_mark_unattached_ap(TO_ENTRY_CONN(conn), + END_STREAM_REASON_HIBERNATING); + break; + default: + connection_mark_for_close(conn); + break; + } + } SMARTLIST_FOREACH_END(conn); +} + /** Return 1 if we should apply rate limiting to conn, and 0 * otherwise. * Right now this just checks if it's an internal IP address or an diff --git a/src/or/connection.h b/src/or/connection.h index 9f1148972..c4b8bf8ab 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -66,6 +66,9 @@ int get_proxy_addrport(tor_addr_t *addr, uint16_t *port, int *proxy_type, int retry_all_listeners(smartlist_t *replaced_conns, smartlist_t *new_conns); +void connection_mark_all_noncontrol_listeners(void); +void connection_mark_all_noncontrol_connections(void); + ssize_t connection_bucket_write_limit(connection_t *conn, time_t now); int global_write_bucket_low(connection_t *conn, size_t attempt, int priority); void connection_bucket_init(void); diff --git a/src/or/hibernate.c b/src/or/hibernate.c index 6fd2b4f19..ce64581d1 100644 --- a/src/or/hibernate.c +++ b/src/or/hibernate.c @@ -735,7 +735,6 @@ hibernate_soft_limit_reached(void) static void hibernate_begin(hibernate_state_t new_state, time_t now) { - connection_t *conn; const or_options_t *options = get_options(); if (new_state == HIBERNATE_STATE_EXITING && @@ -756,15 +755,7 @@ hibernate_begin(hibernate_state_t new_state, time_t now) } /* close listeners. leave control listener(s). */ - while ((conn = connection_get_by_type(CONN_TYPE_OR_LISTENER)) || - (conn = connection_get_by_type(CONN_TYPE_AP_LISTENER)) || - (conn = connection_get_by_type(CONN_TYPE_AP_TRANS_LISTENER)) || - (conn = connection_get_by_type(CONN_TYPE_AP_DNS_LISTENER)) || - (conn = connection_get_by_type(CONN_TYPE_AP_NATD_LISTENER)) || - (conn = connection_get_by_type(CONN_TYPE_DIR_LISTENER))) { - log_info(LD_NET,"Closing listener type %d", conn->type); - connection_mark_for_close(conn); - } + connection_mark_all_noncontrol_listeners(); /* XXX kill intro point circs */ /* XXX upload rendezvous service descriptors with no intro points */ diff --git a/src/or/main.c b/src/or/main.c index 10b80a4dd..abf82339b 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -934,7 +934,7 @@ directory_info_has_arrived(time_t now, int from_cache) update_extrainfo_downloads(now); } - if (server_mode(options) && !we_are_hibernating() && !from_cache && + if (server_mode(options) && !net_is_disabled() && !from_cache && (can_complete_circuit || !any_predicted_circuits(now))) consider_testing_reachability(1, 1); } @@ -1161,11 +1161,11 @@ run_scheduled_events(time_t now) if (router_rebuild_descriptor(1)<0) { log_info(LD_CONFIG, "Couldn't rebuild router descriptor"); } - if (advertised_server_mode()) + if (advertised_server_mode() & !options->DisableNetwork) router_upload_dir_desc_to_dirservers(0); } - if (time_to_try_getting_descriptors < now) { + if (!options->DisableNetwork && time_to_try_getting_descriptors < now) { update_all_descriptor_downloads(now); update_extrainfo_downloads(now); if (router_have_minimum_dir_info()) @@ -1219,7 +1219,7 @@ run_scheduled_events(time_t now) if (time_to_launch_reachability_tests < now && (authdir_mode_tests_reachability(options)) && - !we_are_hibernating()) { + !net_is_disabled()) { time_to_launch_reachability_tests = now + REACHABILITY_TEST_INTERVAL; /* try to determine reachability of the other Tor relays */ dirserv_test_reachability(now); @@ -1355,7 +1355,7 @@ run_scheduled_events(time_t now) /* 2b. Once per minute, regenerate and upload the descriptor if the old * one is inaccurate. */ - if (time_to_check_descriptor < now) { + if (time_to_check_descriptor < now && !options->DisableNetwork) { static int dirport_reachability_count = 0; time_to_check_descriptor = now + CHECK_DESCRIPTOR_INTERVAL; check_descriptor_bandwidth_changed(now); @@ -1430,7 +1430,7 @@ run_scheduled_events(time_t now) connection_expire_held_open(); /** 3d. And every 60 seconds, we relaunch listeners if any died. */ - if (!we_are_hibernating() && time_to_check_listeners < now) { + if (!net_is_disabled() && time_to_check_listeners < now) { retry_all_listeners(NULL, NULL); time_to_check_listeners = now+60; } @@ -1441,7 +1441,7 @@ run_scheduled_events(time_t now) * and we make a new circ if there are no clean circuits. */ have_dir_info = router_have_minimum_dir_info(); - if (have_dir_info && !we_are_hibernating()) + if (have_dir_info && !net_is_disabled()) circuit_build_needed_circs(now); /* every 10 seconds, but not at the same second as other such events */ @@ -1472,7 +1472,7 @@ run_scheduled_events(time_t now) circuit_close_all_marked(); /** 7. And upload service descriptors if necessary. */ - if (can_complete_circuit && !we_are_hibernating()) { + if (can_complete_circuit && !net_is_disabled()) { rend_consider_services_upload(now); rend_consider_descriptor_republication(); } @@ -1489,7 +1489,8 @@ run_scheduled_events(time_t now) /** 9. and if we're a server, check whether our DNS is telling stories to * us. */ - if (public_server_mode(options) && time_to_check_for_correct_dns < now) { + if (!net_is_disabled() && + public_server_mode(options) && time_to_check_for_correct_dns < now) { if (!time_to_check_for_correct_dns) { time_to_check_for_correct_dns = now + 60 + crypto_rand_int(120); } else { @@ -1508,7 +1509,8 @@ run_scheduled_events(time_t now) } /** 11. check the port forwarding app */ - if (time_to_check_port_forwarding < now && + if (!net_is_disabled() && + time_to_check_port_forwarding < now && options->PortForwarding && is_server) { #define PORT_FORWARDING_CHECK_INTERVAL 5 @@ -1520,7 +1522,7 @@ run_scheduled_events(time_t now) } /** 11b. check pending unconfigured managed proxies */ - if (pt_proxies_configuration_pending()) + if (!net_is_disabled() && pt_proxies_configuration_pending()) pt_configure_remaining_proxies(); /** 11c. validate pluggable transports configuration if we need to */ @@ -1592,7 +1594,7 @@ second_elapsed_callback(periodic_timer_t *timer, void *arg) control_event_stream_bandwidth_used(); if (server_mode(options) && - !we_are_hibernating() && + !net_is_disabled() && seconds_elapsed > 0 && can_complete_circuit && stats_n_seconds_working / TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT != @@ -1793,7 +1795,8 @@ do_hup(void) /* retry appropriate downloads */ router_reset_status_download_failures(); router_reset_descriptor_download_failures(); - update_networkstatus_downloads(time(NULL)); + if (!options->DisableNetwork) + update_networkstatus_downloads(time(NULL)); /* We'll retry routerstatus downloads in about 10 seconds; no need to * force a retry there. */ diff --git a/src/or/or.h b/src/or/or.h index 546fe17bf..7fd0597cd 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3439,6 +3439,10 @@ typedef struct { * issue. */ int UserspaceIOCPBuffers; + /** If 1, we accept and launch no external network connections, except on + * control ports. */ + int DisableNetwork; + } or_options_t; /** Persistent state for an onion router, as saved to disk. */ diff --git a/src/or/router.c b/src/or/router.c index b6b96a5ff..d0292aa66 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -780,7 +780,7 @@ check_whether_dirport_reachable(void) const or_options_t *options = get_options(); return !options->DirPort || options->AssumeReachable || - we_are_hibernating() || + net_is_disabled() || can_reach_dir_port; } @@ -806,7 +806,7 @@ decide_to_advertise_dirport(const or_options_t *options, uint16_t dir_port) return 0; if (authdir_mode(options)) /* always publish */ return dir_port; - if (we_are_hibernating()) + if (net_is_disabled()) return 0; if (!check_whether_dirport_reachable()) return 0; @@ -974,6 +974,14 @@ router_perform_bandwidth_test(int num_circs, time_t now) } } +/** Return true iff our network is in some sense disabled: either we're + * hibernating, entering hibernation, or */ +int +net_is_disabled(void) +{ + return get_options()->DisableNetwork || we_are_hibernating(); +} + /** Return true iff we believe ourselves to be an authoritative * directory server. */ diff --git a/src/or/router.h b/src/or/router.h index f9d156cb0..8cc529f86 100644 --- a/src/or/router.h +++ b/src/or/router.h @@ -39,6 +39,8 @@ void router_orport_found_reachable(void); void router_dirport_found_reachable(void); void router_perform_bandwidth_test(int num_circs, time_t now); +int net_is_disabled(void); + int authdir_mode(const or_options_t *options); int authdir_mode_v1(const or_options_t *options); int authdir_mode_v2(const or_options_t *options); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index d97b978f4..689df99c5 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -3244,7 +3244,7 @@ router_set_status(const char *digest, int up) log_debug(LD_DIR,"Marking router %s as %s.", node_describe(node), up ? "up" : "down"); #endif - if (!up && node_is_me(node) && !we_are_hibernating()) + if (!up && node_is_me(node) && !net_is_disabled()) log_warn(LD_NET, "We just marked ourself as down. Are your external " "addresses reachable?"); node->is_running = up; @@ -4009,6 +4009,8 @@ signed_desc_digest_is_recognized(signed_descriptor_t *desc) void update_all_descriptor_downloads(time_t now) { + if (get_options()->DisableNetwork) + return; update_router_descriptor_downloads(now); update_microdesc_downloads(now); launch_dummy_descriptor_download_as_needed(now, get_options()); @@ -4021,6 +4023,8 @@ routerlist_retry_directory_downloads(time_t now) { router_reset_status_download_failures(); router_reset_descriptor_download_failures(); + if (get_options()->DisableNetwork) + return; update_networkstatus_downloads(now); update_all_descriptor_downloads(now); } -- cgit v1.2.3 From f4d8ed4b28bd8f7ec232c02d0c9987c72ade89b5 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Nov 2011 16:01:47 -0500 Subject: Manpage for DisableNetwork --- doc/tor.1.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 7f9bfbbf9..5e2a1e4e6 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -125,6 +125,12 @@ Other options can be specified either on the command-line (--option You probably don't need to adjust this. It has no effect on Windows since that platform lacks getrlimit(). (Default: 1000) +**DisableNetwork** **0**|**1**:: + When this option is set, we don't listen for or accept any connections + other than controller connections, and we don't make any outbound + connections. Controllers sometimes use this option to avoid using + the network until Tor is fully configured. (Default: 0) + **ConstrainedSockets** **0**|**1**:: If set, Tor will tell the kernel to attempt to shrink the buffers for all sockets to the size specified in **ConstrainedSockSize**. This is useful for -- cgit v1.2.3 From 116dd4ae4fdf0a4699cafdae39b3cdb88f3512f6 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Mon, 28 Nov 2011 16:07:13 -0500 Subject: log a notice when disablenetwork is set --- src/or/config.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/or/config.c b/src/or/config.c index 20ade03f0..b08ed6399 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -1105,6 +1105,9 @@ options_act_reversible(const or_options_t *old_options, char **msg) } if (options->DisableNetwork) { /* Aggressively close non-controller stuff, NOW */ + log_notice(LD_NET, "DisableNetwork is set. Tor will not make or accept " + "non-control network connections. Shutting down all existing " + "connections."); connection_mark_all_noncontrol_connections(); } } -- cgit v1.2.3 From 8c5a2c5b804a99155a7822f15fef3feb0e536eaf Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2011 17:46:54 -0500 Subject: Make sure we never launch an evdns resolve when DisableNetwork is 1 --- src/or/dns.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/or/dns.c b/src/or/dns.c index 8ed953690..beb110acb 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -1395,6 +1395,10 @@ launch_resolve(edge_connection_t *exitconn) int r; int options = get_options()->ServerDNSSearchDomains ? 0 : DNS_QUERY_NO_SEARCH; + + if (get_options()->DisableNetwork) + return -1; + /* What? Nameservers not configured? Sounds like a bug. */ if (!nameservers_configured) { log_warn(LD_EXIT, "(Harmless.) Nameservers not configured, but resolve " @@ -1601,6 +1605,9 @@ launch_test_addresses(int fd, short event, void *args) (void)event; (void)args; + if (options->DisableNetwork) + return; + log_info(LD_EXIT, "Launching checks to see whether our nameservers like to " "hijack *everything*."); /* This situation is worse than the failure-hijacking situation. When this -- cgit v1.2.3 From 9e8f3ee8e4d5ad5ce98aeb37b2b8c724a1936355 Mon Sep 17 00:00:00 2001 From: Nick Mathewson Date: Tue, 29 Nov 2011 17:52:16 -0500 Subject: Fix some DOCDOCs --- src/or/connection.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/or/connection.c b/src/or/connection.c index 9c30d8ef7..b87f92217 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -2046,7 +2046,7 @@ retry_all_listeners(smartlist_t *replaced_conns, return retval; } -/** DOCDOC */ +/** Mark every listener of type other than CONTROL_LISTENER to be closed. */ void connection_mark_all_noncontrol_listeners(void) { @@ -2060,7 +2060,7 @@ connection_mark_all_noncontrol_listeners(void) } SMARTLIST_FOREACH_END(conn); } -/** DOCDOC */ +/** Mark every external conection not used for controllers for close. */ void connection_mark_all_noncontrol_connections(void) { -- cgit v1.2.3