LCOV - code coverage report
Current view: top level - feature/dirauth - guardfraction.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 115 127 90.6 %
Date: 2021-11-24 03:28:48 Functions: 5 5 100.0 %

          Line data    Source code
       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           1 : guardfraction_line_apply(const char *guard_id,
      33             :                       uint32_t guardfraction_percentage,
      34             :                       smartlist_t *vote_routerstatuses)
      35             : {
      36           1 :   vote_routerstatus_t *vrs = NULL;
      37             : 
      38           1 :   tor_assert(vote_routerstatuses);
      39             : 
      40           1 :   vrs = smartlist_bsearch(vote_routerstatuses, guard_id,
      41             :                          compare_digest_to_vote_routerstatus_entry);
      42             : 
      43           1 :   if (!vrs) {
      44             :     return 0;
      45             :   }
      46             : 
      47           1 :   vrs->status.has_guardfraction = 1;
      48           1 :   vrs->status.guardfraction_percentage = guardfraction_percentage;
      49             : 
      50           1 :   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           9 : guardfraction_file_parse_guard_line(const char *guard_line,
      65             :                                     smartlist_t *vote_routerstatuses,
      66             :                                     char **err_msg)
      67             : {
      68           9 :   char guard_id[DIGEST_LEN];
      69           9 :   uint32_t guardfraction;
      70           9 :   char *inputs_tmp = NULL;
      71           9 :   int num_ok = 1;
      72             : 
      73           9 :   smartlist_t *sl = smartlist_new();
      74           9 :   int retval = -1;
      75             : 
      76           9 :   tor_assert(err_msg);
      77             : 
      78             :   /* guard_line should contain something like this:
      79             :      <hex digest> <guardfraction> <appearances> */
      80           9 :   smartlist_split_string(sl, guard_line, " ",
      81             :                          SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
      82           9 :   if (smartlist_len(sl) < 3) {
      83           0 :     tor_asprintf(err_msg, "bad line '%s'", guard_line);
      84           0 :     goto done;
      85             :   }
      86             : 
      87           9 :   inputs_tmp = smartlist_get(sl, 0);
      88          17 :   if (strlen(inputs_tmp) != HEX_DIGEST_LEN ||
      89           8 :       base16_decode(guard_id, DIGEST_LEN,
      90             :                     inputs_tmp, HEX_DIGEST_LEN) != DIGEST_LEN) {
      91           1 :     tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp);
      92           1 :     goto done;
      93             :   }
      94             : 
      95           8 :   inputs_tmp = smartlist_get(sl, 1);
      96             :   /* Guardfraction is an integer in [0, 100]. */
      97          16 :   guardfraction =
      98           8 :     (uint32_t) tor_parse_long(inputs_tmp, 10, 0, 100, &num_ok, NULL);
      99           8 :   if (!num_ok) {
     100           3 :     tor_asprintf(err_msg, "wrong percentage '%s'", inputs_tmp);
     101           3 :     goto done;
     102             :   }
     103             : 
     104             :   /* If routerstatuses were provided, apply this info to actual routers. */
     105           5 :   if (vote_routerstatuses) {
     106           1 :     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           9 :  done:
     113             : 
     114          36 :   SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
     115           9 :   smartlist_free(sl);
     116             : 
     117           9 :   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           6 : guardfraction_file_parse_inputs_line(const char *inputs_line,
     130             :                                      int *total_consensuses,
     131             :                                      int *total_days,
     132             :                                      char **err_msg)
     133             : {
     134           6 :   int retval = -1;
     135           6 :   char *inputs_tmp = NULL;
     136           6 :   int num_ok = 1;
     137           6 :   smartlist_t *sl = smartlist_new();
     138             : 
     139           6 :   tor_assert(err_msg);
     140             : 
     141             :   /* Second line is inputs information:
     142             :    *   n-inputs <total_consensuses> <total_days>. */
     143           6 :   smartlist_split_string(sl, inputs_line, " ",
     144             :                          SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
     145           6 :   if (smartlist_len(sl) < 2) {
     146           1 :     tor_asprintf(err_msg, "incomplete line '%s'", inputs_line);
     147           1 :     goto done;
     148             :   }
     149             : 
     150           5 :   inputs_tmp = smartlist_get(sl, 0);
     151          10 :   *total_consensuses =
     152           5 :     (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
     153           5 :   if (!num_ok) {
     154           0 :     tor_asprintf(err_msg, "unparseable consensus '%s'", inputs_tmp);
     155           0 :     goto done;
     156             :   }
     157             : 
     158           5 :   inputs_tmp = smartlist_get(sl, 1);
     159          10 :   *total_days =
     160           5 :     (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
     161           5 :   if (!num_ok) {
     162           0 :     tor_asprintf(err_msg, "unparseable days '%s'", inputs_tmp);
     163           0 :     goto done;
     164             :   }
     165             : 
     166             :   retval = 0;
     167             : 
     168           6 :  done:
     169          17 :   SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
     170           6 :   smartlist_free(sl);
     171             : 
     172           6 :   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           8 : dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
     205             :                                       smartlist_t *vote_routerstatuses)
     206             : {
     207           8 :   config_line_t *front=NULL, *line;
     208           8 :   int ret_tmp;
     209           8 :   int retval = -1;
     210           8 :   int current_line_n = 0; /* line counter for better log messages */
     211             : 
     212             :   /* Guardfraction info to be parsed */
     213           8 :   int total_consensuses = 0;
     214           8 :   int total_days = 0;
     215             : 
     216             :   /* Stats */
     217           8 :   int guards_read_n = 0;
     218           8 :   int guards_applied_n = 0;
     219             : 
     220             :   /* Parse file and split it in lines */
     221           8 :   ret_tmp = config_get_lines(guardfraction_file_str, &front, 0);
     222           8 :   if (ret_tmp < 0) {
     223           0 :     log_warn(LD_CONFIG, "Error reading from guardfraction file");
     224           0 :     goto done;
     225             :   }
     226             : 
     227             :   /* Sort routerstatuses (needed later when applying guardfraction info) */
     228           8 :   if (vote_routerstatuses)
     229           1 :     smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries);
     230             : 
     231          36 :   for (line = front; line; line=line->next) {
     232          30 :     current_line_n++;
     233             : 
     234          30 :     if (!strcmp(line->key, GUARDFRACTION_VERSION)) {
     235           8 :       int num_ok = 1;
     236           8 :       unsigned int version;
     237             : 
     238          16 :       version =
     239           8 :         (unsigned int) tor_parse_long(line->value,
     240             :                                       10, 0, INT_MAX, &num_ok, NULL);
     241             : 
     242           8 :       if (!num_ok || version != 1) {
     243           1 :         log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version);
     244           1 :         goto done;
     245             :       }
     246          22 :     } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) {
     247           7 :       time_t file_written_at;
     248           7 :       time_t now = time(NULL);
     249             : 
     250             :       /* First line is 'written-at <date>' */
     251           7 :       if (parse_iso_time(line->value, &file_written_at) < 0) {
     252           1 :         log_warn(LD_CONFIG, "Guardfraction:%d: Bad date '%s'. Ignoring",
     253             :                  current_line_n, line->value);
     254           1 :         goto done; /* don't tolerate failure here. */
     255             :       }
     256           6 :       if (file_written_at < now - MAX_GUARDFRACTION_FILE_AGE) {
     257           0 :         log_warn(LD_CONFIG, "Guardfraction:%d: was written very long ago '%s'",
     258             :                  current_line_n, line->value);
     259           0 :         goto done; /* don't tolerate failure here. */
     260             :       }
     261          15 :     } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) {
     262           6 :       char *err_msg = NULL;
     263             : 
     264           6 :       if (guardfraction_file_parse_inputs_line(line->value,
     265             :                                                &total_consensuses,
     266             :                                                &total_days,
     267             :                                                &err_msg) < 0) {
     268           1 :         log_warn(LD_CONFIG, "Guardfraction:%d: %s",
     269             :                  current_line_n, err_msg);
     270           1 :         tor_free(err_msg);
     271           1 :         continue;
     272             :       }
     273             : 
     274           9 :     } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) {
     275           9 :       char *err_msg = NULL;
     276             : 
     277           9 :       ret_tmp = guardfraction_file_parse_guard_line(line->value,
     278             :                                                     vote_routerstatuses,
     279             :                                                     &err_msg);
     280           9 :       if (ret_tmp < 0) { /* failed while parsing the guard line */
     281           4 :         log_warn(LD_CONFIG, "Guardfraction:%d: %s",
     282             :                  current_line_n, err_msg);
     283           4 :         tor_free(err_msg);
     284           4 :         continue;
     285             :       }
     286             : 
     287             :       /* Successfully parsed guard line. Check if it was applied properly. */
     288           5 :       guards_read_n++;
     289           5 :       if (ret_tmp > 0) {
     290           1 :         guards_applied_n++;
     291             :       }
     292             :     } else {
     293           0 :       log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)",
     294             :                current_line_n, line->key, line->value);
     295             :     }
     296             :   }
     297             : 
     298           6 :   retval = 0;
     299             : 
     300          11 :   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           8 :  done:
     307           8 :   config_free_lines(front);
     308             : 
     309           8 :   if (retval < 0) {
     310             :     return retval;
     311             :   } else {
     312           6 :     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           1 : dirserv_read_guardfraction_file(const char *fname,
     320             :                              smartlist_t *vote_routerstatuses)
     321             : {
     322           1 :   char *guardfraction_file_str;
     323             : 
     324             :   /* Read file to a string */
     325           1 :   guardfraction_file_str = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
     326           1 :   if (!guardfraction_file_str) {
     327           1 :       log_warn(LD_FS, "Cannot open guardfraction file '%s'. Failing.", fname);
     328           1 :       return -1;
     329             :   }
     330             : 
     331           0 :   return dirserv_read_guardfraction_file_from_str(guardfraction_file_str,
     332             :                                                vote_routerstatuses);
     333             : }

Generated by: LCOV version 1.14