tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

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 }