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 : }
|