Merge branch 'maint-0.4.8' into release-0.4.8
[tor.git] / src / feature / dirauth / guardfraction.c
blob98ea04f6435ea978212ae5ec3d587a2c968b8438
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 */
6 /**
7 * \file bwauth.c
8 * \brief Code to read and apply guard fraction data.
9 **/
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"
17 #include "feature/nodelist/vote_routerstatus_st.h"
19 #include "lib/encoding/confline.h"
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.
26 * Return 1 if we applied the information and 0 if we couldn't find a
27 * matching guard.
29 * Requires that <b>vote_routerstatuses</b> be sorted.
31 static int
32 guardfraction_line_apply(const char *guard_id,
33 uint32_t guardfraction_percentage,
34 smartlist_t *vote_routerstatuses)
36 vote_routerstatus_t *vrs = NULL;
38 tor_assert(vote_routerstatuses);
40 vrs = smartlist_bsearch(vote_routerstatuses, guard_id,
41 compare_digest_to_vote_routerstatus_entry);
43 if (!vrs) {
44 return 0;
47 vrs->status.has_guardfraction = 1;
48 vrs->status.guardfraction_percentage = guardfraction_percentage;
50 return 1;
53 /* Given a guard line from a guardfraction file, parse it and register
54 * its information to <b>vote_routerstatuses</b>.
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.
63 static int
64 guardfraction_file_parse_guard_line(const char *guard_line,
65 smartlist_t *vote_routerstatuses,
66 char **err_msg)
68 char guard_id[DIGEST_LEN];
69 uint32_t guardfraction;
70 char *inputs_tmp = NULL;
71 int num_ok = 1;
73 smartlist_t *sl = smartlist_new();
74 int retval = -1;
76 tor_assert(err_msg);
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;
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;
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;
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. */
112 done:
114 SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
115 smartlist_free(sl);
117 return retval;
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>.
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.
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)
134 int retval = -1;
135 char *inputs_tmp = NULL;
136 int num_ok = 1;
137 smartlist_t *sl = smartlist_new();
139 tor_assert(err_msg);
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;
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;
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;
166 retval = 0;
168 done:
169 SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
170 smartlist_free(sl);
172 return retval;
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 */
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"
184 /** Given a guardfraction file in a string, parse it and register the
185 * guardfraction information to the provided vote routerstatuses.
187 * This is the rough format of the guardfraction file:
189 * guardfraction-file-version 1
190 * written-at <date and time>
191 * n-inputs <number of consensuses parsed> <number of days considered>
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 * ...
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.
203 STATIC int
204 dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
205 smartlist_t *vote_routerstatuses)
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 */
212 /* Guardfraction info to be parsed */
213 int total_consensuses = 0;
214 int total_days = 0;
216 /* Stats */
217 int guards_read_n = 0;
218 int guards_applied_n = 0;
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;
227 /* Sort routerstatuses (needed later when applying guardfraction info) */
228 if (vote_routerstatuses)
229 smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries);
231 for (line = front; line; line=line->next) {
232 current_line_n++;
234 if (!strcmp(line->key, GUARDFRACTION_VERSION)) {
235 int num_ok = 1;
236 unsigned int version;
238 version =
239 (unsigned int) tor_parse_long(line->value,
240 10, 0, INT_MAX, &num_ok, NULL);
242 if (!num_ok || version != 1) {
243 log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version);
244 goto done;
246 } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) {
247 time_t file_written_at;
248 time_t now = time(NULL);
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. */
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. */
261 } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) {
262 char *err_msg = NULL;
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;
274 } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) {
275 char *err_msg = NULL;
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;
287 /* Successfully parsed guard line. Check if it was applied properly. */
288 guards_read_n++;
289 if (ret_tmp > 0) {
290 guards_applied_n++;
292 } else {
293 log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)",
294 current_line_n, line->key, line->value);
298 retval = 0;
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)" );
306 done:
307 config_free_lines(front);
309 if (retval < 0) {
310 return retval;
311 } else {
312 return guards_read_n;
316 /** Read a guardfraction file at <b>fname</b> and load all its
317 * information to <b>vote_routerstatuses</b>. */
319 dirserv_read_guardfraction_file(const char *fname,
320 smartlist_t *vote_routerstatuses)
322 char *guardfraction_file_str;
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;
331 return dirserv_read_guardfraction_file_from_str(guardfraction_file_str,
332 vote_routerstatuses);