1 /* Copyright (c) 2017, The Tor Project, Inc. */
2 /* See LICENSE for licensing information */
7 * \brief consensus diff manager functions
9 * This module is run by directory authorities and caches in order
10 * to remember a number of past consensus documents, and to generate
11 * and serve the diffs from those documents to the latest consensus.
14 #define CONSDIFFMGR_PRIVATE
18 #include "conscache.h"
20 #include "consdiffmgr.h"
21 #include "cpuworker.h"
22 #include "networkstatus.h"
23 #include "routerparse.h"
24 #include "workqueue.h"
27 * Labels to apply to items in the conscache object.
31 /* One of DOCTYPE_CONSENSUS or DOCTYPE_CONSENSUS_DIFF */
32 #define LABEL_DOCTYPE "document-type"
33 /* The valid-after time for a consensus (or for the target consensus of a
34 * diff), encoded as ISO UTC. */
35 #define LABEL_VALID_AFTER "consensus-valid-after"
36 /* The fresh-until time for a consensus (or for the target consensus of a
37 * diff), encoded as ISO UTC. */
38 #define LABEL_FRESH_UNTIL "consensus-fresh-until"
39 /* The valid-until time for a consensus (or for the target consensus of a
40 * diff), encoded as ISO UTC. */
41 #define LABEL_VALID_UNTIL "consensus-valid-until"
42 /* Comma-separated list of hex-encoded identity digests for the voting
44 #define LABEL_SIGNATORIES "consensus-signatories"
45 /* A hex encoded SHA3 digest of the object, as compressed (if any) */
46 #define LABEL_SHA3_DIGEST "sha3-digest"
47 /* A hex encoded SHA3 digest of the object before compression. */
48 #define LABEL_SHA3_DIGEST_UNCOMPRESSED "sha3-digest-uncompressed"
49 /* A hex encoded SHA3 digest-as-signed of a consensus */
50 #define LABEL_SHA3_DIGEST_AS_SIGNED "sha3-digest-as-signed"
51 /* The flavor of the consensus or consensuses diff */
52 #define LABEL_FLAVOR "consensus-flavor"
53 /* Diff only: the SHA3 digest-as-signed of the source consensus. */
54 #define LABEL_FROM_SHA3_DIGEST "from-sha3-digest"
55 /* Diff only: the SHA3 digest-in-full of the target consensus. */
56 #define LABEL_TARGET_SHA3_DIGEST "target-sha3-digest"
57 /* Diff only: the valid-after date of the source consensus. */
58 #define LABEL_FROM_VALID_AFTER "from-valid-after"
59 /* What kind of compression was used? */
60 #define LABEL_COMPRESSION_TYPE "compression"
63 #define DOCTYPE_CONSENSUS "consensus"
64 #define DOCTYPE_CONSENSUS_DIFF "consensus-diff"
67 * Underlying directory that stores consensuses and consensus diffs. Don't
68 * use this directly: use cdm_cache_get() instead.
70 static consensus_cache_t
*cons_diff_cache
= NULL
;
72 * If true, we have learned at least one new consensus since the
73 * consensus cache was last up-to-date.
75 static int cdm_cache_dirty
= 0;
77 * If true, we have scanned the cache to update our hashtable of diffs.
79 static int cdm_cache_loaded
= 0;
82 * Possible status values for cdm_diff_t.cdm_diff_status
84 typedef enum cdm_diff_status_t
{
86 CDM_DIFF_IN_PROGRESS
=2,
90 /** Which methods do we use for precompressing diffs? */
91 static const compress_method_t compress_diffs_with
[] = {
102 /** How many different methods will we try to use for diff compression? */
104 n_diff_compression_methods(void)
106 return ARRAY_LENGTH(compress_diffs_with
);
109 /** Which methods do we use for precompressing consensuses? */
110 static const compress_method_t compress_consensus_with
[] = {
120 /** How many different methods will we try to use for diff compression? */
122 n_consensus_compression_methods(void)
124 return ARRAY_LENGTH(compress_consensus_with
);
127 /** For which compression method do we retain old consensuses? There's no
128 * need to keep all of them, since we won't be serving them. We'll
129 * go with ZLIB_METHOD because it's pretty fast and everyone has it.
131 #define RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD ZLIB_METHOD
133 /** Handles pointing to the latest consensus entries as compressed and
135 static consensus_cache_entry_handle_t
*
136 latest_consensus
[N_CONSENSUS_FLAVORS
]
137 [ARRAY_LENGTH(compress_consensus_with
)];
139 /** Hashtable node used to remember the current status of the diff
140 * from a given sha3 digest to the current consensus. */
141 typedef struct cdm_diff_t
{
142 HT_ENTRY(cdm_diff_t
) node
;
144 /** Consensus flavor for this diff (part of ht key) */
145 consensus_flavor_t flavor
;
146 /** SHA3-256 digest of the consensus that this diff is _from_. (part of the
148 uint8_t from_sha3
[DIGEST256_LEN
];
149 /** Method by which the diff is compressed. (part of the ht key */
150 compress_method_t compress_method
;
152 /** One of the CDM_DIFF_* values, depending on whether this diff
153 * is available, in progress, or impossible to compute. */
154 cdm_diff_status_t cdm_diff_status
;
155 /** SHA3-256 digest of the consensus that this diff is _to. */
156 uint8_t target_sha3
[DIGEST256_LEN
];
158 /** Handle to the cache entry for this diff, if any. We use a handle here
159 * to avoid thinking too hard about cache entry lifetime issues. */
160 consensus_cache_entry_handle_t
*entry
;
163 /** Hashtable mapping flavor and source consensus digest to status. */
164 static HT_HEAD(cdm_diff_ht
, cdm_diff_t
) cdm_diff_ht
= HT_INITIALIZER();
167 * Configuration for this module
169 static consdiff_cfg_t consdiff_cfg
= {
170 // XXXX I'd like to make this number bigger, but it interferes with the
171 // XXXX seccomp2 syscall filter, which tops out at BPF_MAXINS (4096)
173 /* .cache_max_num = */ 128
176 static int consdiffmgr_ensure_space_for_files(int n
);
177 static int consensus_queue_compression_work(const char *consensus
,
178 const networkstatus_t
*as_parsed
);
179 static int consensus_diff_queue_diff_work(consensus_cache_entry_t
*diff_from
,
180 consensus_cache_entry_t
*diff_to
);
181 static void consdiffmgr_set_cache_flags(void);
187 /** Helper: hash the key of a cdm_diff_t. */
189 cdm_diff_hash(const cdm_diff_t
*diff
)
191 uint8_t tmp
[DIGEST256_LEN
+ 2];
192 memcpy(tmp
, diff
->from_sha3
, DIGEST256_LEN
);
193 tmp
[DIGEST256_LEN
] = (uint8_t) diff
->flavor
;
194 tmp
[DIGEST256_LEN
+1] = (uint8_t) diff
->compress_method
;
195 return (unsigned) siphash24g(tmp
, sizeof(tmp
));
197 /** Helper: compare two cdm_diff_t objects for key equality */
199 cdm_diff_eq(const cdm_diff_t
*diff1
, const cdm_diff_t
*diff2
)
201 return fast_memeq(diff1
->from_sha3
, diff2
->from_sha3
, DIGEST256_LEN
) &&
202 diff1
->flavor
== diff2
->flavor
&&
203 diff1
->compress_method
== diff2
->compress_method
;
206 HT_PROTOTYPE(cdm_diff_ht
, cdm_diff_t
, node
, cdm_diff_hash
, cdm_diff_eq
)
207 HT_GENERATE2(cdm_diff_ht
, cdm_diff_t
, node
, cdm_diff_hash
, cdm_diff_eq
,
208 0.6, tor_reallocarray
, tor_free_
)
210 #define cdm_diff_free(diff) \
211 FREE_AND_NULL(cdm_diff_t, cdm_diff_free_, (diff))
213 /** Release all storage held in <b>diff</b>. */
215 cdm_diff_free_(cdm_diff_t
*diff
)
219 consensus_cache_entry_handle_free(diff
->entry
);
223 /** Create and return a new cdm_diff_t with the given values. Does not
224 * add it to the hashtable. */
226 cdm_diff_new(consensus_flavor_t flav
,
227 const uint8_t *from_sha3
,
228 const uint8_t *target_sha3
,
229 compress_method_t method
)
232 ent
= tor_malloc_zero(sizeof(cdm_diff_t
));
234 memcpy(ent
->from_sha3
, from_sha3
, DIGEST256_LEN
);
235 memcpy(ent
->target_sha3
, target_sha3
, DIGEST256_LEN
);
236 ent
->compress_method
= method
;
241 * Examine the diff hashtable to see whether we know anything about computing
242 * a diff of type <b>flav</b> between consensuses with the two provided
243 * SHA3-256 digests. If a computation is in progress, or if the computation
244 * has already been tried and failed, return 1. Otherwise, note the
245 * computation as "in progress" so that we don't reattempt it later, and
249 cdm_diff_ht_check_and_note_pending(consensus_flavor_t flav
,
250 const uint8_t *from_sha3
,
251 const uint8_t *target_sha3
)
253 struct cdm_diff_t search
, *ent
;
256 for (u
= 0; u
< n_diff_compression_methods(); ++u
) {
257 compress_method_t method
= compress_diffs_with
[u
];
258 memset(&search
, 0, sizeof(cdm_diff_t
));
259 search
.flavor
= flav
;
260 search
.compress_method
= method
;
261 memcpy(search
.from_sha3
, from_sha3
, DIGEST256_LEN
);
262 ent
= HT_FIND(cdm_diff_ht
, &cdm_diff_ht
, &search
);
264 tor_assert_nonfatal(ent
->cdm_diff_status
!= CDM_DIFF_PRESENT
);
268 ent
= cdm_diff_new(flav
, from_sha3
, target_sha3
, method
);
269 ent
->cdm_diff_status
= CDM_DIFF_IN_PROGRESS
;
270 HT_INSERT(cdm_diff_ht
, &cdm_diff_ht
, ent
);
276 * Update the status of the diff of type <b>flav</b> between consensuses with
277 * the two provided SHA3-256 digests, so that its status becomes
278 * <b>status</b>, and its value becomes the <b>handle</b>. If <b>handle</b>
279 * is NULL, then the old handle (if any) is freed, and replaced with NULL.
282 cdm_diff_ht_set_status(consensus_flavor_t flav
,
283 const uint8_t *from_sha3
,
284 const uint8_t *to_sha3
,
285 compress_method_t method
,
287 consensus_cache_entry_handle_t
*handle
)
289 if (handle
== NULL
) {
290 tor_assert_nonfatal(status
!= CDM_DIFF_PRESENT
);
293 struct cdm_diff_t search
, *ent
;
294 memset(&search
, 0, sizeof(cdm_diff_t
));
295 search
.flavor
= flav
;
296 search
.compress_method
= method
,
297 memcpy(search
.from_sha3
, from_sha3
, DIGEST256_LEN
);
298 ent
= HT_FIND(cdm_diff_ht
, &cdm_diff_ht
, &search
);
300 ent
= cdm_diff_new(flav
, from_sha3
, to_sha3
, method
);
301 ent
->cdm_diff_status
= CDM_DIFF_IN_PROGRESS
;
302 HT_INSERT(cdm_diff_ht
, &cdm_diff_ht
, ent
);
303 } else if (fast_memneq(ent
->target_sha3
, to_sha3
, DIGEST256_LEN
)) {
304 // This can happen under certain really pathological conditions
305 // if we decide we don't care about a diff before it is actually
310 tor_assert_nonfatal(ent
->cdm_diff_status
== CDM_DIFF_IN_PROGRESS
);
312 ent
->cdm_diff_status
= status
;
313 consensus_cache_entry_handle_free(ent
->entry
);
318 * Helper: Remove from the hash table every present (actually computed) diff
319 * of type <b>flav</b> whose target digest does not match
320 * <b>unless_target_sha3_matches</b>.
322 * This function is used for the hash table to throw away references to diffs
323 * that do not lead to the most given consensus of a given flavor.
326 cdm_diff_ht_purge(consensus_flavor_t flav
,
327 const uint8_t *unless_target_sha3_matches
)
329 cdm_diff_t
**diff
, **next
;
330 for (diff
= HT_START(cdm_diff_ht
, &cdm_diff_ht
); diff
; diff
= next
) {
331 cdm_diff_t
*this = *diff
;
333 if ((*diff
)->cdm_diff_status
== CDM_DIFF_PRESENT
&&
334 flav
== (*diff
)->flavor
) {
336 if (BUG((*diff
)->entry
== NULL
) ||
337 consensus_cache_entry_handle_get((*diff
)->entry
) == NULL
) {
338 /* the underlying entry has gone away; drop this. */
339 next
= HT_NEXT_RMV(cdm_diff_ht
, &cdm_diff_ht
, diff
);
344 if (unless_target_sha3_matches
&&
345 fast_memneq(unless_target_sha3_matches
, (*diff
)->target_sha3
,
347 /* target hash doesn't match; drop this. */
348 next
= HT_NEXT_RMV(cdm_diff_ht
, &cdm_diff_ht
, diff
);
353 next
= HT_NEXT(cdm_diff_ht
, &cdm_diff_ht
, diff
);
358 * Helper: initialize <b>cons_diff_cache</b>.
363 unsigned n_entries
= consdiff_cfg
.cache_max_num
* 2;
365 tor_assert(cons_diff_cache
== NULL
);
366 cons_diff_cache
= consensus_cache_open("diff-cache", n_entries
);
367 if (cons_diff_cache
== NULL
) {
369 log_err(LD_FS
, "Error: Couldn't open storage for consensus diffs.");
370 tor_assert_unreached();
373 consdiffmgr_set_cache_flags();
376 cdm_cache_loaded
= 0;
380 * Helper: return the consensus_cache_t * that backs this manager,
381 * initializing it if needed.
383 STATIC consensus_cache_t
*
386 if (PREDICT_UNLIKELY(cons_diff_cache
== NULL
)) {
389 return cons_diff_cache
;
393 * Helper: given a list of labels, prepend the hex-encoded SHA3 digest
394 * of the <b>bodylen</b>-byte object at <b>body</b> to those labels,
395 * with <b>label</b> as its label.
398 cdm_labels_prepend_sha3(config_line_t
**labels
,
403 uint8_t sha3_digest
[DIGEST256_LEN
];
404 char hexdigest
[HEX_DIGEST256_LEN
+1];
405 crypto_digest256((char *)sha3_digest
,
406 (const char *)body
, bodylen
, DIGEST_SHA3_256
);
407 base16_encode(hexdigest
, sizeof(hexdigest
),
408 (const char *)sha3_digest
, sizeof(sha3_digest
));
410 config_line_prepend(labels
, label
, hexdigest
);
413 /** Helper: if there is a sha3-256 hex-encoded digest in <b>ent</b> with the
414 * given label, set <b>digest_out</b> to that value (decoded), and return 0.
416 * Return -1 if there is no such label, and -2 if it is badly formatted. */
418 cdm_entry_get_sha3_value(uint8_t *digest_out
,
419 consensus_cache_entry_t
*ent
,
425 const char *hex
= consensus_cache_entry_get_value(ent
, label
);
429 int n
= base16_decode((char*)digest_out
, DIGEST256_LEN
, hex
, strlen(hex
));
430 if (n
!= DIGEST256_LEN
)
437 * Helper: look for a consensus with the given <b>flavor</b> and
438 * <b>valid_after</b> time in the cache. Return that consensus if it's
439 * present, or NULL if it's missing.
441 STATIC consensus_cache_entry_t
*
442 cdm_cache_lookup_consensus(consensus_flavor_t flavor
, time_t valid_after
)
444 char formatted_time
[ISO_TIME_LEN
+1];
445 format_iso_time_nospace(formatted_time
, valid_after
);
446 const char *flavname
= networkstatus_get_flavor_name(flavor
);
448 /* We'll filter by valid-after time first, since that should
449 * match the fewest documents. */
450 /* We could add an extra hashtable here, but since we only do this scan
451 * when adding a new consensus, it probably doesn't matter much. */
452 smartlist_t
*matches
= smartlist_new();
453 consensus_cache_find_all(matches
, cdm_cache_get(),
454 LABEL_VALID_AFTER
, formatted_time
);
455 consensus_cache_filter_list(matches
, LABEL_FLAVOR
, flavname
);
456 consensus_cache_filter_list(matches
, LABEL_DOCTYPE
, DOCTYPE_CONSENSUS
);
458 consensus_cache_entry_t
*result
= NULL
;
459 if (smartlist_len(matches
)) {
460 result
= smartlist_get(matches
, 0);
462 smartlist_free(matches
);
467 /** Return the maximum age (in seconds) of consensuses that we should consider
468 * storing. The available space in the directory may impose additional limits
469 * on how much we store. */
471 get_max_age_to_cache(void)
473 const int32_t DEFAULT_MAX_AGE_TO_CACHE
= 8192;
474 const int32_t MIN_MAX_AGE_TO_CACHE
= 0;
475 const int32_t MAX_MAX_AGE_TO_CACHE
= 8192;
476 const char MAX_AGE_TO_CACHE_NAME
[] = "max-consensus-age-to-cache-for-diff";
478 const or_options_t
*options
= get_options();
480 if (options
->MaxConsensusAgeForDiffs
) {
481 const int v
= options
->MaxConsensusAgeForDiffs
;
482 if (v
>= MAX_MAX_AGE_TO_CACHE
* 3600)
483 return MAX_MAX_AGE_TO_CACHE
;
488 /* The parameter is in hours, so we multiply */
489 return 3600 * networkstatus_get_param(NULL
,
490 MAX_AGE_TO_CACHE_NAME
,
491 DEFAULT_MAX_AGE_TO_CACHE
,
492 MIN_MAX_AGE_TO_CACHE
,
493 MAX_MAX_AGE_TO_CACHE
);
497 * Given a string containing a networkstatus consensus, and the results of
498 * having parsed that consensus, add that consensus to the cache if it is not
499 * already present and not too old. Create new consensus diffs from or to
500 * that consensus as appropriate.
502 * Return 0 on success and -1 on failure.
505 consdiffmgr_add_consensus(const char *consensus
,
506 const networkstatus_t
*as_parsed
)
508 if (BUG(consensus
== NULL
) || BUG(as_parsed
== NULL
))
509 return -1; // LCOV_EXCL_LINE
510 if (BUG(as_parsed
->type
!= NS_TYPE_CONSENSUS
))
511 return -1; // LCOV_EXCL_LINE
513 const consensus_flavor_t flavor
= as_parsed
->flavor
;
514 const time_t valid_after
= as_parsed
->valid_after
;
516 if (valid_after
< approx_time() - get_max_age_to_cache()) {
517 log_info(LD_DIRSERV
, "We don't care about this consensus document; it's "
522 /* Do we already have this one? */
523 consensus_cache_entry_t
*entry
=
524 cdm_cache_lookup_consensus(flavor
, valid_after
);
526 log_info(LD_DIRSERV
, "We already have a copy of that consensus");
530 /* We don't have it. Add it to the cache. */
531 return consensus_queue_compression_work(consensus
, as_parsed
);
535 * Helper: used to sort two smartlists of consensus_cache_entry_t by their
536 * LABEL_VALID_AFTER labels.
539 compare_by_valid_after_(const void **a
, const void **b
)
541 const consensus_cache_entry_t
*e1
= *a
;
542 const consensus_cache_entry_t
*e2
= *b
;
543 /* We're in luck here: sorting UTC iso-encoded values lexically will work
544 * fine (until 9999). */
545 return strcmp_opt(consensus_cache_entry_get_value(e1
, LABEL_VALID_AFTER
),
546 consensus_cache_entry_get_value(e2
, LABEL_VALID_AFTER
));
550 * Helper: Sort <b>lst</b> by LABEL_VALID_AFTER and return the most recent
553 static consensus_cache_entry_t
*
554 sort_and_find_most_recent(smartlist_t
*lst
)
556 smartlist_sort(lst
, compare_by_valid_after_
);
557 if (smartlist_len(lst
)) {
558 return smartlist_get(lst
, smartlist_len(lst
) - 1);
564 /** Return i such that compress_consensus_with[i] == method. Return
565 * -1 if no such i exists. */
567 consensus_compression_method_pos(compress_method_t method
)
570 for (i
= 0; i
< n_consensus_compression_methods(); ++i
) {
571 if (compress_consensus_with
[i
] == method
) {
579 * If we know a consensus with the flavor <b>flavor</b> compressed with
580 * <b>method</b>, set *<b>entry_out</b> to that value. Return values are as
581 * for consdiffmgr_find_diff_from().
584 consdiffmgr_find_consensus(struct consensus_cache_entry_t
**entry_out
,
585 consensus_flavor_t flavor
,
586 compress_method_t method
)
588 tor_assert(entry_out
);
589 tor_assert((int)flavor
< N_CONSENSUS_FLAVORS
);
591 int pos
= consensus_compression_method_pos(method
);
593 // We don't compress consensuses with this method.
594 return CONSDIFF_NOT_FOUND
;
596 consensus_cache_entry_handle_t
*handle
= latest_consensus
[flavor
][pos
];
598 return CONSDIFF_NOT_FOUND
;
599 *entry_out
= consensus_cache_entry_handle_get(handle
);
601 return CONSDIFF_AVAILABLE
;
603 return CONSDIFF_NOT_FOUND
;
607 * Look up consensus_cache_entry_t for the consensus of type <b>flavor</b>,
608 * from the source consensus with the specified digest (which must be SHA3).
610 * If the diff is present, store it into *<b>entry_out</b> and return
611 * CONSDIFF_AVAILABLE. Otherwise return CONSDIFF_NOT_FOUND or
612 * CONSDIFF_IN_PROGRESS.
615 consdiffmgr_find_diff_from(consensus_cache_entry_t
**entry_out
,
616 consensus_flavor_t flavor
,
618 const uint8_t *digest
,
620 compress_method_t method
)
622 if (BUG(digest_type
!= DIGEST_SHA3_256
) ||
623 BUG(digestlen
!= DIGEST256_LEN
)) {
624 return CONSDIFF_NOT_FOUND
; // LCOV_EXCL_LINE
627 // Try to look up the entry in the hashtable.
628 cdm_diff_t search
, *ent
;
629 memset(&search
, 0, sizeof(search
));
630 search
.flavor
= flavor
;
631 search
.compress_method
= method
;
632 memcpy(search
.from_sha3
, digest
, DIGEST256_LEN
);
633 ent
= HT_FIND(cdm_diff_ht
, &cdm_diff_ht
, &search
);
636 ent
->cdm_diff_status
== CDM_DIFF_ERROR
) {
637 return CONSDIFF_NOT_FOUND
;
638 } else if (ent
->cdm_diff_status
== CDM_DIFF_IN_PROGRESS
) {
639 return CONSDIFF_IN_PROGRESS
;
640 } else if (BUG(ent
->cdm_diff_status
!= CDM_DIFF_PRESENT
)) {
641 return CONSDIFF_IN_PROGRESS
;
644 if (BUG(ent
->entry
== NULL
)) {
645 return CONSDIFF_NOT_FOUND
;
647 *entry_out
= consensus_cache_entry_handle_get(ent
->entry
);
648 return (*entry_out
) ? CONSDIFF_AVAILABLE
: CONSDIFF_NOT_FOUND
;
651 // XXXX Remove this. I'm keeping it around for now in case we need to
652 // XXXX debug issues in the hashtable.
653 char hex
[HEX_DIGEST256_LEN
+1];
654 base16_encode(hex
, sizeof(hex
), (const char *)digest
, digestlen
);
655 const char *flavname
= networkstatus_get_flavor_name(flavor
);
657 smartlist_t
*matches
= smartlist_new();
658 consensus_cache_find_all(matches
, cdm_cache_get(),
659 LABEL_FROM_SHA3_DIGEST
, hex
);
660 consensus_cache_filter_list(matches
, LABEL_FLAVOR
, flavname
);
661 consensus_cache_filter_list(matches
, LABEL_DOCTYPE
, DOCTYPE_CONSENSUS_DIFF
);
663 *entry_out
= sort_and_find_most_recent(matches
);
664 consdiff_status_t result
=
665 (*entry_out
) ? CONSDIFF_AVAILABLE
: CONSDIFF_NOT_FOUND
;
666 smartlist_free(matches
);
673 * Perform periodic cleanup tasks on the consensus diff cache. Return
674 * the number of objects marked for deletion.
677 consdiffmgr_cleanup(void)
679 smartlist_t
*objects
= smartlist_new();
680 smartlist_t
*consensuses
= smartlist_new();
681 smartlist_t
*diffs
= smartlist_new();
684 log_debug(LD_DIRSERV
, "Looking for consdiffmgr entries to remove");
686 // 1. Delete any consensus or diff or anything whose valid_after is too old.
687 const time_t valid_after_cutoff
= approx_time() - get_max_age_to_cache();
689 consensus_cache_find_all(objects
, cdm_cache_get(),
691 SMARTLIST_FOREACH_BEGIN(objects
, consensus_cache_entry_t
*, ent
) {
692 const char *lv_valid_after
=
693 consensus_cache_entry_get_value(ent
, LABEL_VALID_AFTER
);
694 if (! lv_valid_after
) {
695 log_debug(LD_DIRSERV
, "Ignoring entry because it had no %s label",
699 time_t valid_after
= 0;
700 if (parse_iso_time_nospace(lv_valid_after
, &valid_after
) < 0) {
701 log_debug(LD_DIRSERV
, "Ignoring entry because its %s value (%s) was "
702 "unparseable", LABEL_VALID_AFTER
, escaped(lv_valid_after
));
705 if (valid_after
< valid_after_cutoff
) {
706 log_debug(LD_DIRSERV
, "Deleting entry because its %s value (%s) was "
707 "too old", LABEL_VALID_AFTER
, lv_valid_after
);
708 consensus_cache_entry_mark_for_removal(ent
);
711 } SMARTLIST_FOREACH_END(ent
);
713 // 2. Delete all diffs that lead to a consensus whose valid-after is not the
715 for (int flav
= 0; flav
< N_CONSENSUS_FLAVORS
; ++flav
) {
716 const char *flavname
= networkstatus_get_flavor_name(flav
);
717 /* Determine the most recent consensus of this flavor */
718 consensus_cache_find_all(consensuses
, cdm_cache_get(),
719 LABEL_DOCTYPE
, DOCTYPE_CONSENSUS
);
720 consensus_cache_filter_list(consensuses
, LABEL_FLAVOR
, flavname
);
721 consensus_cache_entry_t
*most_recent
=
722 sort_and_find_most_recent(consensuses
);
723 if (most_recent
== NULL
)
725 const char *most_recent_sha3
=
726 consensus_cache_entry_get_value(most_recent
,
727 LABEL_SHA3_DIGEST_UNCOMPRESSED
);
728 if (BUG(most_recent_sha3
== NULL
))
729 continue; // LCOV_EXCL_LINE
731 /* consider all such-flavored diffs, and look to see if they match. */
732 consensus_cache_find_all(diffs
, cdm_cache_get(),
733 LABEL_DOCTYPE
, DOCTYPE_CONSENSUS_DIFF
);
734 consensus_cache_filter_list(diffs
, LABEL_FLAVOR
, flavname
);
735 SMARTLIST_FOREACH_BEGIN(diffs
, consensus_cache_entry_t
*, diff
) {
736 const char *this_diff_target_sha3
=
737 consensus_cache_entry_get_value(diff
, LABEL_TARGET_SHA3_DIGEST
);
738 if (!this_diff_target_sha3
)
740 if (strcmp(this_diff_target_sha3
, most_recent_sha3
)) {
741 consensus_cache_entry_mark_for_removal(diff
);
744 } SMARTLIST_FOREACH_END(diff
);
745 smartlist_clear(consensuses
);
746 smartlist_clear(diffs
);
749 // 3. Delete all consensuses except the most recent that are compressed with
750 // an un-preferred method.
751 for (int flav
= 0; flav
< N_CONSENSUS_FLAVORS
; ++flav
) {
752 const char *flavname
= networkstatus_get_flavor_name(flav
);
753 /* Determine the most recent consensus of this flavor */
754 consensus_cache_find_all(consensuses
, cdm_cache_get(),
755 LABEL_DOCTYPE
, DOCTYPE_CONSENSUS
);
756 consensus_cache_filter_list(consensuses
, LABEL_FLAVOR
, flavname
);
757 consensus_cache_entry_t
*most_recent
=
758 sort_and_find_most_recent(consensuses
);
759 if (most_recent
== NULL
)
761 const char *most_recent_sha3_uncompressed
=
762 consensus_cache_entry_get_value(most_recent
,
763 LABEL_SHA3_DIGEST_UNCOMPRESSED
);
764 const char *retain_methodname
= compression_method_get_name(
765 RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD
);
767 if (BUG(most_recent_sha3_uncompressed
== NULL
))
769 SMARTLIST_FOREACH_BEGIN(consensuses
, consensus_cache_entry_t
*, ent
) {
770 const char *lv_sha3_uncompressed
=
771 consensus_cache_entry_get_value(ent
, LABEL_SHA3_DIGEST_UNCOMPRESSED
);
772 if (BUG(! lv_sha3_uncompressed
))
774 if (!strcmp(lv_sha3_uncompressed
, most_recent_sha3_uncompressed
))
775 continue; // This _is_ the most recent.
776 const char *lv_methodname
=
777 consensus_cache_entry_get_value(ent
, LABEL_COMPRESSION_TYPE
);
778 if (! lv_methodname
|| strcmp(lv_methodname
, retain_methodname
)) {
779 consensus_cache_entry_mark_for_removal(ent
);
782 } SMARTLIST_FOREACH_END(ent
);
785 smartlist_free(objects
);
786 smartlist_free(consensuses
);
787 smartlist_free(diffs
);
789 // Actually remove files, if they're not used.
790 consensus_cache_delete_pending(cdm_cache_get(), 0);
795 * Initialize the consensus diff manager and its cache, and configure
796 * its parameters based on the latest torrc and networkstatus parameters.
799 consdiffmgr_configure(const consdiff_cfg_t
*cfg
)
802 memcpy(&consdiff_cfg
, cfg
, sizeof(consdiff_cfg
));
804 (void) cdm_cache_get();
808 * Tell the sandbox (if any) configured by <b>cfg</b> to allow the
809 * operations that the consensus diff manager will need.
812 consdiffmgr_register_with_sandbox(struct sandbox_cfg_elem
**cfg
)
814 return consensus_cache_register_with_sandbox(cdm_cache_get(), cfg
);
818 * Scan the consensus diff manager's cache for any grossly malformed entries,
819 * and mark them as deletable. Return 0 if no problems were found; 1
820 * if problems were found and fixed.
823 consdiffmgr_validate(void)
825 /* Right now, we only check for entries that have bad sha3 values */
828 smartlist_t
*objects
= smartlist_new();
829 consensus_cache_find_all(objects
, cdm_cache_get(),
831 SMARTLIST_FOREACH_BEGIN(objects
, consensus_cache_entry_t
*, obj
) {
832 uint8_t sha3_expected
[DIGEST256_LEN
];
833 uint8_t sha3_received
[DIGEST256_LEN
];
834 int r
= cdm_entry_get_sha3_value(sha3_expected
, obj
, LABEL_SHA3_DIGEST
);
836 /* digest isn't there; that's allowed */
838 } else if (r
== -2) {
839 /* digest is malformed; that's not allowed */
841 consensus_cache_entry_mark_for_removal(obj
);
846 consensus_cache_entry_incref(obj
);
847 r
= consensus_cache_entry_get_body(obj
, &body
, &bodylen
);
849 crypto_digest256((char *)sha3_received
, (const char *)body
, bodylen
,
852 consensus_cache_entry_decref(obj
);
856 // Deconfuse coverity about the possibility of sha3_received being
860 if (fast_memneq(sha3_received
, sha3_expected
, DIGEST256_LEN
)) {
862 consensus_cache_entry_mark_for_removal(obj
);
866 } SMARTLIST_FOREACH_END(obj
);
867 smartlist_free(objects
);
872 * Helper: build new diffs of <b>flavor</b> as needed
875 consdiffmgr_rescan_flavor_(consensus_flavor_t flavor
)
877 smartlist_t
*matches
= NULL
;
878 smartlist_t
*diffs
= NULL
;
879 smartlist_t
*compute_diffs_from
= NULL
;
880 strmap_t
*have_diff_from
= NULL
;
882 // look for the most recent consensus, and for all previous in-range
883 // consensuses. Do they all have diffs to it?
884 const char *flavname
= networkstatus_get_flavor_name(flavor
);
886 // 1. find the most recent consensus, and the ones that we might want
888 const char *methodname
= compression_method_get_name(
889 RETAIN_CONSENSUS_COMPRESSED_WITH_METHOD
);
891 matches
= smartlist_new();
892 consensus_cache_find_all(matches
, cdm_cache_get(),
893 LABEL_FLAVOR
, flavname
);
894 consensus_cache_filter_list(matches
, LABEL_DOCTYPE
, DOCTYPE_CONSENSUS
);
895 consensus_cache_filter_list(matches
, LABEL_COMPRESSION_TYPE
, methodname
);
896 consensus_cache_entry_t
*most_recent
= sort_and_find_most_recent(matches
);
898 log_info(LD_DIRSERV
, "No 'most recent' %s consensus found; "
899 "not making diffs", flavname
);
902 tor_assert(smartlist_len(matches
));
903 smartlist_del(matches
, smartlist_len(matches
) - 1);
905 const char *most_recent_valid_after
=
906 consensus_cache_entry_get_value(most_recent
, LABEL_VALID_AFTER
);
907 if (BUG(most_recent_valid_after
== NULL
))
908 goto done
; //LCOV_EXCL_LINE
909 uint8_t most_recent_sha3
[DIGEST256_LEN
];
910 if (BUG(cdm_entry_get_sha3_value(most_recent_sha3
, most_recent
,
911 LABEL_SHA3_DIGEST_UNCOMPRESSED
) < 0))
912 goto done
; //LCOV_EXCL_LINE
914 // 2. Find all the relevant diffs _to_ this consensus. These are ones
915 // that we don't need to compute.
916 diffs
= smartlist_new();
917 consensus_cache_find_all(diffs
, cdm_cache_get(),
918 LABEL_VALID_AFTER
, most_recent_valid_after
);
919 consensus_cache_filter_list(diffs
, LABEL_DOCTYPE
, DOCTYPE_CONSENSUS_DIFF
);
920 consensus_cache_filter_list(diffs
, LABEL_FLAVOR
, flavname
);
921 have_diff_from
= strmap_new();
922 SMARTLIST_FOREACH_BEGIN(diffs
, consensus_cache_entry_t
*, diff
) {
923 const char *va
= consensus_cache_entry_get_value(diff
,
924 LABEL_FROM_VALID_AFTER
);
926 continue; // LCOV_EXCL_LINE
927 strmap_set(have_diff_from
, va
, diff
);
928 } SMARTLIST_FOREACH_END(diff
);
930 // 3. See which consensuses in 'matches' don't have diffs yet.
931 smartlist_reverse(matches
); // from newest to oldest.
932 compute_diffs_from
= smartlist_new();
933 SMARTLIST_FOREACH_BEGIN(matches
, consensus_cache_entry_t
*, ent
) {
934 const char *va
= consensus_cache_entry_get_value(ent
, LABEL_VALID_AFTER
);
936 continue; // LCOV_EXCL_LINE
937 if (strmap_get(have_diff_from
, va
) != NULL
)
938 continue; /* we already have this one. */
939 smartlist_add(compute_diffs_from
, ent
);
940 /* Since we are not going to serve this as the most recent consensus
941 * any more, we should stop keeping it mmap'd when it's not in use.
943 consensus_cache_entry_mark_for_aggressive_release(ent
);
944 } SMARTLIST_FOREACH_END(ent
);
947 "The most recent %s consensus is valid-after %s. We have diffs to "
948 "this consensus for %d/%d older %s consensuses. Generating diffs "
951 most_recent_valid_after
,
952 smartlist_len(matches
) - smartlist_len(compute_diffs_from
),
953 smartlist_len(matches
),
955 smartlist_len(compute_diffs_from
));
957 // 4. Update the hashtable; remove entries in this flavor to other
958 // target consensuses.
959 cdm_diff_ht_purge(flavor
, most_recent_sha3
);
961 // 5. Actually launch the requests.
962 SMARTLIST_FOREACH_BEGIN(compute_diffs_from
, consensus_cache_entry_t
*, c
) {
963 if (BUG(c
== most_recent
))
964 continue; // LCOV_EXCL_LINE
966 uint8_t this_sha3
[DIGEST256_LEN
];
967 if (cdm_entry_get_sha3_value(this_sha3
, c
,
968 LABEL_SHA3_DIGEST_AS_SIGNED
)<0) {
969 // Not actually a bug, since we might be running with a directory
970 // with stale files from before the #22143 fixes.
973 if (cdm_diff_ht_check_and_note_pending(flavor
,
974 this_sha3
, most_recent_sha3
)) {
975 // This is already pending, or we encountered an error.
978 consensus_diff_queue_diff_work(c
, most_recent
);
979 } SMARTLIST_FOREACH_END(c
);
982 smartlist_free(matches
);
983 smartlist_free(diffs
);
984 smartlist_free(compute_diffs_from
);
985 strmap_free(have_diff_from
, NULL
);
989 * Scan the cache for the latest consensuses and add their handles to
993 consdiffmgr_consensus_load(void)
995 smartlist_t
*matches
= smartlist_new();
996 for (int flav
= 0; flav
< N_CONSENSUS_FLAVORS
; ++flav
) {
997 const char *flavname
= networkstatus_get_flavor_name(flav
);
998 smartlist_clear(matches
);
999 consensus_cache_find_all(matches
, cdm_cache_get(),
1000 LABEL_FLAVOR
, flavname
);
1001 consensus_cache_filter_list(matches
, LABEL_DOCTYPE
, DOCTYPE_CONSENSUS
);
1002 consensus_cache_entry_t
*most_recent
= sort_and_find_most_recent(matches
);
1004 continue; // no consensuses.
1005 const char *most_recent_sha3
=
1006 consensus_cache_entry_get_value(most_recent
,
1007 LABEL_SHA3_DIGEST_UNCOMPRESSED
);
1008 if (BUG(most_recent_sha3
== NULL
))
1009 continue; // LCOV_EXCL_LINE
1010 consensus_cache_filter_list(matches
, LABEL_SHA3_DIGEST_UNCOMPRESSED
,
1013 // Everything that remains matches the most recent consensus of this
1015 SMARTLIST_FOREACH_BEGIN(matches
, consensus_cache_entry_t
*, ent
) {
1016 const char *lv_compression
=
1017 consensus_cache_entry_get_value(ent
, LABEL_COMPRESSION_TYPE
);
1018 compress_method_t method
=
1019 compression_method_get_by_name(lv_compression
);
1020 int pos
= consensus_compression_method_pos(method
);
1023 consensus_cache_entry_handle_free(latest_consensus
[flav
][pos
]);
1024 latest_consensus
[flav
][pos
] = consensus_cache_entry_handle_new(ent
);
1025 } SMARTLIST_FOREACH_END(ent
);
1027 smartlist_free(matches
);
1031 * Scan the cache for diffs, and add them to the hashtable.
1034 consdiffmgr_diffs_load(void)
1036 smartlist_t
*diffs
= smartlist_new();
1037 consensus_cache_find_all(diffs
, cdm_cache_get(),
1038 LABEL_DOCTYPE
, DOCTYPE_CONSENSUS_DIFF
);
1039 SMARTLIST_FOREACH_BEGIN(diffs
, consensus_cache_entry_t
*, diff
) {
1040 const char *lv_flavor
=
1041 consensus_cache_entry_get_value(diff
, LABEL_FLAVOR
);
1044 int flavor
= networkstatus_parse_flavor_name(lv_flavor
);
1047 const char *lv_compression
=
1048 consensus_cache_entry_get_value(diff
, LABEL_COMPRESSION_TYPE
);
1049 compress_method_t method
= NO_METHOD
;
1050 if (lv_compression
) {
1051 method
= compression_method_get_by_name(lv_compression
);
1052 if (method
== UNKNOWN_METHOD
) {
1057 uint8_t from_sha3
[DIGEST256_LEN
];
1058 uint8_t to_sha3
[DIGEST256_LEN
];
1059 if (cdm_entry_get_sha3_value(from_sha3
, diff
, LABEL_FROM_SHA3_DIGEST
)<0)
1061 if (cdm_entry_get_sha3_value(to_sha3
, diff
, LABEL_TARGET_SHA3_DIGEST
)<0)
1064 cdm_diff_ht_set_status(flavor
, from_sha3
, to_sha3
,
1067 consensus_cache_entry_handle_new(diff
));
1068 } SMARTLIST_FOREACH_END(diff
);
1069 smartlist_free(diffs
);
1073 * Build new diffs as needed.
1076 consdiffmgr_rescan(void)
1078 if (cdm_cache_dirty
== 0)
1081 // Clean up here to make room for new diffs, and to ensure that older
1082 // consensuses do not have any entries.
1083 consdiffmgr_cleanup();
1085 if (cdm_cache_loaded
== 0) {
1086 consdiffmgr_diffs_load();
1087 consdiffmgr_consensus_load();
1088 cdm_cache_loaded
= 1;
1091 for (int flav
= 0; flav
< N_CONSENSUS_FLAVORS
; ++flav
) {
1092 consdiffmgr_rescan_flavor_((consensus_flavor_t
) flav
);
1095 cdm_cache_dirty
= 0;
1099 * Helper: compare two files by their from-valid-after and valid-after labels,
1100 * trying to sort in ascending order by from-valid-after (when present) and
1101 * valid-after (when not). Place everything that has neither label first in
1105 compare_by_staleness_(const void **a
, const void **b
)
1107 const consensus_cache_entry_t
*e1
= *a
;
1108 const consensus_cache_entry_t
*e2
= *b
;
1109 const char *va1
, *fva1
, *va2
, *fva2
;
1110 va1
= consensus_cache_entry_get_value(e1
, LABEL_VALID_AFTER
);
1111 va2
= consensus_cache_entry_get_value(e2
, LABEL_VALID_AFTER
);
1112 fva1
= consensus_cache_entry_get_value(e1
, LABEL_FROM_VALID_AFTER
);
1113 fva2
= consensus_cache_entry_get_value(e2
, LABEL_FROM_VALID_AFTER
);
1120 /* See note about iso-encoded values in compare_by_valid_after_. Also note
1121 * that missing dates will get placed first. */
1122 return strcmp_opt(va1
, va2
);
1125 /** If there are not enough unused filenames to store <b>n</b> files, then
1126 * delete old consensuses until there are. (We have to keep track of the
1127 * number of filenames because of the way that the seccomp2 cache works.)
1129 * Return 0 on success, -1 on failure.
1132 consdiffmgr_ensure_space_for_files(int n
)
1134 consensus_cache_t
*cache
= cdm_cache_get();
1135 if (consensus_cache_get_n_filenames_available(cache
) >= n
) {
1136 // there are already enough unused filenames.
1139 // Try a cheap deletion of stuff that's waiting to get deleted.
1140 consensus_cache_delete_pending(cache
, 0);
1141 if (consensus_cache_get_n_filenames_available(cache
) >= n
) {
1142 // okay, _that_ made enough filenames available.
1145 // Let's get more assertive: clean out unused stuff, and force-remove
1146 // the files that we can.
1147 consdiffmgr_cleanup();
1148 consensus_cache_delete_pending(cache
, 1);
1149 const int n_to_remove
= n
- consensus_cache_get_n_filenames_available(cache
);
1150 if (n_to_remove
<= 0) {
1155 // At this point, we're going to have to throw out objects that will be
1157 smartlist_t
*objects
= smartlist_new();
1158 consensus_cache_find_all(objects
, cache
, NULL
, NULL
);
1159 smartlist_sort(objects
, compare_by_staleness_
);
1161 SMARTLIST_FOREACH_BEGIN(objects
, consensus_cache_entry_t
*, ent
) {
1162 consensus_cache_entry_mark_for_removal(ent
);
1163 if (++n_marked
>= n_to_remove
)
1165 } SMARTLIST_FOREACH_END(ent
);
1166 smartlist_free(objects
);
1168 consensus_cache_delete_pending(cache
, 1);
1170 if (consensus_cache_may_overallocate(cache
)) {
1171 /* If we're allowed to throw extra files into the cache, let's do so
1172 * rather getting upset.
1177 if (BUG(n_marked
< n_to_remove
))
1184 * Set consensus cache flags on the objects in this consdiffmgr.
1187 consdiffmgr_set_cache_flags(void)
1189 /* Right now, we just mark the consensus objects for aggressive release,
1190 * so that they get mmapped for as little time as possible. */
1191 smartlist_t
*objects
= smartlist_new();
1192 consensus_cache_find_all(objects
, cdm_cache_get(), LABEL_DOCTYPE
,
1194 SMARTLIST_FOREACH_BEGIN(objects
, consensus_cache_entry_t
*, ent
) {
1195 consensus_cache_entry_mark_for_aggressive_release(ent
);
1196 } SMARTLIST_FOREACH_END(ent
);
1197 smartlist_free(objects
);
1201 * Called before shutdown: drop all storage held by the consdiffmgr.c module.
1204 consdiffmgr_free_all(void)
1206 cdm_diff_t
**diff
, **next
;
1207 for (diff
= HT_START(cdm_diff_ht
, &cdm_diff_ht
); diff
; diff
= next
) {
1208 cdm_diff_t
*this = *diff
;
1209 next
= HT_NEXT_RMV(cdm_diff_ht
, &cdm_diff_ht
, diff
);
1210 cdm_diff_free(this);
1214 for (i
= 0; i
< N_CONSENSUS_FLAVORS
; ++i
) {
1215 for (j
= 0; j
< n_consensus_compression_methods(); ++j
) {
1216 consensus_cache_entry_handle_free(latest_consensus
[i
][j
]);
1219 memset(latest_consensus
, 0, sizeof(latest_consensus
));
1220 consensus_cache_free(cons_diff_cache
);
1221 cons_diff_cache
= NULL
;
1228 typedef struct compressed_result_t
{
1229 config_line_t
*labels
;
1231 * Output: Body of the diff, as compressed.
1235 * Output: length of body_out
1238 } compressed_result_t
;
1241 * Compress the bytestring <b>input</b> of length <b>len</b> using the
1242 * <n>n_methods</b> compression methods listed in the array <b>methods</b>.
1244 * For each successful compression, set the fields in the <b>results_out</b>
1245 * array in the position corresponding to the compression method. Use
1246 * <b>labels_in</b> as a basis for the labels of the result.
1248 * Return 0 if all compression succeeded; -1 if any failed.
1251 compress_multiple(compressed_result_t
*results_out
, int n_methods
,
1252 const compress_method_t
*methods
,
1253 const uint8_t *input
, size_t len
,
1254 const config_line_t
*labels_in
)
1258 for (i
= 0; i
< n_methods
; ++i
) {
1259 compress_method_t method
= methods
[i
];
1260 const char *methodname
= compression_method_get_name(method
);
1263 if (0 == tor_compress(&result
, &sz
, (const char*)input
, len
, method
)) {
1264 results_out
[i
].body
= (uint8_t*)result
;
1265 results_out
[i
].bodylen
= sz
;
1266 results_out
[i
].labels
= config_lines_dup(labels_in
);
1267 cdm_labels_prepend_sha3(&results_out
[i
].labels
, LABEL_SHA3_DIGEST
,
1268 results_out
[i
].body
,
1269 results_out
[i
].bodylen
);
1270 config_line_prepend(&results_out
[i
].labels
,
1271 LABEL_COMPRESSION_TYPE
,
1281 * Given an array of <b>n</b> compressed_result_t in <b>results</b>,
1282 * as produced by compress_multiple, store them all into the
1283 * consdiffmgr, and store handles to them in the <b>handles_out</b>
1286 * Return CDM_DIFF_PRESENT if any was stored, and CDM_DIFF_ERROR if none
1289 static cdm_diff_status_t
1290 store_multiple(consensus_cache_entry_handle_t
**handles_out
,
1292 const compress_method_t
*methods
,
1293 const compressed_result_t
*results
,
1294 const char *description
)
1296 cdm_diff_status_t status
= CDM_DIFF_ERROR
;
1297 consdiffmgr_ensure_space_for_files(n
);
1300 for (i
= 0; i
< n
; ++i
) {
1301 compress_method_t method
= methods
[i
];
1302 uint8_t *body_out
= results
[i
].body
;
1303 size_t bodylen_out
= results
[i
].bodylen
;
1304 config_line_t
*labels
= results
[i
].labels
;
1305 const char *methodname
= compression_method_get_name(method
);
1306 if (body_out
&& bodylen_out
&& labels
) {
1307 /* Success! Store the results */
1308 log_info(LD_DIRSERV
, "Adding %s, compressed with %s",
1309 description
, methodname
);
1311 consensus_cache_entry_t
*ent
=
1312 consensus_cache_add(cdm_cache_get(),
1317 static ratelim_t cant_store_ratelim
= RATELIM_INIT(5*60);
1318 log_fn_ratelim(&cant_store_ratelim
, LOG_WARN
, LD_FS
,
1319 "Unable to store object %s compressed with %s.",
1320 description
, methodname
);
1324 status
= CDM_DIFF_PRESENT
;
1325 handles_out
[i
] = consensus_cache_entry_handle_new(ent
);
1326 consensus_cache_entry_decref(ent
);
1333 * An object passed to a worker thread that will try to produce a consensus
1336 typedef struct consensus_diff_worker_job_t
{
1338 * Input: The consensus to compute the diff from. Holds a reference to the
1339 * cache entry, which must not be released until the job is passed back to
1340 * the main thread. The body must be mapped into memory in the main thread.
1342 consensus_cache_entry_t
*diff_from
;
1344 * Input: The consensus to compute the diff to. Holds a reference to the
1345 * cache entry, which must not be released until the job is passed back to
1346 * the main thread. The body must be mapped into memory in the main thread.
1348 consensus_cache_entry_t
*diff_to
;
1350 /** Output: labels and bodies */
1351 compressed_result_t out
[ARRAY_LENGTH(compress_diffs_with
)];
1352 } consensus_diff_worker_job_t
;
1354 /** Given a consensus_cache_entry_t, check whether it has a label claiming
1355 * that it was compressed. If so, uncompress its contents into <b>out</b> and
1356 * set <b>outlen</b> to hold their size. If not, just copy the body into
1357 * <b>out</b> and set <b>outlen</b> to its length. Return 0 on success,
1360 * In all cases, the output is nul-terminated. */
1362 uncompress_or_copy(char **out
, size_t *outlen
,
1363 consensus_cache_entry_t
*ent
)
1365 const uint8_t *body
;
1368 if (consensus_cache_entry_get_body(ent
, &body
, &bodylen
) < 0)
1371 const char *lv_compression
=
1372 consensus_cache_entry_get_value(ent
, LABEL_COMPRESSION_TYPE
);
1373 compress_method_t method
= NO_METHOD
;
1376 method
= compression_method_get_by_name(lv_compression
);
1378 return tor_uncompress(out
, outlen
, (const char *)body
, bodylen
,
1379 method
, 1, LOG_WARN
);
1383 * Worker function. This function runs inside a worker thread and receives
1384 * a consensus_diff_worker_job_t as its input.
1386 static workqueue_reply_t
1387 consensus_diff_worker_threadfn(void *state_
, void *work_
)
1390 consensus_diff_worker_job_t
*job
= work_
;
1391 const uint8_t *diff_from
, *diff_to
;
1392 size_t len_from
, len_to
;
1394 /* We need to have the body already mapped into RAM here.
1396 r
= consensus_cache_entry_get_body(job
->diff_from
, &diff_from
, &len_from
);
1398 return WQ_RPL_REPLY
; // LCOV_EXCL_LINE
1399 r
= consensus_cache_entry_get_body(job
->diff_to
, &diff_to
, &len_to
);
1401 return WQ_RPL_REPLY
; // LCOV_EXCL_LINE
1403 const char *lv_to_valid_after
=
1404 consensus_cache_entry_get_value(job
->diff_to
, LABEL_VALID_AFTER
);
1405 const char *lv_to_fresh_until
=
1406 consensus_cache_entry_get_value(job
->diff_to
, LABEL_FRESH_UNTIL
);
1407 const char *lv_to_valid_until
=
1408 consensus_cache_entry_get_value(job
->diff_to
, LABEL_VALID_UNTIL
);
1409 const char *lv_to_signatories
=
1410 consensus_cache_entry_get_value(job
->diff_to
, LABEL_SIGNATORIES
);
1411 const char *lv_from_valid_after
=
1412 consensus_cache_entry_get_value(job
->diff_from
, LABEL_VALID_AFTER
);
1413 const char *lv_from_digest
=
1414 consensus_cache_entry_get_value(job
->diff_from
,
1415 LABEL_SHA3_DIGEST_AS_SIGNED
);
1416 const char *lv_from_flavor
=
1417 consensus_cache_entry_get_value(job
->diff_from
, LABEL_FLAVOR
);
1418 const char *lv_to_flavor
=
1419 consensus_cache_entry_get_value(job
->diff_to
, LABEL_FLAVOR
);
1420 const char *lv_to_digest
=
1421 consensus_cache_entry_get_value(job
->diff_to
,
1422 LABEL_SHA3_DIGEST_UNCOMPRESSED
);
1424 if (! lv_from_digest
) {
1425 /* This isn't a bug right now, since it can happen if you're migrating
1426 * from an older version of master to a newer one. The older ones didn't
1427 * annotate their stored consensus objects with sha3-digest-as-signed.
1429 return WQ_RPL_REPLY
; // LCOV_EXCL_LINE
1432 /* All these values are mandatory on the input */
1433 if (BUG(!lv_to_valid_after
) ||
1434 BUG(!lv_from_valid_after
) ||
1435 BUG(!lv_from_flavor
) ||
1436 BUG(!lv_to_flavor
)) {
1437 return WQ_RPL_REPLY
; // LCOV_EXCL_LINE
1439 /* The flavors need to match */
1440 if (BUG(strcmp(lv_from_flavor
, lv_to_flavor
))) {
1441 return WQ_RPL_REPLY
; // LCOV_EXCL_LINE
1444 char *consensus_diff
;
1446 char *diff_from_nt
= NULL
, *diff_to_nt
= NULL
;
1447 size_t diff_from_nt_len
, diff_to_nt_len
;
1449 if (uncompress_or_copy(&diff_from_nt
, &diff_from_nt_len
,
1450 job
->diff_from
) < 0) {
1451 return WQ_RPL_REPLY
;
1453 if (uncompress_or_copy(&diff_to_nt
, &diff_to_nt_len
,
1454 job
->diff_to
) < 0) {
1455 tor_free(diff_from_nt
);
1456 return WQ_RPL_REPLY
;
1458 tor_assert(diff_from_nt
);
1459 tor_assert(diff_to_nt
);
1461 // XXXX ugh; this is going to calculate the SHA3 of both its
1462 // XXXX inputs again, even though we already have that. Maybe it's time
1463 // XXXX to change the API here?
1464 consensus_diff
= consensus_diff_generate(diff_from_nt
, diff_to_nt
);
1465 tor_free(diff_from_nt
);
1466 tor_free(diff_to_nt
);
1468 if (!consensus_diff
) {
1469 /* Couldn't generate consensus; we'll leave the reply blank. */
1470 return WQ_RPL_REPLY
;
1473 /* Compress the results and send the reply */
1474 tor_assert(compress_diffs_with
[0] == NO_METHOD
);
1475 size_t difflen
= strlen(consensus_diff
);
1476 job
->out
[0].body
= (uint8_t *) consensus_diff
;
1477 job
->out
[0].bodylen
= difflen
;
1479 config_line_t
*common_labels
= NULL
;
1480 if (lv_to_valid_until
)
1481 config_line_prepend(&common_labels
, LABEL_VALID_UNTIL
, lv_to_valid_until
);
1482 if (lv_to_fresh_until
)
1483 config_line_prepend(&common_labels
, LABEL_FRESH_UNTIL
, lv_to_fresh_until
);
1484 if (lv_to_signatories
)
1485 config_line_prepend(&common_labels
, LABEL_SIGNATORIES
, lv_to_signatories
);
1486 cdm_labels_prepend_sha3(&common_labels
,
1487 LABEL_SHA3_DIGEST_UNCOMPRESSED
,
1489 job
->out
[0].bodylen
);
1490 config_line_prepend(&common_labels
, LABEL_FROM_VALID_AFTER
,
1491 lv_from_valid_after
);
1492 config_line_prepend(&common_labels
, LABEL_VALID_AFTER
,
1494 config_line_prepend(&common_labels
, LABEL_FLAVOR
, lv_from_flavor
);
1495 config_line_prepend(&common_labels
, LABEL_FROM_SHA3_DIGEST
,
1497 config_line_prepend(&common_labels
, LABEL_TARGET_SHA3_DIGEST
,
1499 config_line_prepend(&common_labels
, LABEL_DOCTYPE
,
1500 DOCTYPE_CONSENSUS_DIFF
);
1502 job
->out
[0].labels
= config_lines_dup(common_labels
);
1503 cdm_labels_prepend_sha3(&job
->out
[0].labels
,
1506 job
->out
[0].bodylen
);
1508 compress_multiple(job
->out
+1,
1509 n_diff_compression_methods()-1,
1510 compress_diffs_with
+1,
1511 (const uint8_t*)consensus_diff
, difflen
, common_labels
);
1513 config_free_lines(common_labels
);
1514 return WQ_RPL_REPLY
;
1517 #define consensus_diff_worker_job_free(job) \
1518 FREE_AND_NULL(consensus_diff_worker_job_t, \
1519 consensus_diff_worker_job_free_, (job))
1522 * Helper: release all storage held in <b>job</b>.
1525 consensus_diff_worker_job_free_(consensus_diff_worker_job_t
*job
)
1530 for (u
= 0; u
< n_diff_compression_methods(); ++u
) {
1531 config_free_lines(job
->out
[u
].labels
);
1532 tor_free(job
->out
[u
].body
);
1534 consensus_cache_entry_decref(job
->diff_from
);
1535 consensus_cache_entry_decref(job
->diff_to
);
1540 * Worker function: This function runs in the main thread, and receives
1541 * a consensus_diff_worker_job_t that the worker thread has already
1545 consensus_diff_worker_replyfn(void *work_
)
1547 tor_assert(in_main_thread());
1550 consensus_diff_worker_job_t
*job
= work_
;
1552 const char *lv_from_digest
=
1553 consensus_cache_entry_get_value(job
->diff_from
,
1554 LABEL_SHA3_DIGEST_AS_SIGNED
);
1555 const char *lv_to_digest
=
1556 consensus_cache_entry_get_value(job
->diff_to
,
1557 LABEL_SHA3_DIGEST_UNCOMPRESSED
);
1558 const char *lv_flavor
=
1559 consensus_cache_entry_get_value(job
->diff_to
, LABEL_FLAVOR
);
1560 if (BUG(lv_from_digest
== NULL
))
1561 lv_from_digest
= "???"; // LCOV_EXCL_LINE
1562 if (BUG(lv_to_digest
== NULL
))
1563 lv_to_digest
= "???"; // LCOV_EXCL_LINE
1565 uint8_t from_sha3
[DIGEST256_LEN
];
1566 uint8_t to_sha3
[DIGEST256_LEN
];
1569 if (BUG(cdm_entry_get_sha3_value(from_sha3
, job
->diff_from
,
1570 LABEL_SHA3_DIGEST_AS_SIGNED
) < 0))
1572 if (BUG(cdm_entry_get_sha3_value(to_sha3
, job
->diff_to
,
1573 LABEL_SHA3_DIGEST_UNCOMPRESSED
) < 0))
1575 if (BUG(lv_flavor
== NULL
)) {
1577 } else if ((flav
= networkstatus_parse_flavor_name(lv_flavor
)) < 0) {
1581 consensus_cache_entry_handle_t
*handles
[ARRAY_LENGTH(compress_diffs_with
)];
1582 memset(handles
, 0, sizeof(handles
));
1584 char description
[128];
1585 tor_snprintf(description
, sizeof(description
),
1586 "consensus diff from %s to %s",
1587 lv_from_digest
, lv_to_digest
);
1589 int status
= store_multiple(handles
,
1590 n_diff_compression_methods(),
1591 compress_diffs_with
,
1595 if (status
!= CDM_DIFF_PRESENT
) {
1596 /* Failure! Nothing to do but complain */
1597 log_warn(LD_DIRSERV
,
1598 "Worker was unable to compute consensus diff "
1599 "from %s to %s", lv_from_digest
, lv_to_digest
);
1600 /* Cache this error so we don't try to compute this one again. */
1601 status
= CDM_DIFF_ERROR
;
1605 for (u
= 0; u
< ARRAY_LENGTH(handles
); ++u
) {
1606 compress_method_t method
= compress_diffs_with
[u
];
1608 consensus_cache_entry_handle_t
*h
= handles
[u
];
1609 int this_status
= status
;
1611 this_status
= CDM_DIFF_ERROR
;
1613 tor_assert_nonfatal(h
!= NULL
|| this_status
== CDM_DIFF_ERROR
);
1614 cdm_diff_ht_set_status(flav
, from_sha3
, to_sha3
, method
, this_status
, h
);
1616 consensus_cache_entry_handle_free(handles
[u
]);
1620 consensus_diff_worker_job_free(job
);
1624 * Queue the job of computing the diff from <b>diff_from</b> to <b>diff_to</b>
1625 * in a worker thread.
1628 consensus_diff_queue_diff_work(consensus_cache_entry_t
*diff_from
,
1629 consensus_cache_entry_t
*diff_to
)
1631 tor_assert(in_main_thread());
1633 consensus_cache_entry_incref(diff_from
);
1634 consensus_cache_entry_incref(diff_to
);
1636 consensus_diff_worker_job_t
*job
= tor_malloc_zero(sizeof(*job
));
1637 job
->diff_from
= diff_from
;
1638 job
->diff_to
= diff_to
;
1640 /* Make sure body is mapped. */
1641 const uint8_t *body
;
1643 int r1
= consensus_cache_entry_get_body(diff_from
, &body
, &bodylen
);
1644 int r2
= consensus_cache_entry_get_body(diff_to
, &body
, &bodylen
);
1645 if (r1
< 0 || r2
< 0)
1648 workqueue_entry_t
*work
;
1649 work
= cpuworker_queue_work(WQ_PRI_LOW
,
1650 consensus_diff_worker_threadfn
,
1651 consensus_diff_worker_replyfn
,
1658 consensus_diff_worker_job_free(job
); // includes decrefs.
1663 * Holds requests and replies for consensus_compress_workers.
1665 typedef struct consensus_compress_worker_job_t
{
1667 size_t consensus_len
;
1668 consensus_flavor_t flavor
;
1669 config_line_t
*labels_in
;
1670 compressed_result_t out
[ARRAY_LENGTH(compress_consensus_with
)];
1671 } consensus_compress_worker_job_t
;
1673 #define consensus_compress_worker_job_free(job) \
1674 FREE_AND_NULL(consensus_compress_worker_job_t, \
1675 consensus_compress_worker_job_free_, (job))
1678 * Free all resources held in <b>job</b>
1681 consensus_compress_worker_job_free_(consensus_compress_worker_job_t
*job
)
1685 tor_free(job
->consensus
);
1686 config_free_lines(job
->labels_in
);
1688 for (u
= 0; u
< n_consensus_compression_methods(); ++u
) {
1689 config_free_lines(job
->out
[u
].labels
);
1690 tor_free(job
->out
[u
].body
);
1695 * Worker function. This function runs inside a worker thread and receives
1696 * a consensus_compress_worker_job_t as its input.
1698 static workqueue_reply_t
1699 consensus_compress_worker_threadfn(void *state_
, void *work_
)
1702 consensus_compress_worker_job_t
*job
= work_
;
1703 consensus_flavor_t flavor
= job
->flavor
;
1704 const char *consensus
= job
->consensus
;
1705 size_t bodylen
= job
->consensus_len
;
1707 config_line_t
*labels
= config_lines_dup(job
->labels_in
);
1708 const char *flavname
= networkstatus_get_flavor_name(flavor
);
1710 cdm_labels_prepend_sha3(&labels
, LABEL_SHA3_DIGEST_UNCOMPRESSED
,
1711 (const uint8_t *)consensus
, bodylen
);
1713 const char *start
, *end
;
1714 if (router_get_networkstatus_v3_signed_boundaries(consensus
,
1715 &start
, &end
) < 0) {
1717 end
= consensus
+bodylen
;
1719 cdm_labels_prepend_sha3(&labels
, LABEL_SHA3_DIGEST_AS_SIGNED
,
1720 (const uint8_t *)start
,
1723 config_line_prepend(&labels
, LABEL_FLAVOR
, flavname
);
1724 config_line_prepend(&labels
, LABEL_DOCTYPE
, DOCTYPE_CONSENSUS
);
1726 compress_multiple(job
->out
,
1727 n_consensus_compression_methods(),
1728 compress_consensus_with
,
1729 (const uint8_t*)consensus
, bodylen
, labels
);
1730 config_free_lines(labels
);
1731 return WQ_RPL_REPLY
;
1735 * Worker function: This function runs in the main thread, and receives
1736 * a consensus_diff_compress_job_t that the worker thread has already
1740 consensus_compress_worker_replyfn(void *work_
)
1742 consensus_compress_worker_job_t
*job
= work_
;
1744 consensus_cache_entry_handle_t
*handles
[
1745 ARRAY_LENGTH(compress_consensus_with
)];
1746 memset(handles
, 0, sizeof(handles
));
1748 store_multiple(handles
,
1749 n_consensus_compression_methods(),
1750 compress_consensus_with
,
1753 cdm_cache_dirty
= 1;
1756 consensus_flavor_t f
= job
->flavor
;
1757 tor_assert((int)f
< N_CONSENSUS_FLAVORS
);
1758 for (u
= 0; u
< ARRAY_LENGTH(handles
); ++u
) {
1759 if (handles
[u
] == NULL
)
1761 consensus_cache_entry_handle_free(latest_consensus
[f
][u
]);
1762 latest_consensus
[f
][u
] = handles
[u
];
1765 consensus_compress_worker_job_free(job
);
1769 * If true, we compress in worker threads.
1771 static int background_compression
= 0;
1774 * Queue a job to compress <b>consensus</b> and store its compressed
1775 * text in the cache.
1778 consensus_queue_compression_work(const char *consensus
,
1779 const networkstatus_t
*as_parsed
)
1781 tor_assert(consensus
);
1782 tor_assert(as_parsed
);
1784 consensus_compress_worker_job_t
*job
= tor_malloc_zero(sizeof(*job
));
1785 job
->consensus
= tor_strdup(consensus
);
1786 job
->consensus_len
= strlen(consensus
);
1787 job
->flavor
= as_parsed
->flavor
;
1789 char va_str
[ISO_TIME_LEN
+1];
1790 char vu_str
[ISO_TIME_LEN
+1];
1791 char fu_str
[ISO_TIME_LEN
+1];
1792 format_iso_time_nospace(va_str
, as_parsed
->valid_after
);
1793 format_iso_time_nospace(fu_str
, as_parsed
->fresh_until
);
1794 format_iso_time_nospace(vu_str
, as_parsed
->valid_until
);
1795 config_line_append(&job
->labels_in
, LABEL_VALID_AFTER
, va_str
);
1796 config_line_append(&job
->labels_in
, LABEL_FRESH_UNTIL
, fu_str
);
1797 config_line_append(&job
->labels_in
, LABEL_VALID_UNTIL
, vu_str
);
1798 if (as_parsed
->voters
) {
1799 smartlist_t
*hexvoters
= smartlist_new();
1800 SMARTLIST_FOREACH_BEGIN(as_parsed
->voters
,
1801 networkstatus_voter_info_t
*, vi
) {
1802 if (smartlist_len(vi
->sigs
) == 0)
1803 continue; // didn't sign.
1804 char d
[HEX_DIGEST_LEN
+1];
1805 base16_encode(d
, sizeof(d
), vi
->identity_digest
, DIGEST_LEN
);
1806 smartlist_add_strdup(hexvoters
, d
);
1807 } SMARTLIST_FOREACH_END(vi
);
1808 char *signers
= smartlist_join_strings(hexvoters
, ",", 0, NULL
);
1809 config_line_prepend(&job
->labels_in
, LABEL_SIGNATORIES
, signers
);
1811 SMARTLIST_FOREACH(hexvoters
, char *, cp
, tor_free(cp
));
1812 smartlist_free(hexvoters
);
1815 if (background_compression
) {
1816 workqueue_entry_t
*work
;
1817 work
= cpuworker_queue_work(WQ_PRI_LOW
,
1818 consensus_compress_worker_threadfn
,
1819 consensus_compress_worker_replyfn
,
1822 consensus_compress_worker_job_free(job
);
1828 consensus_compress_worker_threadfn(NULL
, job
);
1829 consensus_compress_worker_replyfn(job
);
1835 * Tell the consdiffmgr backend to compress consensuses in worker threads.
1838 consdiffmgr_enable_background_compression(void)
1840 // This isn't the default behavior because it would break unit tests.
1841 background_compression
= 1;
1844 /** Read the set of voters from the cached object <b>ent</b> into
1845 * <b>out</b>, as a list of hex-encoded digests. Return 0 on success,
1846 * -1 if no signatories were recorded. */
1848 consensus_cache_entry_get_voter_id_digests(const consensus_cache_entry_t
*ent
,
1854 s
= consensus_cache_entry_get_value(ent
, LABEL_SIGNATORIES
);
1857 smartlist_split_string(out
, s
, ",", SPLIT_SKIP_SPACE
|SPLIT_STRIP_SPACE
, 0);
1861 /** Read the fresh-until time of cached object <b>ent</b> into *<b>out</b>
1862 * and return 0, or return -1 if no such time was recorded. */
1864 consensus_cache_entry_get_fresh_until(const consensus_cache_entry_t
*ent
,
1870 s
= consensus_cache_entry_get_value(ent
, LABEL_FRESH_UNTIL
);
1871 if (s
== NULL
|| parse_iso_time_nospace(s
, out
) < 0)
1877 /** Read the valid until timestamp from the cached object <b>ent</b> into
1878 * *<b>out</b> and return 0, or return -1 if no such time was recorded. */
1880 consensus_cache_entry_get_valid_until(const consensus_cache_entry_t
*ent
,
1887 s
= consensus_cache_entry_get_value(ent
, LABEL_VALID_UNTIL
);
1888 if (s
== NULL
|| parse_iso_time_nospace(s
, out
) < 0)
1894 /** Read the valid after timestamp from the cached object <b>ent</b> into
1895 * *<b>out</b> and return 0, or return -1 if no such time was recorded. */
1897 consensus_cache_entry_get_valid_after(const consensus_cache_entry_t
*ent
,
1904 s
= consensus_cache_entry_get_value(ent
, LABEL_VALID_AFTER
);
1906 if (s
== NULL
|| parse_iso_time_nospace(s
, out
) < 0)