Line data Source code
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 2 : gen_vote_routerstatus_for_tests(const char *digest_in_hex, int is_guard)
28 : {
29 2 : int retval;
30 2 : vote_routerstatus_t *vrs = NULL;
31 2 : routerstatus_t *rs;
32 :
33 2 : vrs = tor_malloc_zero(sizeof(vote_routerstatus_t));
34 2 : rs = &vrs->status;
35 :
36 : { /* Useful information for tests */
37 2 : char digest_tmp[DIGEST_LEN];
38 :
39 : /* Guard or not? */
40 2 : rs->is_possible_guard = is_guard;
41 :
42 : /* Fill in the fpr */
43 2 : tt_int_op(strlen(digest_in_hex), OP_EQ, HEX_DIGEST_LEN);
44 2 : retval = base16_decode(digest_tmp, sizeof(digest_tmp),
45 : digest_in_hex, HEX_DIGEST_LEN);
46 2 : tt_int_op(retval, OP_EQ, sizeof(digest_tmp));
47 2 : memcpy(rs->identity_digest, digest_tmp, DIGEST_LEN);
48 : }
49 :
50 : { /* Misc info (maybe not used in tests) */
51 2 : vrs->version = tor_strdup("0.1.2.14");
52 2 : strlcpy(rs->nickname, "router2", sizeof(rs->nickname));
53 2 : memset(rs->descriptor_digest, 78, DIGEST_LEN);
54 2 : tor_addr_from_ipv4h(&rs->ipv4_addr, 0x99008801);
55 2 : rs->ipv4_orport = 443;
56 2 : rs->ipv4_dirport = 8000;
57 : /* all flags but running cleared */
58 2 : rs->is_flagged_running = 1;
59 2 : vrs->has_measured_bw = 1;
60 2 : rs->has_bandwidth = 1;
61 : }
62 :
63 2 : return vrs;
64 :
65 0 : done:
66 0 : vote_routerstatus_free(vrs);
67 :
68 0 : return NULL;
69 : }
70 :
71 : /** Make sure our parsers reject corrupted guardfraction files. */
72 : static void
73 1 : test_parse_guardfraction_file_bad(void *arg)
74 : {
75 1 : int retval;
76 1 : char *guardfraction_bad = NULL;
77 1 : const char *yesterday_date_str = get_yesterday_date_str();
78 :
79 1 : (void) arg;
80 :
81 : /* Start parsing all those corrupted guardfraction files! */
82 :
83 : /* Guardfraction file version is not a number! */
84 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
93 1 : tt_int_op(retval, OP_EQ, -1);
94 1 : tor_free(guardfraction_bad);
95 :
96 : /* This one does not have a date! Parsing should fail. */
97 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
105 1 : tt_int_op(retval, OP_EQ, -1);
106 1 : tor_free(guardfraction_bad);
107 :
108 : /* This one has an incomplete n-inputs line, but parsing should
109 : still continue. */
110 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
119 1 : tt_int_op(retval, OP_EQ, 2);
120 1 : tor_free(guardfraction_bad);
121 :
122 : /* This one does not have a fingerprint in the guard line! */
123 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
131 1 : tt_int_op(retval, OP_EQ, 0);
132 1 : tor_free(guardfraction_bad);
133 :
134 : /* This one does not even have an integer guardfraction value. */
135 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
144 1 : tt_int_op(retval, OP_EQ, 1);
145 1 : tor_free(guardfraction_bad);
146 :
147 : /* This one is not a percentage (not in [0, 100]) */
148 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
157 1 : tt_int_op(retval, OP_EQ, 1);
158 1 : tor_free(guardfraction_bad);
159 :
160 : /* This one is not a percentage either (not in [0, 100]) */
161 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_bad, NULL);
169 1 : tt_int_op(retval, OP_EQ, 0);
170 :
171 1 : done:
172 1 : tor_free(guardfraction_bad);
173 1 : }
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 1 : test_parse_guardfraction_file_good(void *arg)
179 : {
180 1 : int retval;
181 1 : vote_routerstatus_t *vrs_guard = NULL;
182 1 : vote_routerstatus_t *vrs_dummy = NULL;
183 1 : char *guardfraction_good = NULL;
184 1 : const char *yesterday_date_str = get_yesterday_date_str();
185 1 : smartlist_t *routerstatuses = smartlist_new();
186 :
187 : /* Some test values that we need to validate later */
188 1 : const char fpr_guard[] = "D0EDB47BEAD32D26D0A837F7D5357EC3AD3B8777";
189 1 : const char fpr_unlisted[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
190 1 : const int guardfraction_value = 42;
191 :
192 1 : (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 1 : vrs_guard = gen_vote_routerstatus_for_tests(fpr_guard, 1);
201 1 : tt_assert(vrs_guard);
202 1 : smartlist_add(routerstatuses, vrs_guard);
203 :
204 : /* This one is a guard but it's not in the guardfraction file */
205 1 : vrs_dummy = gen_vote_routerstatus_for_tests(fpr_unlisted, 1);
206 1 : tt_assert(vrs_dummy);
207 1 : smartlist_add(routerstatuses, vrs_dummy);
208 : }
209 :
210 1 : 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 1 : retval = dirserv_read_guardfraction_file_from_str(guardfraction_good,
220 : routerstatuses);
221 1 : 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 1 : tt_assert(vrs_guard->status.has_guardfraction);
227 1 : 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 1 : tt_assert(!vrs_dummy->status.has_guardfraction);
234 : }
235 :
236 1 : done:
237 1 : vote_routerstatus_free(vrs_guard);
238 1 : vote_routerstatus_free(vrs_dummy);
239 1 : smartlist_free(routerstatuses);
240 1 : tor_free(guardfraction_good);
241 1 : }
242 :
243 : /** Make sure that the guardfraction bandwidths get calculated properly. */
244 : static void
245 1 : test_get_guardfraction_bandwidth(void *arg)
246 : {
247 1 : guardfraction_bandwidth_t gf_bw;
248 1 : const int orig_bw = 1000;
249 :
250 1 : (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 1 : guard_get_guardfraction_bandwidth(&gf_bw,
255 : orig_bw, 25);
256 :
257 1 : tt_int_op(gf_bw.guard_bw, OP_EQ, 250);
258 1 : 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 1 : tt_int_op(gf_bw.non_guard_bw + gf_bw.guard_bw, OP_EQ, orig_bw);
263 :
264 1 : done:
265 1 : ;
266 1 : }
267 :
268 : /** Parse the GuardFraction element of the consensus, and make sure it
269 : * gets parsed correctly. */
270 : static void
271 1 : test_parse_guardfraction_consensus(void *arg)
272 : {
273 1 : int retval;
274 1 : or_options_t *options = get_options_mutable();
275 :
276 1 : const char *guardfraction_str_good = "GuardFraction=66";
277 1 : routerstatus_t rs_good;
278 1 : routerstatus_t rs_no_guard;
279 :
280 1 : const char *guardfraction_str_bad1 = "GuardFraction="; /* no value */
281 1 : routerstatus_t rs_bad1;
282 :
283 1 : const char *guardfraction_str_bad2 = "GuardFraction=166"; /* no percentage */
284 1 : routerstatus_t rs_bad2;
285 :
286 1 : (void) arg;
287 :
288 : /* GuardFraction use is currently disabled by default. So we need to
289 : manually enable it. */
290 1 : options->UseGuardFraction = 1;
291 :
292 : { /* Properly formatted GuardFraction. Check that it gets applied
293 : correctly. */
294 1 : memset(&rs_good, 0, sizeof(routerstatus_t));
295 1 : rs_good.is_possible_guard = 1;
296 :
297 1 : retval = routerstatus_parse_guardfraction(guardfraction_str_good,
298 : NULL, NULL,
299 : &rs_good);
300 1 : tt_int_op(retval, OP_EQ, 0);
301 1 : tt_assert(rs_good.has_guardfraction);
302 1 : 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 1 : memset(&rs_no_guard, 0, sizeof(routerstatus_t));
308 1 : tt_assert(!rs_no_guard.is_possible_guard);
309 :
310 1 : setup_full_capture_of_logs(LOG_WARN);
311 1 : retval = routerstatus_parse_guardfraction(guardfraction_str_good,
312 : NULL, NULL,
313 : &rs_no_guard);
314 1 : tt_int_op(retval, OP_EQ, 0);
315 1 : tt_assert(!rs_no_guard.has_guardfraction);
316 1 : expect_single_log_msg_containing("Got GuardFraction for non-guard . "
317 : "This is not supposed to happen.");
318 1 : teardown_capture_of_logs();
319 : }
320 :
321 : { /* Bad GuardFraction. Function should fail and not apply. */
322 1 : memset(&rs_bad1, 0, sizeof(routerstatus_t));
323 1 : rs_bad1.is_possible_guard = 1;
324 :
325 1 : retval = routerstatus_parse_guardfraction(guardfraction_str_bad1,
326 : NULL, NULL,
327 : &rs_bad1);
328 1 : tt_int_op(retval, OP_EQ, -1);
329 1 : tt_assert(!rs_bad1.has_guardfraction);
330 : }
331 :
332 : { /* Bad GuardFraction. Function should fail and not apply. */
333 1 : memset(&rs_bad2, 0, sizeof(routerstatus_t));
334 1 : rs_bad2.is_possible_guard = 1;
335 :
336 1 : retval = routerstatus_parse_guardfraction(guardfraction_str_bad2,
337 : NULL, NULL,
338 : &rs_bad2);
339 1 : tt_int_op(retval, OP_EQ, -1);
340 1 : tt_assert(!rs_bad2.has_guardfraction);
341 : }
342 :
343 1 : done:
344 1 : teardown_capture_of_logs();
345 1 : }
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 1 : test_should_apply_guardfraction(void *arg)
351 : {
352 1 : networkstatus_t vote_enabled, vote_disabled, vote_missing;
353 1 : or_options_t *options = get_options_mutable();
354 :
355 1 : (void) arg;
356 :
357 : { /* Fill the votes for later */
358 : /* This one suggests enabled GuardFraction. */
359 1 : memset(&vote_enabled, 0, sizeof(vote_enabled));
360 1 : vote_enabled.net_params = smartlist_new();
361 1 : smartlist_split_string(vote_enabled.net_params,
362 : "UseGuardFraction=1", NULL, 0, 0);
363 :
364 : /* This one suggests disabled GuardFraction. */
365 1 : memset(&vote_disabled, 0, sizeof(vote_disabled));
366 1 : vote_disabled.net_params = smartlist_new();
367 1 : smartlist_split_string(vote_disabled.net_params,
368 : "UseGuardFraction=0", NULL, 0, 0);
369 :
370 : /* This one doesn't have GuardFraction at all. */
371 1 : memset(&vote_missing, 0, sizeof(vote_missing));
372 1 : vote_missing.net_params = smartlist_new();
373 1 : 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 1 : options->UseGuardFraction = 1;
380 1 : 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 1 : options->UseGuardFraction = 0;
385 1 : tt_int_op(should_apply_guardfraction(&vote_enabled), OP_EQ, 0);
386 :
387 : /* Now let's test torrc option set to auto. */
388 1 : 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 1 : 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 1 : 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 1 : tt_int_op(should_apply_guardfraction(&vote_missing), OP_EQ, 0);
401 :
402 1 : done:
403 2 : SMARTLIST_FOREACH(vote_enabled.net_params, char *, cp, tor_free(cp));
404 2 : SMARTLIST_FOREACH(vote_disabled.net_params, char *, cp, tor_free(cp));
405 2 : SMARTLIST_FOREACH(vote_missing.net_params, char *, cp, tor_free(cp));
406 1 : smartlist_free(vote_enabled.net_params);
407 1 : smartlist_free(vote_disabled.net_params);
408 1 : smartlist_free(vote_missing.net_params);
409 1 : }
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 : };
|