Tor  0.4.7.0-alpha-dev
guardfraction.c
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"
16 
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,
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
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 }
int base16_decode(char *dest, size_t destlen, const char *src, size_t srclen)
Definition: binascii.c:506
int config_get_lines(const char *string, config_line_t **result, int extended)
Definition: confline.c:200
Header for confline.c.
#define HEX_DIGEST_LEN
Definition: crypto_digest.h:35
#define DIGEST_LEN
Definition: digest_sizes.h:20
#define RFTS_IGNORE_MISSING
Definition: files.h:101
Header file for guardfraction.c.
int dirserv_read_guardfraction_file(const char *fname, smartlist_t *vote_routerstatuses)
#define LD_FS
Definition: log.h:70
#define LD_GENERAL
Definition: log.h:62
#define LD_CONFIG
Definition: log.h:68
#define tor_free(p)
Definition: malloc.h:52
int compare_digest_to_vote_routerstatus_entry(const void *_key, const void **_member)
Header file for networkstatus.c.
Header file for ns_parse.c.
Master header file for Tor-specific functionality.
long tor_parse_long(const char *s, int base, long min, long max, int *ok, char **next)
Definition: parse_int.c:59
int tor_asprintf(char **strp, const char *fmt,...)
Definition: printf.c:75
void * smartlist_bsearch(const smartlist_t *sl, const void *key, int(*compare)(const void *key, const void **member))
Definition: smartlist.c:411
void smartlist_sort(smartlist_t *sl, int(*compare)(const void **a, const void **b))
Definition: smartlist.c:334
smartlist_t * smartlist_new(void)
#define SMARTLIST_FOREACH(sl, type, var, cmd)
int smartlist_split_string(smartlist_t *sl, const char *str, const char *sep, int flags, int max)
unsigned int has_guardfraction
uint32_t guardfraction_percentage
#define STATIC
Definition: testsupport.h:32
int parse_iso_time(const char *cp, time_t *t)
Definition: time_fmt.c:392
#define tor_assert(expr)
Definition: util_bug.h:102
Routerstatus (vote entry) structure.