test_guardfraction.c (14807B)
1 /* Copyright (c) 2014-2021, The Tor Project, Inc. */ 2 /* See LICENSE for licensing information */ 3 4 #define GUARDFRACTION_PRIVATE 5 #define NETWORKSTATUS_PRIVATE 6 #define NS_PARSE_PRIVATE 7 8 #include "orconfig.h" 9 #include "core/or/or.h" 10 #include "app/config/config.h" 11 #include "feature/dirauth/guardfraction.h" 12 #include "feature/client/entrynodes.h" 13 #include "feature/dirparse/ns_parse.h" 14 #include "feature/nodelist/networkstatus.h" 15 16 #include "feature/nodelist/networkstatus_st.h" 17 #include "feature/dirauth/vote_microdesc_hash_st.h" 18 #include "feature/nodelist/vote_routerstatus_st.h" 19 20 #include "test/test.h" 21 #include "test/test_helpers.h" 22 #include "test/log_test_helpers.h" 23 24 /** Generate a vote_routerstatus_t for a router with identity digest 25 * <b>digest_in_hex</b>. */ 26 static vote_routerstatus_t * 27 gen_vote_routerstatus_for_tests(const char *digest_in_hex, int is_guard) 28 { 29 int retval; 30 vote_routerstatus_t *vrs = NULL; 31 routerstatus_t *rs; 32 33 vrs = tor_malloc_zero(sizeof(vote_routerstatus_t)); 34 rs = &vrs->status; 35 36 { /* Useful information for tests */ 37 char digest_tmp[DIGEST_LEN]; 38 39 /* Guard or not? */ 40 rs->is_possible_guard = is_guard; 41 42 /* Fill in the fpr */ 43 tt_int_op(strlen(digest_in_hex), OP_EQ, HEX_DIGEST_LEN); 44 retval = base16_decode(digest_tmp, sizeof(digest_tmp), 45 digest_in_hex, HEX_DIGEST_LEN); 46 tt_int_op(retval, OP_EQ, sizeof(digest_tmp)); 47 memcpy(rs->identity_digest, digest_tmp, DIGEST_LEN); 48 } 49 50 { /* Misc info (maybe not used in tests) */ 51 vrs->version = tor_strdup("0.1.2.14"); 52 strlcpy(rs->nickname, "router2", sizeof(rs->nickname)); 53 memset(rs->descriptor_digest, 78, DIGEST_LEN); 54 tor_addr_from_ipv4h(&rs->ipv4_addr, 0x99008801); 55 rs->ipv4_orport = 443; 56 rs->ipv4_dirport = 8000; 57 /* all flags but running cleared */ 58 rs->is_flagged_running = 1; 59 vrs->has_measured_bw = 1; 60 rs->has_bandwidth = 1; 61 } 62 63 return vrs; 64 65 done: 66 vote_routerstatus_free(vrs); 67 68 return NULL; 69 } 70 71 /** Make sure our parsers reject corrupted guardfraction files. */ 72 static void 73 test_parse_guardfraction_file_bad(void *arg) 74 { 75 int retval; 76 char *guardfraction_bad = NULL; 77 const char *yesterday_date_str = get_yesterday_date_str(); 78 79 (void) arg; 80 81 /* Start parsing all those corrupted guardfraction files! */ 82 83 /* Guardfraction file version is not a number! */ 84 tor_asprintf(&guardfraction_bad, 85 "guardfraction-file-version nan\n" 86 "written-at %s\n" 87 "n-inputs 420 3\n" 88 "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n" 89 "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", 90 yesterday_date_str); 91 92 retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); 93 tt_int_op(retval, OP_EQ, -1); 94 tor_free(guardfraction_bad); 95 96 /* This one does not have a date! Parsing should fail. */ 97 tor_asprintf(&guardfraction_bad, 98 "guardfraction-file-version 1\n" 99 "written-at not_date\n" 100 "n-inputs 420 3\n" 101 "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n" 102 "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n"); 103 104 retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); 105 tt_int_op(retval, OP_EQ, -1); 106 tor_free(guardfraction_bad); 107 108 /* This one has an incomplete n-inputs line, but parsing should 109 still continue. */ 110 tor_asprintf(&guardfraction_bad, 111 "guardfraction-file-version 1\n" 112 "written-at %s\n" 113 "n-inputs biggie\n" 114 "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 100 420\n" 115 "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", 116 yesterday_date_str); 117 118 retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); 119 tt_int_op(retval, OP_EQ, 2); 120 tor_free(guardfraction_bad); 121 122 /* This one does not have a fingerprint in the guard line! */ 123 tor_asprintf(&guardfraction_bad, 124 "guardfraction-file-version 1\n" 125 "written-at %s\n" 126 "n-inputs 420 3\n" 127 "guard-seen not_a_fingerprint 100 420\n", 128 yesterday_date_str); 129 130 retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); 131 tt_int_op(retval, OP_EQ, 0); 132 tor_free(guardfraction_bad); 133 134 /* This one does not even have an integer guardfraction value. */ 135 tor_asprintf(&guardfraction_bad, 136 "guardfraction-file-version 1\n" 137 "written-at %s\n" 138 "n-inputs 420 3\n" 139 "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 NaN 420\n" 140 "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", 141 yesterday_date_str); 142 143 retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); 144 tt_int_op(retval, OP_EQ, 1); 145 tor_free(guardfraction_bad); 146 147 /* This one is not a percentage (not in [0, 100]) */ 148 tor_asprintf(&guardfraction_bad, 149 "guardfraction-file-version 1\n" 150 "written-at %s\n" 151 "n-inputs 420 3\n" 152 "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 666 420\n" 153 "guard-seen 07B5547026DF3E229806E135CFA8552D56AFBABC 5 420\n", 154 yesterday_date_str); 155 156 retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); 157 tt_int_op(retval, OP_EQ, 1); 158 tor_free(guardfraction_bad); 159 160 /* This one is not a percentage either (not in [0, 100]) */ 161 tor_asprintf(&guardfraction_bad, 162 "guardfraction-file-version 1\n" 163 "written-at %s\n" 164 "n-inputs 420 3\n" 165 "guard-seen D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777 -3 420\n", 166 yesterday_date_str); 167 168 retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL); 169 tt_int_op(retval, OP_EQ, 0); 170 171 done: 172 tor_free(guardfraction_bad); 173 } 174 175 /** Make sure that our test guardfraction file gets parsed properly, and 176 * its information are applied properly to our routerstatuses. */ 177 static void 178 test_parse_guardfraction_file_good(void *arg) 179 { 180 int retval; 181 vote_routerstatus_t *vrs_guard = NULL; 182 vote_routerstatus_t *vrs_dummy = NULL; 183 char *guardfraction_good = NULL; 184 const char *yesterday_date_str = get_yesterday_date_str(); 185 smartlist_t *routerstatuses = smartlist_new(); 186 187 /* Some test values that we need to validate later */ 188 const char fpr_guard[] = "D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777"; 189 const char fpr_unlisted[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; 190 const int guardfraction_value = 42; 191 192 (void) arg; 193 194 { 195 /* Populate the smartlist with some fake routerstatuses, so that 196 after parsing the guardfraction file we can check that their 197 elements got filled properly. */ 198 199 /* This one is a guard */ 200 vrs_guard = gen_vote_routerstatus_for_tests(fpr_guard, 1); 201 tt_assert(vrs_guard); 202 smartlist_add(routerstatuses, vrs_guard); 203 204 /* This one is a guard but it's not in the guardfraction file */ 205 vrs_dummy = gen_vote_routerstatus_for_tests(fpr_unlisted, 1); 206 tt_assert(vrs_dummy); 207 smartlist_add(routerstatuses, vrs_dummy); 208 } 209 210 tor_asprintf(&guardfraction_good, 211 "guardfraction-file-version 1\n" 212 "written-at %s\n" 213 "n-inputs 420 3\n" 214 "guard-seen %s %d 420\n", 215 yesterday_date_str, 216 fpr_guard, guardfraction_value); 217 218 /* Read the guardfraction file */ 219 retval = dirserv_read_guardfraction_file_from_str(guardfraction_good, 220 routerstatuses); 221 tt_int_op(retval, OP_EQ, 1); 222 223 { /* Test that routerstatus fields got filled properly */ 224 225 /* The guardfraction fields of the guard should be filled. */ 226 tt_assert(vrs_guard->status.has_guardfraction); 227 tt_int_op(vrs_guard->status.guardfraction_percentage, 228 OP_EQ, 229 guardfraction_value); 230 231 /* The guard that was not in the guardfraction file should not have 232 been touched either. */ 233 tt_assert(!vrs_dummy->status.has_guardfraction); 234 } 235 236 done: 237 vote_routerstatus_free(vrs_guard); 238 vote_routerstatus_free(vrs_dummy); 239 smartlist_free(routerstatuses); 240 tor_free(guardfraction_good); 241 } 242 243 /** Make sure that the guardfraction bandwidths get calculated properly. */ 244 static void 245 test_get_guardfraction_bandwidth(void *arg) 246 { 247 guardfraction_bandwidth_t gf_bw; 248 const int orig_bw = 1000; 249 250 (void) arg; 251 252 /* A guard with bandwidth 1000 and GuardFraction 0.25, should have 253 bandwidth 250 as a guard and bandwidth 750 as a non-guard. */ 254 guard_get_guardfraction_bandwidth(&gf_bw, 255 orig_bw, 25); 256 257 tt_int_op(gf_bw.guard_bw, OP_EQ, 250); 258 tt_int_op(gf_bw.non_guard_bw, OP_EQ, 750); 259 260 /* Also check the 'guard_bw + non_guard_bw == original_bw' 261 * invariant. */ 262 tt_int_op(gf_bw.non_guard_bw + gf_bw.guard_bw, OP_EQ, orig_bw); 263 264 done: 265 ; 266 } 267 268 /** Parse the GuardFraction element of the consensus, and make sure it 269 * gets parsed correctly. */ 270 static void 271 test_parse_guardfraction_consensus(void *arg) 272 { 273 int retval; 274 or_options_t *options = get_options_mutable(); 275 276 const char *guardfraction_str_good = "GuardFraction=66"; 277 routerstatus_t rs_good; 278 routerstatus_t rs_no_guard; 279 280 const char *guardfraction_str_bad1 = "GuardFraction="; /* no value */ 281 routerstatus_t rs_bad1; 282 283 const char *guardfraction_str_bad2 = "GuardFraction=166"; /* no percentage */ 284 routerstatus_t rs_bad2; 285 286 (void) arg; 287 288 /* GuardFraction use is currently disabled by default. So we need to 289 manually enable it. */ 290 options->UseGuardFraction = 1; 291 292 { /* Properly formatted GuardFraction. Check that it gets applied 293 correctly. */ 294 memset(&rs_good, 0, sizeof(routerstatus_t)); 295 rs_good.is_possible_guard = 1; 296 297 retval = routerstatus_parse_guardfraction(guardfraction_str_good, 298 NULL, NULL, 299 &rs_good); 300 tt_int_op(retval, OP_EQ, 0); 301 tt_assert(rs_good.has_guardfraction); 302 tt_int_op(rs_good.guardfraction_percentage, OP_EQ, 66); 303 } 304 305 { /* Properly formatted GuardFraction but router is not a 306 guard. GuardFraction should not get applied. */ 307 memset(&rs_no_guard, 0, sizeof(routerstatus_t)); 308 tt_assert(!rs_no_guard.is_possible_guard); 309 310 setup_full_capture_of_logs(LOG_WARN); 311 retval = routerstatus_parse_guardfraction(guardfraction_str_good, 312 NULL, NULL, 313 &rs_no_guard); 314 tt_int_op(retval, OP_EQ, 0); 315 tt_assert(!rs_no_guard.has_guardfraction); 316 expect_single_log_msg_containing("Got GuardFraction for non-guard . " 317 "This is not supposed to happen."); 318 teardown_capture_of_logs(); 319 } 320 321 { /* Bad GuardFraction. Function should fail and not apply. */ 322 memset(&rs_bad1, 0, sizeof(routerstatus_t)); 323 rs_bad1.is_possible_guard = 1; 324 325 retval = routerstatus_parse_guardfraction(guardfraction_str_bad1, 326 NULL, NULL, 327 &rs_bad1); 328 tt_int_op(retval, OP_EQ, -1); 329 tt_assert(!rs_bad1.has_guardfraction); 330 } 331 332 { /* Bad GuardFraction. Function should fail and not apply. */ 333 memset(&rs_bad2, 0, sizeof(routerstatus_t)); 334 rs_bad2.is_possible_guard = 1; 335 336 retval = routerstatus_parse_guardfraction(guardfraction_str_bad2, 337 NULL, NULL, 338 &rs_bad2); 339 tt_int_op(retval, OP_EQ, -1); 340 tt_assert(!rs_bad2.has_guardfraction); 341 } 342 343 done: 344 teardown_capture_of_logs(); 345 } 346 347 /** Make sure that we use GuardFraction information when we should, 348 * according to the torrc option and consensus parameter. */ 349 static void 350 test_should_apply_guardfraction(void *arg) 351 { 352 networkstatus_t vote_enabled, vote_disabled, vote_missing; 353 or_options_t *options = get_options_mutable(); 354 355 (void) arg; 356 357 { /* Fill the votes for later */ 358 /* This one suggests enabled GuardFraction. */ 359 memset(&vote_enabled, 0, sizeof(vote_enabled)); 360 vote_enabled.net_params = smartlist_new(); 361 smartlist_split_string(vote_enabled.net_params, 362 "UseGuardFraction=1", NULL, 0, 0); 363 364 /* This one suggests disabled GuardFraction. */ 365 memset(&vote_disabled, 0, sizeof(vote_disabled)); 366 vote_disabled.net_params = smartlist_new(); 367 smartlist_split_string(vote_disabled.net_params, 368 "UseGuardFraction=0", NULL, 0, 0); 369 370 /* This one doesn't have GuardFraction at all. */ 371 memset(&vote_missing, 0, sizeof(vote_missing)); 372 vote_missing.net_params = smartlist_new(); 373 smartlist_split_string(vote_missing.net_params, 374 "leon=trout", NULL, 0, 0); 375 } 376 377 /* If torrc option is set to yes, we should always use 378 * guardfraction.*/ 379 options->UseGuardFraction = 1; 380 tt_int_op(should_apply_guardfraction(&vote_disabled), OP_EQ, 1); 381 382 /* If torrc option is set to no, we should never use 383 * guardfraction.*/ 384 options->UseGuardFraction = 0; 385 tt_int_op(should_apply_guardfraction(&vote_enabled), OP_EQ, 0); 386 387 /* Now let's test torrc option set to auto. */ 388 options->UseGuardFraction = -1; 389 390 /* If torrc option is set to auto, and consensus parameter is set to 391 * yes, we should use guardfraction. */ 392 tt_int_op(should_apply_guardfraction(&vote_enabled), OP_EQ, 1); 393 394 /* If torrc option is set to auto, and consensus parameter is set to 395 * no, we should use guardfraction. */ 396 tt_int_op(should_apply_guardfraction(&vote_disabled), OP_EQ, 0); 397 398 /* If torrc option is set to auto, and consensus parameter is not 399 * set, we should fallback to "no". */ 400 tt_int_op(should_apply_guardfraction(&vote_missing), OP_EQ, 0); 401 402 done: 403 SMARTLIST_FOREACH(vote_enabled.net_params, char *, cp, tor_free(cp)); 404 SMARTLIST_FOREACH(vote_disabled.net_params, char *, cp, tor_free(cp)); 405 SMARTLIST_FOREACH(vote_missing.net_params, char *, cp, tor_free(cp)); 406 smartlist_free(vote_enabled.net_params); 407 smartlist_free(vote_disabled.net_params); 408 smartlist_free(vote_missing.net_params); 409 } 410 411 struct testcase_t guardfraction_tests[] = { 412 { "parse_guardfraction_file_bad", test_parse_guardfraction_file_bad, 413 TT_FORK, NULL, NULL }, 414 { "parse_guardfraction_file_good", test_parse_guardfraction_file_good, 415 TT_FORK, NULL, NULL }, 416 { "parse_guardfraction_consensus", test_parse_guardfraction_consensus, 417 TT_FORK, NULL, NULL }, 418 { "get_guardfraction_bandwidth", test_get_guardfraction_bandwidth, 419 TT_FORK, NULL, NULL }, 420 { "should_apply_guardfraction", test_should_apply_guardfraction, 421 TT_FORK, NULL, NULL }, 422 423 END_OF_TESTCASES 424 };