diff --git a/changes/feature18329 b/changes/feature18329 new file mode 100644 index 0000000000..1dabf50244 --- /dev/null +++ b/changes/feature18329 @@ -0,0 +1,9 @@ + o Minor features (bridge): + - Bridge relays can now set the BridgeDistribution config option to + add a "bridge-distribution-request" line to their bridge descriptor, + which tells BridgeDB how they'd like their bridge address to be + given out. (Note that as of Oct 2017, BridgeDB does not yet implement + this feature.) As a side benefit, this feature provides a way + to distinguish bridge descriptors from non-bridge descriptors. + Implements tickets 18329. + diff --git a/doc/tor.1.txt b/doc/tor.1.txt index ba2bc13dad..e76efddcd5 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1670,6 +1670,17 @@ is non-zero): server descriptor to the bridge database, rather than to the public directory authorities. +[[BridgeDistribution]] **BridgeDistribution** __string__:: + If set along with BridgeRelay, Tor will include a new line in its + bridge descriptor which indicates to the BridgeDB service how it + would like its bridge address to be given out. Set it to "none" if + you want BridgeDB to avoid distributing your bridge address, or "any" to + let BridgeDB decide. (Default: any) + + + Note: as of Oct 2017, the BridgeDB part of this option is not yet + implemented. Until BridgeDB is updated to obey this option, your + bridge will make this request, but it will not (yet) be obeyed. + [[ContactInfo]] **ContactInfo** __email_address__:: Administrative contact information for this relay or bridge. This line can be used to contact you if your relay or bridge is misconfigured or diff --git a/src/or/config.c b/src/or/config.c index 705113c80a..63a1cac99e 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -253,6 +253,7 @@ static config_var_t option_vars_[] = { V(BridgePassword, STRING, NULL), V(BridgeRecordUsageByCountry, BOOL, "1"), V(BridgeRelay, BOOL, "0"), + V(BridgeDistribution, STRING, NULL), V(CellStatistics, BOOL, "0"), V(PaddingStatistics, BOOL, "1"), V(LearnCircuitBuildTimeout, BOOL, "1"), @@ -731,7 +732,6 @@ static int parse_ports(or_options_t *options, int validate_only, static int check_server_ports(const smartlist_t *ports, const or_options_t *options, int *num_low_ports_out); - static int validate_data_directory(or_options_t *options); static int write_configuration_file(const char *fname, const or_options_t *options); @@ -3512,6 +3512,15 @@ options_validate(or_options_t *old_options, or_options_t *options, REJECT("Relays cannot set ReducedConnectionPadding. "); } + if (options->BridgeDistribution) { + if (!options->BridgeRelay) { + REJECT("You set BridgeDistribution, but you didn't set BridgeRelay!"); + } + if (check_bridge_distribution_setting(options->BridgeDistribution) < 0) { + REJECT("Invalid BridgeDistribution value."); + } + } + if (options->MinUptimeHidServDirectoryV2 < 0) { log_warn(LD_CONFIG, "MinUptimeHidServDirectoryV2 option must be at " "least 0 seconds. Changing to 0."); @@ -4686,6 +4695,8 @@ options_transition_affects_descriptor(const or_options_t *old_options, get_effective_bwburst(old_options) != get_effective_bwburst(new_options) || !opt_streq(old_options->ContactInfo, new_options->ContactInfo) || + !opt_streq(old_options->BridgeDistribution, + new_options->BridgeDistribution) || !config_lines_eq(old_options->MyFamily, new_options->MyFamily) || !opt_streq(old_options->AccountingStart, new_options->AccountingStart) || old_options->AccountingMax != new_options->AccountingMax || @@ -6583,6 +6594,55 @@ warn_client_dns_cache(const char *option, int disabling) "to your destination."); } +/** + * Validate the configured bridge distribution method from a BridgeDistribution + * config line. + * + * The input bd, is a string taken from the BridgeDistribution config + * line (if present). If the option wasn't set, return 0 immediately. The + * BridgeDistribution option is then validated. Currently valid, recognised + * options are: + * + * - "none" + * - "any" + * - "https" + * - "email" + * - "moat" + * - "hyphae" + * + * If the option string is unrecognised, a warning will be logged and 0 is + * returned. If the option string contains an invalid character, -1 is + * returned. + **/ +STATIC int +check_bridge_distribution_setting(const char *bd) +{ + if (bd == NULL) + return 0; + + const char *RECOGNIZED[] = { + "none", "any", "https", "email", "moat", "hyphae" + }; + unsigned i; + for (i = 0; i < ARRAY_LENGTH(RECOGNIZED); ++i) { + if (!strcmp(bd, RECOGNIZED[i])) + return 0; + } + + const char *cp = bd; + // Method = (KeywordChar | "_") + + while (TOR_ISALNUM(*cp) || *cp == '-' || *cp == '_') + ++cp; + + if (*cp == 0) { + log_warn(LD_CONFIG, "Unrecognized BridgeDistribution value %s. I'll " + "assume you know what you are doing...", escaped(bd)); + return 0; // we reached the end of the string; all is well + } else { + return -1; // we found a bad character in the string. + } +} + /** * Parse port configuration for a single port type. * diff --git a/src/or/config.h b/src/or/config.h index af945f16be..efdd8c59b0 100644 --- a/src/or/config.h +++ b/src/or/config.h @@ -203,6 +203,8 @@ STATIC int parse_port_config(smartlist_t *out, const char *defaultaddr, int defaultport, const unsigned flags); + +STATIC int check_bridge_distribution_setting(const char *bd); #endif /* defined(CONFIG_PRIVATE) */ #endif /* !defined(TOR_CONFIG_H) */ diff --git a/src/or/or.h b/src/or/or.h index f1e78ab025..26f00b7181 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3770,6 +3770,10 @@ typedef struct { int BridgeAuthoritativeDir; /**< Boolean: is this an authoritative directory * that aggregates bridge descriptors? */ + /** If set on a bridge relay, it will include this value on a new + * "bridge-distribution-request" line in its bridge descriptor. */ + char *BridgeDistribution; + /** If set on a bridge authority, it will answer requests on its dirport * for bridge statuses -- but only if the requests use this password. */ char *BridgePassword; diff --git a/src/or/router.c b/src/or/router.c index 172531e70e..8ad5d038ef 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -2956,6 +2956,18 @@ router_dump_router_to_string(routerinfo_t *router, smartlist_add_asprintf(chunks, "contact %s\n", ci); } + if (options->BridgeRelay) { + const char *bd; + if (options->BridgeDistribution && strlen(options->BridgeDistribution)) { + bd = options->BridgeDistribution; + } else { + bd = "any"; + } + if (strchr(bd, '\n') || strchr(bd, '\r')) + bd = escaped(bd); + smartlist_add_asprintf(chunks, "bridge-distribution-request %s\n", bd); + } + if (router->onion_curve25519_pkey) { char kbuf[128]; base64_encode(kbuf, sizeof(kbuf), diff --git a/src/test/include.am b/src/test/include.am index 4cc2fae4ca..50cee693ab 100644 --- a/src/test/include.am +++ b/src/test/include.am @@ -146,6 +146,7 @@ src_test_test_SOURCES = \ src/test/test_relaycell.c \ src/test/test_rendcache.c \ src/test/test_replay.c \ + src/test/test_router.c \ src/test/test_routerkeys.c \ src/test/test_routerlist.c \ src/test/test_routerset.c \ diff --git a/src/test/test.c b/src/test/test.c index 368d874be9..da4f7a0553 100644 --- a/src/test/test.c +++ b/src/test/test.c @@ -1223,6 +1223,7 @@ struct testgroup_t testgroups[] = { { "relaycell/", relaycell_tests }, { "rend_cache/", rend_cache_tests }, { "replaycache/", replaycache_tests }, + { "router/", router_tests }, { "routerkeys/", routerkeys_tests }, { "routerlist/", routerlist_tests }, { "routerset/" , routerset_tests }, diff --git a/src/test/test_config.c b/src/test/test_config.c index 58268705ea..456d8bcc3e 100644 --- a/src/test/test_config.c +++ b/src/test/test_config.c @@ -5446,6 +5446,73 @@ test_config_dup_and_filter(void *arg) config_free_lines(line_dup); } +/* If we're not configured to be a bridge, but we set + * BridgeDistribution, then options_validate () should return -1. */ +static void +test_config_check_bridge_distribution_setting_not_a_bridge(void *arg) +{ + or_options_t* options = get_options_mutable(); + or_options_t* old_options = options; + or_options_t* default_options = options; + char* message = (char*)(""); + int ret; + + (void)arg; + + options->BridgeRelay = 0; + options->BridgeDistribution = (char*)("https"); + + ret = options_validate(old_options, options, default_options, 0, &message); + + tt_int_op(ret, OP_EQ, -1); + done: + return; +} + +/* If the BridgeDistribution setting was valid, 0 should be returned. */ +static void +test_config_check_bridge_distribution_setting_valid(void *arg) +{ + int ret = check_bridge_distribution_setting("https"); + + (void)arg; + + tt_int_op(ret, OP_EQ, 0); + done: + return; +} + +/* If the BridgeDistribution setting was invalid, -1 should be returned. */ +static void +test_config_check_bridge_distribution_setting_invalid(void *arg) +{ + int ret = check_bridge_distribution_setting("hyphens-are-allowed"); + + (void)arg; + + tt_int_op(ret, OP_EQ, 0); + + ret = check_bridge_distribution_setting("asterisks*are*forbidden"); + + tt_int_op(ret, OP_EQ, -1); + done: + return; +} + +/* If the BridgeDistribution setting was unrecognised, a warning should be + * logged and 0 should be returned. */ +static void +test_config_check_bridge_distribution_setting_unrecognised(void *arg) +{ + int ret = check_bridge_distribution_setting("unicorn"); + + (void)arg; + + tt_int_op(ret, OP_EQ, 0); + done: + return; +} + #define CONFIG_TEST(name, flags) \ { #name, test_config_ ## name, flags, NULL, NULL } @@ -5489,6 +5556,10 @@ struct testcase_t config_tests[] = { CONFIG_TEST(include_flag_torrc_only, TT_FORK), CONFIG_TEST(include_flag_defaults_only, TT_FORK), CONFIG_TEST(dup_and_filter, 0), + CONFIG_TEST(check_bridge_distribution_setting_not_a_bridge, TT_FORK), + CONFIG_TEST(check_bridge_distribution_setting_valid, 0), + CONFIG_TEST(check_bridge_distribution_setting_invalid, 0), + CONFIG_TEST(check_bridge_distribution_setting_unrecognised, 0), END_OF_TESTCASES }; diff --git a/src/test/test_router.c b/src/test/test_router.c new file mode 100644 index 0000000000..4e96e24534 --- /dev/null +++ b/src/test/test_router.c @@ -0,0 +1,112 @@ +/* Copyright (c) 2017, The Tor Project, Inc. */ +/* Copyright (c) 2017, isis agora lovecruft */ +/* See LICENSE for licensing information */ + +/** + * \file test_router.c + * \brief Unittests for code in src/or/router.c + **/ + +#include "or.h" +#include "config.h" +#include "crypto_curve25519.h" +#include "crypto_ed25519.h" +#include "router.h" +#include "routerlist.h" + +/* Test suite stuff */ +#include "test.h" + +NS_DECL(const routerinfo_t *, router_get_my_routerinfo, (void)); + +static routerinfo_t* mock_routerinfo; + +static const routerinfo_t* +NS(router_get_my_routerinfo)(void) +{ + crypto_pk_t* ident_key; + crypto_pk_t* tap_key; + time_t now; + + if (!mock_routerinfo) { + /* Mock the published timestamp, otherwise router_dump_router_to_string() + * will poop its pants. */ + time(&now); + + /* We'll need keys, or router_dump_router_to_string() would return NULL. */ + ident_key = pk_generate(0); + tap_key = pk_generate(0); + + tor_assert(ident_key != NULL); + tor_assert(tap_key != NULL); + + mock_routerinfo = tor_malloc_zero(sizeof(routerinfo_t)); + mock_routerinfo->nickname = tor_strdup("ConlonNancarrow"); + mock_routerinfo->addr = 123456789; + mock_routerinfo->or_port = 443; + mock_routerinfo->platform = tor_strdup("unittest"); + mock_routerinfo->cache_info.published_on = now; + mock_routerinfo->identity_pkey = crypto_pk_dup_key(ident_key); + mock_routerinfo->onion_pkey = crypto_pk_dup_key(tap_key); + mock_routerinfo->bandwidthrate = 9001; + mock_routerinfo->bandwidthburst = 9002; + } + + return mock_routerinfo; +} + +/* If no distribution option was set, then check_bridge_distribution_setting() + * should have set it to "any". */ +static void +test_router_dump_router_to_string_no_bridge_distribution_method(void *arg) +{ + const char* needle = "bridge-distribution-request any"; + or_options_t* options = get_options_mutable(); + routerinfo_t* router = NULL; + curve25519_keypair_t ntor_keypair; + ed25519_keypair_t signing_keypair; + char* desc = NULL; + char* found = NULL; + (void)arg; + + NS_MOCK(router_get_my_routerinfo); + + options->ORPort_set = 1; + options->BridgeRelay = 1; + + /* Generate keys which router_dump_router_to_string() expects to exist. */ + tt_int_op(0, ==, curve25519_keypair_generate(&ntor_keypair, 0)); + tt_int_op(0, ==, ed25519_keypair_generate(&signing_keypair, 0)); + + /* Set up part of our routerinfo_t so that we don't trigger any other + * assertions in router_dump_router_to_string(). */ + router = (routerinfo_t*)router_get_my_routerinfo(); + tt_ptr_op(router, !=, NULL); + + router->onion_curve25519_pkey = &ntor_keypair.pubkey; + + /* Generate our server descriptor and ensure that the substring + * "bridge-distribution-request any" occurs somewhere within it. */ + desc = router_dump_router_to_string(router, + router->identity_pkey, + router->onion_pkey, + &ntor_keypair, + &signing_keypair); + tt_ptr_op(desc, !=, NULL); + found = strstr(desc, needle); + tt_ptr_op(found, !=, NULL); + + done: + NS_UNMOCK(router_get_my_routerinfo); + + tor_free(desc); +} + +#define ROUTER_TEST(name, flags) \ + { #name, test_router_ ## name, flags, NULL, NULL } + +struct testcase_t router_tests[] = { + ROUTER_TEST(dump_router_to_string_no_bridge_distribution_method, TT_FORK), + END_OF_TESTCASES +}; +