guardfraction.c (10827B)
1 /* Copyright (c) 2001-2004, Roger Dingledine. 2 * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. 3 * Copyright (c) 2007-2021, The Tor Project, Inc. */ 4 /* See LICENSE for licensing information */ 5 6 /** 7 * \file bwauth.c 8 * \brief Code to read and apply guard fraction data. 9 **/ 10 11 #define GUARDFRACTION_PRIVATE 12 #include "core/or/or.h" 13 #include "feature/dirauth/guardfraction.h" 14 #include "feature/nodelist/networkstatus.h" 15 #include "feature/dirparse/ns_parse.h" 16 17 #include "feature/nodelist/vote_routerstatus_st.h" 18 19 #include "lib/encoding/confline.h" 20 21 /** The guardfraction of the guard with identity fingerprint <b>guard_id</b> 22 * is <b>guardfraction_percentage</b>. See if we have a vote routerstatus for 23 * this guard in <b>vote_routerstatuses</b>, and if we do, register the 24 * information to it. 25 * 26 * Return 1 if we applied the information and 0 if we couldn't find a 27 * matching guard. 28 * 29 * Requires that <b>vote_routerstatuses</b> be sorted. 30 */ 31 static int 32 guardfraction_line_apply(const char *guard_id, 33 uint32_t guardfraction_percentage, 34 smartlist_t *vote_routerstatuses) 35 { 36 vote_routerstatus_t *vrs = NULL; 37 38 tor_assert(vote_routerstatuses); 39 40 vrs = smartlist_bsearch(vote_routerstatuses, guard_id, 41 compare_digest_to_vote_routerstatus_entry); 42 43 if (!vrs) { 44 return 0; 45 } 46 47 vrs->status.has_guardfraction = 1; 48 vrs->status.guardfraction_percentage = guardfraction_percentage; 49 50 return 1; 51 } 52 53 /* Given a guard line from a guardfraction file, parse it and register 54 * its information to <b>vote_routerstatuses</b>. 55 * 56 * Return: 57 * * 1 if the line was proper and its information got registered. 58 * * 0 if the line was proper but no currently active guard was found 59 * to register the guardfraction information to. 60 * * -1 if the line could not be parsed and set <b>err_msg</b> to a 61 newly allocated string containing the error message. 62 */ 63 static int 64 guardfraction_file_parse_guard_line(const char *guard_line, 65 smartlist_t *vote_routerstatuses, 66 char **err_msg) 67 { 68 char guard_id[DIGEST_LEN]; 69 uint32_t guardfraction; 70 char *inputs_tmp = NULL; 71 int num_ok = 1; 72 73 smartlist_t *sl = smartlist_new(); 74 int retval = -1; 75 76 tor_assert(err_msg); 77 78 /* guard_line should contain something like this: 79 <hex digest> <guardfraction> <appearances> */ 80 smartlist_split_string(sl, guard_line, " ", 81 SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3); 82 if (smartlist_len(sl) < 3) { 83 tor_asprintf(err_msg, "bad line '%s'", guard_line); 84 goto done; 85 } 86 87 inputs_tmp = smartlist_get(sl, 0); 88 if (strlen(inputs_tmp) != HEX_DIGEST_LEN || 89 base16_decode(guard_id, DIGEST_LEN, 90 inputs_tmp, HEX_DIGEST_LEN) != DIGEST_LEN) { 91 tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp); 92 goto done; 93 } 94 95 inputs_tmp = smartlist_get(sl, 1); 96 /* Guardfraction is an integer in [0, 100]. */ 97 guardfraction = 98 (uint32_t) tor_parse_long(inputs_tmp, 10, 0, 100, &num_ok, NULL); 99 if (!num_ok) { 100 tor_asprintf(err_msg, "wrong percentage '%s'", inputs_tmp); 101 goto done; 102 } 103 104 /* If routerstatuses were provided, apply this info to actual routers. */ 105 if (vote_routerstatuses) { 106 retval = guardfraction_line_apply(guard_id, guardfraction, 107 vote_routerstatuses); 108 } else { 109 retval = 0; /* If we got this far, line was correctly formatted. */ 110 } 111 112 done: 113 114 SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); 115 smartlist_free(sl); 116 117 return retval; 118 } 119 120 /** Given an inputs line from a guardfraction file, parse it and 121 * register its information to <b>total_consensuses</b> and 122 * <b>total_days</b>. 123 * 124 * Return 0 if it parsed well. Return -1 if there was an error, and 125 * set <b>err_msg</b> to a newly allocated string containing the 126 * error message. 127 */ 128 static int 129 guardfraction_file_parse_inputs_line(const char *inputs_line, 130 int *total_consensuses, 131 int *total_days, 132 char **err_msg) 133 { 134 int retval = -1; 135 char *inputs_tmp = NULL; 136 int num_ok = 1; 137 smartlist_t *sl = smartlist_new(); 138 139 tor_assert(err_msg); 140 141 /* Second line is inputs information: 142 * n-inputs <total_consensuses> <total_days>. */ 143 smartlist_split_string(sl, inputs_line, " ", 144 SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3); 145 if (smartlist_len(sl) < 2) { 146 tor_asprintf(err_msg, "incomplete line '%s'", inputs_line); 147 goto done; 148 } 149 150 inputs_tmp = smartlist_get(sl, 0); 151 *total_consensuses = 152 (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL); 153 if (!num_ok) { 154 tor_asprintf(err_msg, "unparseable consensus '%s'", inputs_tmp); 155 goto done; 156 } 157 158 inputs_tmp = smartlist_get(sl, 1); 159 *total_days = 160 (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL); 161 if (!num_ok) { 162 tor_asprintf(err_msg, "unparseable days '%s'", inputs_tmp); 163 goto done; 164 } 165 166 retval = 0; 167 168 done: 169 SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp)); 170 smartlist_free(sl); 171 172 return retval; 173 } 174 175 /* Maximum age of a guardfraction file that we are willing to accept. */ 176 #define MAX_GUARDFRACTION_FILE_AGE (7*24*60*60) /* approx a week */ 177 178 /** Static strings of guardfraction files. */ 179 #define GUARDFRACTION_DATE_STR "written-at" 180 #define GUARDFRACTION_INPUTS "n-inputs" 181 #define GUARDFRACTION_GUARD "guard-seen" 182 #define GUARDFRACTION_VERSION "guardfraction-file-version" 183 184 /** Given a guardfraction file in a string, parse it and register the 185 * guardfraction information to the provided vote routerstatuses. 186 * 187 * This is the rough format of the guardfraction file: 188 * 189 * guardfraction-file-version 1 190 * written-at <date and time> 191 * n-inputs <number of consensuses parsed> <number of days considered> 192 * 193 * guard-seen <fpr 1> <guardfraction percentage> <consensus appearances> 194 * guard-seen <fpr 2> <guardfraction percentage> <consensus appearances> 195 * guard-seen <fpr 3> <guardfraction percentage> <consensus appearances> 196 * guard-seen <fpr 4> <guardfraction percentage> <consensus appearances> 197 * guard-seen <fpr 5> <guardfraction percentage> <consensus appearances> 198 * ... 199 * 200 * Return -1 if the parsing failed and 0 if it went smoothly. Parsing 201 * should tolerate errors in all lines but the written-at header. 202 */ 203 STATIC int 204 dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str, 205 smartlist_t *vote_routerstatuses) 206 { 207 config_line_t *front=NULL, *line; 208 int ret_tmp; 209 int retval = -1; 210 int current_line_n = 0; /* line counter for better log messages */ 211 212 /* Guardfraction info to be parsed */ 213 int total_consensuses = 0; 214 int total_days = 0; 215 216 /* Stats */ 217 int guards_read_n = 0; 218 int guards_applied_n = 0; 219 220 /* Parse file and split it in lines */ 221 ret_tmp = config_get_lines(guardfraction_file_str, &front, 0); 222 if (ret_tmp < 0) { 223 log_warn(LD_CONFIG, "Error reading from guardfraction file"); 224 goto done; 225 } 226 227 /* Sort routerstatuses (needed later when applying guardfraction info) */ 228 if (vote_routerstatuses) 229 smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries); 230 231 for (line = front; line; line=line->next) { 232 current_line_n++; 233 234 if (!strcmp(line->key, GUARDFRACTION_VERSION)) { 235 int num_ok = 1; 236 unsigned int version; 237 238 version = 239 (unsigned int) tor_parse_long(line->value, 240 10, 0, INT_MAX, &num_ok, NULL); 241 242 if (!num_ok || version != 1) { 243 log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version); 244 goto done; 245 } 246 } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) { 247 time_t file_written_at; 248 time_t now = time(NULL); 249 250 /* First line is 'written-at <date>' */ 251 if (parse_iso_time(line->value, &file_written_at) < 0) { 252 log_warn(LD_CONFIG, "Guardfraction:%d: Bad date '%s'. Ignoring", 253 current_line_n, line->value); 254 goto done; /* don't tolerate failure here. */ 255 } 256 if (file_written_at < now - MAX_GUARDFRACTION_FILE_AGE) { 257 log_warn(LD_CONFIG, "Guardfraction:%d: was written very long ago '%s'", 258 current_line_n, line->value); 259 goto done; /* don't tolerate failure here. */ 260 } 261 } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) { 262 char *err_msg = NULL; 263 264 if (guardfraction_file_parse_inputs_line(line->value, 265 &total_consensuses, 266 &total_days, 267 &err_msg) < 0) { 268 log_warn(LD_CONFIG, "Guardfraction:%d: %s", 269 current_line_n, err_msg); 270 tor_free(err_msg); 271 continue; 272 } 273 274 } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) { 275 char *err_msg = NULL; 276 277 ret_tmp = guardfraction_file_parse_guard_line(line->value, 278 vote_routerstatuses, 279 &err_msg); 280 if (ret_tmp < 0) { /* failed while parsing the guard line */ 281 log_warn(LD_CONFIG, "Guardfraction:%d: %s", 282 current_line_n, err_msg); 283 tor_free(err_msg); 284 continue; 285 } 286 287 /* Successfully parsed guard line. Check if it was applied properly. */ 288 guards_read_n++; 289 if (ret_tmp > 0) { 290 guards_applied_n++; 291 } 292 } else { 293 log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)", 294 current_line_n, line->key, line->value); 295 } 296 } 297 298 retval = 0; 299 300 log_info(LD_CONFIG, 301 "Successfully parsed guardfraction file with %d consensuses over " 302 "%d days. Parsed %d nodes and applied %d of them%s.", 303 total_consensuses, total_days, guards_read_n, guards_applied_n, 304 vote_routerstatuses ? "" : " (no routerstatus provided)" ); 305 306 done: 307 config_free_lines(front); 308 309 if (retval < 0) { 310 return retval; 311 } else { 312 return guards_read_n; 313 } 314 } 315 316 /** Read a guardfraction file at <b>fname</b> and load all its 317 * information to <b>vote_routerstatuses</b>. */ 318 int 319 dirserv_read_guardfraction_file(const char *fname, 320 smartlist_t *vote_routerstatuses) 321 { 322 char *guardfraction_file_str; 323 324 /* Read file to a string */ 325 guardfraction_file_str = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL); 326 if (!guardfraction_file_str) { 327 log_warn(LD_FS, "Cannot open guardfraction file '%s'. Failing.", fname); 328 return -1; 329 } 330 331 return dirserv_read_guardfraction_file_from_str(guardfraction_file_str, 332 vote_routerstatuses); 333 }