/* Copyright (c) 2014-2017, The Tor Project, Inc. */ /* See LICENSE for licensing information */ #define DIRSERV_PRIVATE #define ROUTERPARSE_PRIVATE #define NETWORKSTATUS_PRIVATE #include "orconfig.h" #include "or.h" #include "config.h" #include "dirserv.h" #include "container.h" #include "entrynodes.h" #include "util.h" #include "routerparse.h" #include "networkstatus.h" #include "test.h" #include "test_helpers.h" #include "log_test_helpers.h" /** Generate a vote_routerstatus_t for a router with identity digest * digest_in_hex. */ static vote_routerstatus_t * gen_vote_routerstatus_for_tests(const char *digest_in_hex, int is_guard) { int retval; vote_routerstatus_t *vrs = NULL; routerstatus_t *rs; vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); rs = &vrs->status; { /* Useful information for tests */ char digest_tmp[DIGEST_LEN]; /* Guard or not? */ rs->is_possible_guard = is_guard; /* Fill in the fpr */ tt_int_op(strlen(digest_in_hex), ==, HEX_DIGEST_LEN); retval = base16_decode(digest_tmp, sizeof(digest_tmp), digest_in_hex, HEX_DIGEST_LEN); tt_int_op(retval, ==, sizeof(digest_tmp)); memcpy(rs->identity_digest, digest_tmp, DIGEST_LEN); } { /* Misc info (maybe not used in tests) */ vrs->version = tor_strdup("0.1.2.14"); strlcpy(rs->nickname, "router2", sizeof(rs->nickname)); memset(rs->descriptor_digest, 78, DIGEST_LEN); rs->addr = 0x99008801; rs->or_port = 443; rs->dir_port = 8000; /* all flags but running cleared */ rs->is_flagged_running = 1; vrs->has_measured_bw = 1; rs->has_bandwidth = 1; } return vrs; done: vote_routerstatus_free(vrs); return NULL; } /** Make sure our parsers reject corrupted guardfraction files. */ static void test_parse_guardfraction_file_bad(void *arg) { int retval; char *guardfraction_bad = NULL; const char *yesterday_date_str = get_yesterday_date_str(); (void) arg; /* Start parsing all those corrupted guardfraction files! */ /* Guardfraction file version is not a number! */ tor_asprintf(&guardfraction_bad, "guardfraction-file-version nan\n" "written-at %s\n" "n-inputs 420 3\n" "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n" "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); tt_int_op(retval, ==, -1); tor_free(guardfraction_bad); /* This one does not have a date! Parsing should fail. */ tor_asprintf(&guardfraction_bad, "guardfraction-file-version 1\n" "written-at not_date\n" "n-inputs 420 3\n" "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n" "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n"); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); tt_int_op(retval, ==, -1); tor_free(guardfraction_bad); /* This one has an incomplete n-inputs line, but parsing should still continue. */ tor_asprintf(&guardfraction_bad, "guardfraction-file-version 1\n" "written-at %s\n" "n-inputs biggie\n" "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n" "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); tt_int_op(retval, ==, 2); tor_free(guardfraction_bad); /* This one does not have a fingerprint in the guard line! */ tor_asprintf(&guardfraction_bad, "guardfraction-file-version 1\n" "written-at %s\n" "n-inputs 420 3\n" "guard-seen not_a_fingerprint 100 420\n", yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); tt_int_op(retval, ==, 0); tor_free(guardfraction_bad); /* This one does not even have an integer guardfraction value. */ tor_asprintf(&guardfraction_bad, "guardfraction-file-version 1\n" "written-at %s\n" "n-inputs 420 3\n" "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 NaN 420\n" "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); tt_int_op(retval, ==, 1); tor_free(guardfraction_bad); /* This one is not a percentage (not in [0, 100]) */ tor_asprintf(&guardfraction_bad, "guardfraction-file-version 1\n" "written-at %s\n" "n-inputs 420 3\n" "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 666 420\n" "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); tt_int_op(retval, ==, 1); tor_free(guardfraction_bad); /* This one is not a percentage either (not in [0, 100]) */ tor_asprintf(&guardfraction_bad, "guardfraction-file-version 1\n" "written-at %s\n" "n-inputs 420 3\n" "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 -3 420\n", yesterday_date_str); retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); tt_int_op(retval, ==, 0); done: tor_free(guardfraction_bad); } /** Make sure that our test guardfraction file gets parsed properly, and * its information are applied properly to our routerstatuses. */ static void test_parse_guardfraction_file_good(void *arg) { int retval; vote_routerstatus_t *vrs_guard = NULL; vote_routerstatus_t *vrs_dummy = NULL; char *guardfraction_good = NULL; const char *yesterday_date_str = get_yesterday_date_str(); smartlist_t *routerstatuses = smartlist_new(); /* Some test values that we need to validate later */ const char fpr_guard[] = "D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777"; const char fpr_unlisted[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; const int guardfraction_value = 42; (void) arg; { /* Populate the smartlist with some fake routerstatuses, so that after parsing the guardfraction file we can check that their elements got filled properly. */ /* This one is a guard */ vrs_guard = gen_vote_routerstatus_for_tests(fpr_guard, 1); tt_assert(vrs_guard); smartlist_add(routerstatuses, vrs_guard); /* This one is a guard but it's not in the guardfraction file */ vrs_dummy = gen_vote_routerstatus_for_tests(fpr_unlisted, 1); tt_assert(vrs_dummy); smartlist_add(routerstatuses, vrs_dummy); } tor_asprintf(&guardfraction_good, "guardfraction-file-version 1\n" "written-at %s\n" "n-inputs 420 3\n" "guard-seen %s %d 420\n", yesterday_date_str, fpr_guard, guardfraction_value); /* Read the guardfraction file */ retval = dirserv_read_guardfraction_file_from_str(guardfraction_good, routerstatuses); tt_int_op(retval, ==, 1); { /* Test that routerstatus fields got filled properly */ /* The guardfraction fields of the guard should be filled. */ tt_assert(vrs_guard->status.has_guardfraction); tt_int_op(vrs_guard->status.guardfraction_percentage, ==, guardfraction_value); /* The guard that was not in the guardfraction file should not have been touched either. */ tt_assert(!vrs_dummy->status.has_guardfraction); } done: vote_routerstatus_free(vrs_guard); vote_routerstatus_free(vrs_dummy); smartlist_free(routerstatuses); tor_free(guardfraction_good); } /** Make sure that the guardfraction bandwidths get calculated properly. */ static void test_get_guardfraction_bandwidth(void *arg) { guardfraction_bandwidth_t gf_bw; const int orig_bw = 1000; (void) arg; /* A guard with bandwidth 1000 and GuardFraction 0.25, should have bandwidth 250 as a guard and bandwidth 750 as a non-guard. */ guard_get_guardfraction_bandwidth(&gf_bw, orig_bw, 25); tt_int_op(gf_bw.guard_bw, ==, 250); tt_int_op(gf_bw.non_guard_bw, ==, 750); /* Also check the 'guard_bw + non_guard_bw == original_bw' * invariant. */ tt_int_op(gf_bw.non_guard_bw + gf_bw.guard_bw, ==, orig_bw); done: ; } /** Parse the GuardFraction element of the consensus, and make sure it * gets parsed correctly. */ static void test_parse_guardfraction_consensus(void *arg) { int retval; or_options_t *options = get_options_mutable(); const char *guardfraction_str_good = "GuardFraction=66"; routerstatus_t rs_good; routerstatus_t rs_no_guard; const char *guardfraction_str_bad1 = "GuardFraction="; /* no value */ routerstatus_t rs_bad1; const char *guardfraction_str_bad2 = "GuardFraction=166"; /* no percentage */ routerstatus_t rs_bad2; (void) arg; /* GuardFraction use is currently disabled by default. So we need to manually enable it. */ options->UseGuardFraction = 1; { /* Properly formatted GuardFraction. Check that it gets applied correctly. */ memset(&rs_good, 0, sizeof(routerstatus_t)); rs_good.is_possible_guard = 1; retval = routerstatus_parse_guardfraction(guardfraction_str_good, NULL, NULL, &rs_good); tt_int_op(retval, ==, 0); tt_assert(rs_good.has_guardfraction); tt_int_op(rs_good.guardfraction_percentage, ==, 66); } { /* Properly formatted GuardFraction but router is not a guard. GuardFraction should not get applied. */ memset(&rs_no_guard, 0, sizeof(routerstatus_t)); tt_assert(!rs_no_guard.is_possible_guard); setup_full_capture_of_logs(LOG_WARN); retval = routerstatus_parse_guardfraction(guardfraction_str_good, NULL, NULL, &rs_no_guard); tt_int_op(retval, ==, 0); tt_assert(!rs_no_guard.has_guardfraction); expect_single_log_msg_containing("Got GuardFraction for non-guard . " "This is not supposed to happen."); teardown_capture_of_logs(); } { /* Bad GuardFraction. Function should fail and not apply. */ memset(&rs_bad1, 0, sizeof(routerstatus_t)); rs_bad1.is_possible_guard = 1; retval = routerstatus_parse_guardfraction(guardfraction_str_bad1, NULL, NULL, &rs_bad1); tt_int_op(retval, ==, -1); tt_assert(!rs_bad1.has_guardfraction); } { /* Bad GuardFraction. Function should fail and not apply. */ memset(&rs_bad2, 0, sizeof(routerstatus_t)); rs_bad2.is_possible_guard = 1; retval = routerstatus_parse_guardfraction(guardfraction_str_bad2, NULL, NULL, &rs_bad2); tt_int_op(retval, ==, -1); tt_assert(!rs_bad2.has_guardfraction); } done: teardown_capture_of_logs(); } /** Make sure that we use GuardFraction information when we should, * according to the torrc option and consensus parameter. */ static void test_should_apply_guardfraction(void *arg) { networkstatus_t vote_enabled, vote_disabled, vote_missing; or_options_t *options = get_options_mutable(); (void) arg; { /* Fill the votes for later */ /* This one suggests enabled GuardFraction. */ memset(&vote_enabled, 0, sizeof(vote_enabled)); vote_enabled.net_params = smartlist_new(); smartlist_split_string(vote_enabled.net_params, "UseGuardFraction=1", NULL, 0, 0); /* This one suggests disabled GuardFraction. */ memset(&vote_disabled, 0, sizeof(vote_disabled)); vote_disabled.net_params = smartlist_new(); smartlist_split_string(vote_disabled.net_params, "UseGuardFraction=0", NULL, 0, 0); /* This one doesn't have GuardFraction at all. */ memset(&vote_missing, 0, sizeof(vote_missing)); vote_missing.net_params = smartlist_new(); smartlist_split_string(vote_missing.net_params, "leon=trout", NULL, 0, 0); } /* If torrc option is set to yes, we should always use * guardfraction.*/ options->UseGuardFraction = 1; tt_int_op(should_apply_guardfraction(&vote_disabled), ==, 1); /* If torrc option is set to no, we should never use * guardfraction.*/ options->UseGuardFraction = 0; tt_int_op(should_apply_guardfraction(&vote_enabled), ==, 0); /* Now let's test torrc option set to auto. */ options->UseGuardFraction = -1; /* If torrc option is set to auto, and consensus parameter is set to * yes, we should use guardfraction. */ tt_int_op(should_apply_guardfraction(&vote_enabled), ==, 1); /* If torrc option is set to auto, and consensus parameter is set to * no, we should use guardfraction. */ tt_int_op(should_apply_guardfraction(&vote_disabled), ==, 0); /* If torrc option is set to auto, and consensus parameter is not * set, we should fallback to "no". */ tt_int_op(should_apply_guardfraction(&vote_missing), ==, 0); done: SMARTLIST_FOREACH(vote_enabled.net_params, char *, cp, tor_free(cp)); SMARTLIST_FOREACH(vote_disabled.net_params, char *, cp, tor_free(cp)); SMARTLIST_FOREACH(vote_missing.net_params, char *, cp, tor_free(cp)); smartlist_free(vote_enabled.net_params); smartlist_free(vote_disabled.net_params); smartlist_free(vote_missing.net_params); } struct testcase_t guardfraction_tests[] = { { "parse_guardfraction_file_bad", test_parse_guardfraction_file_bad, TT_FORK, NULL, NULL }, { "parse_guardfraction_file_good", test_parse_guardfraction_file_good, TT_FORK, NULL, NULL }, { "parse_guardfraction_consensus", test_parse_guardfraction_consensus, TT_FORK, NULL, NULL }, { "get_guardfraction_bandwidth", test_get_guardfraction_bandwidth, TT_FORK, NULL, NULL }, { "should_apply_guardfraction", test_should_apply_guardfraction, TT_FORK, NULL, NULL }, END_OF_TESTCASES };