#include "core/or/or.h"
#include "app/config/config.h"
#include "core/mainloop/connection.h"
#include "core/mainloop/mainloop.h"
#include "core/mainloop/netstatus.h"
#include "core/or/circuitbuild.h"
#include "core/or/circuitlist.h"
#include "core/or/circuituse.h"
#include "core/or/crypt_path_st.h"
#include "core/or/extendinfo.h"
#include "core/or/extend_info_st.h"
#include "core/or/origin_circuit_st.h"
#include "core/or/relay.h"
#include "feature/control/control_events.h"
#include "feature/dirauth/authmode.h"
#include "feature/dirclient/dirclient.h"
#include "feature/dircommon/directory.h"
#include "feature/nodelist/authority_cert_st.h"
#include "feature/nodelist/routerinfo.h"
#include "feature/nodelist/routerinfo_st.h"
#include "feature/nodelist/routerlist.h"
#include "feature/nodelist/routerset.h"
#include "feature/nodelist/torcert.h"
#include "feature/relay/relay_periodic.h"
#include "feature/relay/router.h"
#include "feature/relay/selftest.h"
static bool have_orport_for_family(int family);
static void inform_testing_reachability(const tor_addr_t *addr,
uint16_t port,
bool is_dirport);
static bool can_reach_or_port_ipv4 = false;
static bool can_reach_or_port_ipv6 = false;
static bool can_reach_dir_port = false;
static bool have_informed_testing_or_port_ipv4 = false;
static bool have_informed_testing_or_port_ipv6 = false;
static bool have_informed_testing_dir_port = false;
void
router_reset_reachability(void)
{
can_reach_or_port_ipv4 = can_reach_or_port_ipv6 = can_reach_dir_port = false;
have_informed_testing_or_port_ipv4 =
have_informed_testing_or_port_ipv6 =
have_informed_testing_dir_port = false;
}
static int
router_reachability_checks_disabled(const or_options_t *options)
{
return options->AssumeReachable ||
net_is_disabled();
}
int
router_orport_seems_reachable(const or_options_t *options,
int family)
{
tor_assert_nonfatal(family == AF_INET || family == AF_INET6 || family == 0);
int reach_checks_disabled = router_reachability_checks_disabled(options);
if (reach_checks_disabled) {
return true;
}
const bool ipv6_assume_reachable = (options->AssumeReachableIPv6 == 1);
const bool checking_ipv4 = (family == AF_INET || family == 0);
const bool checking_ipv6 = (family == AF_INET6 || family == 0);
if (checking_ipv4) {
if (have_orport_for_family(AF_INET) && !can_reach_or_port_ipv4) {
return false;
}
}
if (checking_ipv6 && !ipv6_assume_reachable) {
if (have_orport_for_family(AF_INET6) && !can_reach_or_port_ipv6) {
return false;
}
}
return true;
}
int
router_dirport_seems_reachable(const or_options_t *options)
{
int reach_checks_disabled = router_reachability_checks_disabled(options) ||
authdir_mode(options) ||
!options->DirPort_set;
return reach_checks_disabled ||
can_reach_dir_port;
}
static int
router_should_check_reachability(int test_or, int test_dir)
{
const routerinfo_t *me = router_get_my_routerinfo();
const or_options_t *options = get_options();
if (!me)
return 0;
if (routerset_contains_router(options->ExcludeNodes, me, -1) &&
options->StrictNodes) {
if (test_or || test_dir) {
#define SELF_EXCLUDED_WARN_INTERVAL 3600
static ratelim_t warning_limit=RATELIM_INIT(SELF_EXCLUDED_WARN_INTERVAL);
log_fn_ratelim(&warning_limit, LOG_WARN, LD_CIRC,
"Can't perform self-tests for this relay: we have "
"listed ourself in ExcludeNodes, and StrictNodes is set. "
"We cannot learn whether we are usable, and will not "
"be able to advertise ourself.");
}
return 0;
}
return 1;
}
static bool
have_orport_for_family(int family)
{
const routerinfo_t *me = router_get_my_routerinfo();
if (!me)
return false;
tor_addr_port_t ap;
if (router_get_orport(me, &ap, family) < 0) {
return false;
}
return true;
}
static extend_info_t *
extend_info_from_router(const routerinfo_t *r, int family)
{
crypto_pk_t *rsa_pubkey;
extend_info_t *info;
tor_addr_port_t ap;
if (BUG(!r)) {
return NULL;
}
tor_assert_nonfatal(router_or_conn_should_skip_reachable_address_check(
get_options(), 0));
const ed25519_public_key_t *ed_id_key;
if (r->cache_info.signing_key_cert)
ed_id_key = &r->cache_info.signing_key_cert->signing_key;
else
ed_id_key = NULL;
if (router_get_orport(r, &ap, family) < 0) {
return NULL;
}
rsa_pubkey = router_get_rsa_onion_pkey(r->onion_pkey, r->onion_pkey_len);
info = extend_info_new(r->nickname, r->cache_info.identity_digest,
ed_id_key,
rsa_pubkey, r->onion_curve25519_pkey,
&ap.addr, ap.port);
crypto_pk_free(rsa_pubkey);
return info;
}
static void
router_do_orport_reachability_checks(const routerinfo_t *me,
int family,
int orport_reachable)
{
extend_info_t *ei = extend_info_from_router(me, family);
int ipv6_flags = (family == AF_INET6 ? CIRCLAUNCH_IS_IPV6_SELFTEST : 0);
if (ei) {
const char *family_name = fmt_af_family(family);
const tor_addr_port_t *ap = extend_info_get_orport(ei, family);
log_info(LD_CIRC, "Testing %s of my %s ORPort: %s.",
!orport_reachable ? "reachability" : "bandwidth",
family_name, fmt_addrport_ap(ap));
if (!orport_reachable) {
inform_testing_reachability(&ap->addr, ap->port, false);
}
circuit_launch_by_extend_info(CIRCUIT_PURPOSE_TESTING, ei,
CIRCLAUNCH_NEED_CAPACITY|
CIRCLAUNCH_IS_INTERNAL|
ipv6_flags);
extend_info_free(ei);
}
}
static void
router_do_dirport_reachability_checks(const routerinfo_t *me)
{
tor_addr_port_t my_dirport;
tor_addr_copy(&my_dirport.addr, &me->ipv4_addr);
my_dirport.port = me->ipv4_dirport;
if (!connection_get_by_type_addr_port_purpose(
CONN_TYPE_DIR,
&my_dirport.addr, my_dirport.port,
DIR_PURPOSE_FETCH_SERVERDESC)) {
directory_request_t *req =
directory_request_new(DIR_PURPOSE_FETCH_SERVERDESC);
directory_request_set_dir_addr_port(req, &my_dirport);
directory_request_set_directory_id_digest(req,
me->cache_info.identity_digest);
directory_request_set_indirection(req, DIRIND_ANON_DIRPORT);
directory_request_set_resource(req, "authority.z");
directory_initiate_request(req);
directory_request_free(req);
inform_testing_reachability(&my_dirport.addr, my_dirport.port, true);
}
}
void
router_do_reachability_checks(int test_or, int test_dir)
{
const routerinfo_t *me = router_get_my_routerinfo();
const or_options_t *options = get_options();
int orport_reachable_v4 =
router_orport_seems_reachable(options, AF_INET);
int orport_reachable_v6 =
router_orport_seems_reachable(options, AF_INET6);
if (router_should_check_reachability(test_or, test_dir)) {
bool need_testing = !circuit_enough_testing_circs();
if (test_or && (!orport_reachable_v4 || need_testing)) {
router_do_orport_reachability_checks(me, AF_INET, orport_reachable_v4);
}
if (test_or && (!orport_reachable_v6 || need_testing)) {
router_do_orport_reachability_checks(me, AF_INET6, orport_reachable_v6);
}
if (test_dir && !router_dirport_seems_reachable(options)) {
router_do_dirport_reachability_checks(me);
}
}
}
static void
inform_testing_reachability(const tor_addr_t *addr,
uint16_t port,
bool is_dirport)
{
if (!router_get_my_routerinfo())
return;
bool *have_informed_ptr;
if (is_dirport) {
have_informed_ptr = &have_informed_testing_dir_port;
} else if (tor_addr_family(addr) == AF_INET) {
have_informed_ptr = &have_informed_testing_or_port_ipv4;
} else {
have_informed_ptr = &have_informed_testing_or_port_ipv6;
}
if (*have_informed_ptr) {
return;
}
char addr_buf[TOR_ADDRPORT_BUF_LEN];
strlcpy(addr_buf, fmt_addrport(addr, port), sizeof(addr_buf));
const char *control_addr_type = is_dirport ? "DIRADDRESS" : "ORADDRESS";
const char *port_type = is_dirport ? "DirPort" : "ORPort";
const char *afname = fmt_af_family(tor_addr_family(addr));
control_event_server_status(LOG_NOTICE,
"CHECKING_REACHABILITY %s=%s",
control_addr_type, addr_buf);
log_notice(LD_OR, "Now checking whether %s %s %s is reachable... "
"(this may take up to %d minutes -- look for log "
"messages indicating success)",
afname, port_type, addr_buf,
TIMEOUT_UNTIL_UNREACHABILITY_COMPLAINT/60);
*have_informed_ptr = true;
}
static bool
ready_to_publish(const or_options_t *options)
{
return options->PublishServerDescriptor_ != NO_DIRINFO &&
router_dirport_seems_reachable(options) &&
router_all_orports_seem_reachable(options);
}
void
router_orport_found_reachable(int family)
{
const routerinfo_t *me = router_get_my_routerinfo();
const or_options_t *options = get_options();
const char *reachable_reason = "ORPort found reachable";
bool *can_reach_ptr;
if (family == AF_INET) {
can_reach_ptr = &can_reach_or_port_ipv4;
} else if (family == AF_INET6) {
can_reach_ptr = &can_reach_or_port_ipv6;
} else {
tor_assert_nonfatal_unreached();
return;
}
if (!*can_reach_ptr && me) {
tor_addr_port_t ap;
if (router_get_orport(me, &ap, family) < 0) {
return;
}
char *address = tor_strdup(fmt_addrport_ap(&ap));
*can_reach_ptr = true;
log_notice(LD_OR,"Self-testing indicates your ORPort %s is reachable from "
"the outside. Excellent.%s",
address,
ready_to_publish(options) ?
" Publishing server descriptor." : "");
if (family == AF_INET6) {
mark_my_descriptor_if_omit_ipv6_changes(reachable_reason, false);
} else {
mark_my_descriptor_dirty(reachable_reason);
}
if (options->TestingTorNetwork == 1) {
reschedule_descriptor_update_check();
}
control_event_server_status(LOG_NOTICE,
"REACHABILITY_SUCCEEDED ORADDRESS=%s",
address);
tor_free(address);
}
}
void
router_dirport_found_reachable(void)
{
const routerinfo_t *me = router_get_my_routerinfo();
const or_options_t *options = get_options();
if (!can_reach_dir_port && me) {
char *address = tor_addr_to_str_dup(&me->ipv4_addr);
if (!address)
return;
can_reach_dir_port = true;
log_notice(LD_DIRSERV,"Self-testing indicates your DirPort is reachable "
"from the outside. Excellent.%s",
ready_to_publish(options) ?
" Publishing server descriptor." : "");
if (router_should_advertise_dirport(options, me->ipv4_dirport)) {
mark_my_descriptor_dirty("DirPort found reachable");
if (options->TestingTorNetwork == 1) {
reschedule_descriptor_update_check();
}
}
control_event_server_status(LOG_NOTICE,
"REACHABILITY_SUCCEEDED DIRADDRESS=%s:%d",
address, me->ipv4_dirport);
tor_free(address);
}
}
void
router_perform_bandwidth_test(int num_circs, time_t now)
{
int num_cells = (int)(get_options()->BandwidthRate * 10 /
CELL_MAX_NETWORK_SIZE);
int max_cells = num_cells < CIRCWINDOW_START ?
num_cells : CIRCWINDOW_START;
int cells_per_circuit = max_cells / num_circs;
origin_circuit_t *circ = NULL;
log_notice(LD_OR,"Performing bandwidth self-test...done.");
while ((circ = circuit_get_next_by_pk_and_purpose(circ, NULL,
CIRCUIT_PURPOSE_TESTING))) {
int i = cells_per_circuit;
if (circ->base_.state != CIRCUIT_STATE_OPEN)
continue;
circ->base_.timestamp_dirty = now;
while (i-- > 0) {
if (relay_send_command_from_edge(0, TO_CIRCUIT(circ),
RELAY_COMMAND_DROP,
NULL, 0, circ->cpath->prev)<0) {
return;
}
}
}
}