guardfraction.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333
  1. /* Copyright (c) 2001-2004, Roger Dingledine.
  2. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
  3. * Copyright (c) 2007-2019, The Tor Project, Inc. */
  4. /* See LICENSE for licensing information */
  5. /**
  6. * \file bwauth.c
  7. * \brief Code to read and apply guard fraction data.
  8. **/
  9. #define GUARDFRACTION_PRIVATE
  10. #include "core/or/or.h"
  11. #include "feature/dirauth/guardfraction.h"
  12. #include "feature/nodelist/networkstatus.h"
  13. #include "feature/dirparse/ns_parse.h"
  14. #include "feature/nodelist/vote_routerstatus_st.h"
  15. #include "lib/encoding/confline.h"
  16. /** The guardfraction of the guard with identity fingerprint <b>guard_id</b>
  17. * is <b>guardfraction_percentage</b>. See if we have a vote routerstatus for
  18. * this guard in <b>vote_routerstatuses</b>, and if we do, register the
  19. * information to it.
  20. *
  21. * Return 1 if we applied the information and 0 if we couldn't find a
  22. * matching guard.
  23. *
  24. * Requires that <b>vote_routerstatuses</b> be sorted.
  25. */
  26. static int
  27. guardfraction_line_apply(const char *guard_id,
  28. uint32_t guardfraction_percentage,
  29. smartlist_t *vote_routerstatuses)
  30. {
  31. vote_routerstatus_t *vrs = NULL;
  32. tor_assert(vote_routerstatuses);
  33. vrs = smartlist_bsearch(vote_routerstatuses, guard_id,
  34. compare_digest_to_vote_routerstatus_entry);
  35. if (!vrs) {
  36. return 0;
  37. }
  38. vrs->status.has_guardfraction = 1;
  39. vrs->status.guardfraction_percentage = guardfraction_percentage;
  40. return 1;
  41. }
  42. /* Given a guard line from a guardfraction file, parse it and register
  43. * its information to <b>vote_routerstatuses</b>.
  44. *
  45. * Return:
  46. * * 1 if the line was proper and its information got registered.
  47. * * 0 if the line was proper but no currently active guard was found
  48. * to register the guardfraction information to.
  49. * * -1 if the line could not be parsed and set <b>err_msg</b> to a
  50. newly allocated string containing the error message.
  51. */
  52. static int
  53. guardfraction_file_parse_guard_line(const char *guard_line,
  54. smartlist_t *vote_routerstatuses,
  55. char **err_msg)
  56. {
  57. char guard_id[DIGEST_LEN];
  58. uint32_t guardfraction;
  59. char *inputs_tmp = NULL;
  60. int num_ok = 1;
  61. smartlist_t *sl = smartlist_new();
  62. int retval = -1;
  63. tor_assert(err_msg);
  64. /* guard_line should contain something like this:
  65. <hex digest> <guardfraction> <appearances> */
  66. smartlist_split_string(sl, guard_line, " ",
  67. SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
  68. if (smartlist_len(sl) < 3) {
  69. tor_asprintf(err_msg, "bad line '%s'", guard_line);
  70. goto done;
  71. }
  72. inputs_tmp = smartlist_get(sl, 0);
  73. if (strlen(inputs_tmp) != HEX_DIGEST_LEN ||
  74. base16_decode(guard_id, DIGEST_LEN,
  75. inputs_tmp, HEX_DIGEST_LEN) != DIGEST_LEN) {
  76. tor_asprintf(err_msg, "bad digest '%s'", inputs_tmp);
  77. goto done;
  78. }
  79. inputs_tmp = smartlist_get(sl, 1);
  80. /* Guardfraction is an integer in [0, 100]. */
  81. guardfraction =
  82. (uint32_t) tor_parse_long(inputs_tmp, 10, 0, 100, &num_ok, NULL);
  83. if (!num_ok) {
  84. tor_asprintf(err_msg, "wrong percentage '%s'", inputs_tmp);
  85. goto done;
  86. }
  87. /* If routerstatuses were provided, apply this info to actual routers. */
  88. if (vote_routerstatuses) {
  89. retval = guardfraction_line_apply(guard_id, guardfraction,
  90. vote_routerstatuses);
  91. } else {
  92. retval = 0; /* If we got this far, line was correctly formatted. */
  93. }
  94. done:
  95. SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
  96. smartlist_free(sl);
  97. return retval;
  98. }
  99. /** Given an inputs line from a guardfraction file, parse it and
  100. * register its information to <b>total_consensuses</b> and
  101. * <b>total_days</b>.
  102. *
  103. * Return 0 if it parsed well. Return -1 if there was an error, and
  104. * set <b>err_msg</b> to a newly allocated string containing the
  105. * error message.
  106. */
  107. static int
  108. guardfraction_file_parse_inputs_line(const char *inputs_line,
  109. int *total_consensuses,
  110. int *total_days,
  111. char **err_msg)
  112. {
  113. int retval = -1;
  114. char *inputs_tmp = NULL;
  115. int num_ok = 1;
  116. smartlist_t *sl = smartlist_new();
  117. tor_assert(err_msg);
  118. /* Second line is inputs information:
  119. * n-inputs <total_consensuses> <total_days>. */
  120. smartlist_split_string(sl, inputs_line, " ",
  121. SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 3);
  122. if (smartlist_len(sl) < 2) {
  123. tor_asprintf(err_msg, "incomplete line '%s'", inputs_line);
  124. goto done;
  125. }
  126. inputs_tmp = smartlist_get(sl, 0);
  127. *total_consensuses =
  128. (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
  129. if (!num_ok) {
  130. tor_asprintf(err_msg, "unparseable consensus '%s'", inputs_tmp);
  131. goto done;
  132. }
  133. inputs_tmp = smartlist_get(sl, 1);
  134. *total_days =
  135. (int) tor_parse_long(inputs_tmp, 10, 0, INT_MAX, &num_ok, NULL);
  136. if (!num_ok) {
  137. tor_asprintf(err_msg, "unparseable days '%s'", inputs_tmp);
  138. goto done;
  139. }
  140. retval = 0;
  141. done:
  142. SMARTLIST_FOREACH(sl, char *, cp, tor_free(cp));
  143. smartlist_free(sl);
  144. return retval;
  145. }
  146. /* Maximum age of a guardfraction file that we are willing to accept. */
  147. #define MAX_GUARDFRACTION_FILE_AGE (7*24*60*60) /* approx a week */
  148. /** Static strings of guardfraction files. */
  149. #define GUARDFRACTION_DATE_STR "written-at"
  150. #define GUARDFRACTION_INPUTS "n-inputs"
  151. #define GUARDFRACTION_GUARD "guard-seen"
  152. #define GUARDFRACTION_VERSION "guardfraction-file-version"
  153. /** Given a guardfraction file in a string, parse it and register the
  154. * guardfraction information to the provided vote routerstatuses.
  155. *
  156. * This is the rough format of the guardfraction file:
  157. *
  158. * guardfraction-file-version 1
  159. * written-at <date and time>
  160. * n-inputs <number of consesuses parsed> <number of days considered>
  161. *
  162. * guard-seen <fpr 1> <guardfraction percentage> <consensus appearances>
  163. * guard-seen <fpr 2> <guardfraction percentage> <consensus appearances>
  164. * guard-seen <fpr 3> <guardfraction percentage> <consensus appearances>
  165. * guard-seen <fpr 4> <guardfraction percentage> <consensus appearances>
  166. * guard-seen <fpr 5> <guardfraction percentage> <consensus appearances>
  167. * ...
  168. *
  169. * Return -1 if the parsing failed and 0 if it went smoothly. Parsing
  170. * should tolerate errors in all lines but the written-at header.
  171. */
  172. STATIC int
  173. dirserv_read_guardfraction_file_from_str(const char *guardfraction_file_str,
  174. smartlist_t *vote_routerstatuses)
  175. {
  176. config_line_t *front=NULL, *line;
  177. int ret_tmp;
  178. int retval = -1;
  179. int current_line_n = 0; /* line counter for better log messages */
  180. /* Guardfraction info to be parsed */
  181. int total_consensuses = 0;
  182. int total_days = 0;
  183. /* Stats */
  184. int guards_read_n = 0;
  185. int guards_applied_n = 0;
  186. /* Parse file and split it in lines */
  187. ret_tmp = config_get_lines(guardfraction_file_str, &front, 0);
  188. if (ret_tmp < 0) {
  189. log_warn(LD_CONFIG, "Error reading from guardfraction file");
  190. goto done;
  191. }
  192. /* Sort routerstatuses (needed later when applying guardfraction info) */
  193. if (vote_routerstatuses)
  194. smartlist_sort(vote_routerstatuses, compare_vote_routerstatus_entries);
  195. for (line = front; line; line=line->next) {
  196. current_line_n++;
  197. if (!strcmp(line->key, GUARDFRACTION_VERSION)) {
  198. int num_ok = 1;
  199. unsigned int version;
  200. version =
  201. (unsigned int) tor_parse_long(line->value,
  202. 10, 0, INT_MAX, &num_ok, NULL);
  203. if (!num_ok || version != 1) {
  204. log_warn(LD_GENERAL, "Got unknown guardfraction version %d.", version);
  205. goto done;
  206. }
  207. } else if (!strcmp(line->key, GUARDFRACTION_DATE_STR)) {
  208. time_t file_written_at;
  209. time_t now = time(NULL);
  210. /* First line is 'written-at <date>' */
  211. if (parse_iso_time(line->value, &file_written_at) < 0) {
  212. log_warn(LD_CONFIG, "Guardfraction:%d: Bad date '%s'. Ignoring",
  213. current_line_n, line->value);
  214. goto done; /* don't tolerate failure here. */
  215. }
  216. if (file_written_at < now - MAX_GUARDFRACTION_FILE_AGE) {
  217. log_warn(LD_CONFIG, "Guardfraction:%d: was written very long ago '%s'",
  218. current_line_n, line->value);
  219. goto done; /* don't tolerate failure here. */
  220. }
  221. } else if (!strcmp(line->key, GUARDFRACTION_INPUTS)) {
  222. char *err_msg = NULL;
  223. if (guardfraction_file_parse_inputs_line(line->value,
  224. &total_consensuses,
  225. &total_days,
  226. &err_msg) < 0) {
  227. log_warn(LD_CONFIG, "Guardfraction:%d: %s",
  228. current_line_n, err_msg);
  229. tor_free(err_msg);
  230. continue;
  231. }
  232. } else if (!strcmp(line->key, GUARDFRACTION_GUARD)) {
  233. char *err_msg = NULL;
  234. ret_tmp = guardfraction_file_parse_guard_line(line->value,
  235. vote_routerstatuses,
  236. &err_msg);
  237. if (ret_tmp < 0) { /* failed while parsing the guard line */
  238. log_warn(LD_CONFIG, "Guardfraction:%d: %s",
  239. current_line_n, err_msg);
  240. tor_free(err_msg);
  241. continue;
  242. }
  243. /* Successfully parsed guard line. Check if it was applied properly. */
  244. guards_read_n++;
  245. if (ret_tmp > 0) {
  246. guards_applied_n++;
  247. }
  248. } else {
  249. log_warn(LD_CONFIG, "Unknown guardfraction line %d (%s %s)",
  250. current_line_n, line->key, line->value);
  251. }
  252. }
  253. retval = 0;
  254. log_info(LD_CONFIG,
  255. "Successfully parsed guardfraction file with %d consensuses over "
  256. "%d days. Parsed %d nodes and applied %d of them%s.",
  257. total_consensuses, total_days, guards_read_n, guards_applied_n,
  258. vote_routerstatuses ? "" : " (no routerstatus provided)" );
  259. done:
  260. config_free_lines(front);
  261. if (retval < 0) {
  262. return retval;
  263. } else {
  264. return guards_read_n;
  265. }
  266. }
  267. /** Read a guardfraction file at <b>fname</b> and load all its
  268. * information to <b>vote_routerstatuses</b>. */
  269. int
  270. dirserv_read_guardfraction_file(const char *fname,
  271. smartlist_t *vote_routerstatuses)
  272. {
  273. char *guardfraction_file_str;
  274. /* Read file to a string */
  275. guardfraction_file_str = read_file_to_str(fname, RFTS_IGNORE_MISSING, NULL);
  276. if (!guardfraction_file_str) {
  277. log_warn(LD_FS, "Cannot open guardfraction file '%s'. Failing.", fname);
  278. return -1;
  279. }
  280. return dirserv_read_guardfraction_file_from_str(guardfraction_file_str,
  281. vote_routerstatuses);
  282. }