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 */
8 * \brief Code to read and apply guard fraction data.
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
26 * Return 1 if we applied the information and 0 if we couldn't find a
29 * Requires that <b>vote_routerstatuses</b> be sorted.
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
);
47 vrs
->status
.has_guardfraction
= 1;
48 vrs
->status
.guardfraction_percentage
= guardfraction_percentage
;
53 /* Given a guard line from a guardfraction file, parse it and register
54 * its information to <b>vote_routerstatuses</b>.
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.
64 guardfraction_file_parse_guard_line(const char *guard_line
,
65 smartlist_t
*vote_routerstatuses
,
68 char guard_id
[DIGEST_LEN
];
69 uint32_t guardfraction
;
70 char *inputs_tmp
= NULL
;
73 smartlist_t
*sl
= smartlist_new();
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
);
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
);
95 inputs_tmp
= smartlist_get(sl
, 1);
96 /* Guardfraction is an integer in [0, 100]. */
98 (uint32_t) tor_parse_long(inputs_tmp
, 10, 0, 100, &num_ok
, NULL
);
100 tor_asprintf(err_msg
, "wrong percentage '%s'", inputs_tmp
);
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
);
109 retval
= 0; /* If we got this far, line was correctly formatted. */
114 SMARTLIST_FOREACH(sl
, char *, cp
, tor_free(cp
));
120 /** Given an inputs line from a guardfraction file, parse it and
121 * register its information to <b>total_consensuses</b> and
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
129 guardfraction_file_parse_inputs_line(const char *inputs_line
,
130 int *total_consensuses
,
135 char *inputs_tmp
= NULL
;
137 smartlist_t
*sl
= smartlist_new();
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
);
150 inputs_tmp
= smartlist_get(sl
, 0);
152 (int) tor_parse_long(inputs_tmp
, 10, 0, INT_MAX
, &num_ok
, NULL
);
154 tor_asprintf(err_msg
, "unparseable consensus '%s'", inputs_tmp
);
158 inputs_tmp
= smartlist_get(sl
, 1);
160 (int) tor_parse_long(inputs_tmp
, 10, 0, INT_MAX
, &num_ok
, NULL
);
162 tor_asprintf(err_msg
, "unparseable days '%s'", inputs_tmp
);
169 SMARTLIST_FOREACH(sl
, char *, cp
, tor_free(cp
));
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>
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.
204 dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str
,
205 smartlist_t
*vote_routerstatuses
)
207 config_line_t
*front
=NULL
, *line
;
210 int current_line_n
= 0; /* line counter for better log messages */
212 /* Guardfraction info to be parsed */
213 int total_consensuses
= 0;
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);
223 log_warn(LD_CONFIG
, "Error reading from guardfraction file");
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
) {
234 if (!strcmp(line
->key
, GUARDFRACTION_VERSION
)) {
236 unsigned int 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
);
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
,
268 log_warn(LD_CONFIG
, "Guardfraction:%d: %s",
269 current_line_n
, err_msg
);
274 } else if (!strcmp(line
->key
, GUARDFRACTION_GUARD
)) {
275 char *err_msg
= NULL
;
277 ret_tmp
= guardfraction_file_parse_guard_line(line
->value
,
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
);
287 /* Successfully parsed guard line. Check if it was applied properly. */
293 log_warn(LD_CONFIG
, "Unknown guardfraction line %d (%s %s)",
294 current_line_n
, line
->key
, line
->value
);
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)" );
307 config_free_lines(front
);
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
);
331 return dirserv_read_guardfraction_file_from_str(guardfraction_file_str
,
332 vote_routerstatuses
);