summaryrefslogtreecommitdiff
path: root/src/feature
diff options
context:
space:
mode:
authorGeorge Kadianakis <desnacked@riseup.net>2020-03-03 14:35:31 +0200
committerGeorge Kadianakis <desnacked@riseup.net>2020-03-03 14:35:31 +0200
commitedc0bf5089df13d1d6a246e67bddb484ac99ad59 (patch)
tree1cec37e97780ca407ac88ecf838aa87bb8eec442 /src/feature
parent6472d9cfdf1198cf3a09659204170f9942a8fba3 (diff)
parenta5bc08579ff67a06441478016025eb4f3fd879b0 (diff)
downloadtor-edc0bf5089df13d1d6a246e67bddb484ac99ad59.tar.gz
tor-edc0bf5089df13d1d6a246e67bddb484ac99ad59.zip
Merge branch 'tor-github/pr/1763'
Diffstat (limited to 'src/feature')
-rw-r--r--src/feature/dirauth/dirauth_config.c4
-rw-r--r--src/feature/dirauth/dirvote.c8
-rw-r--r--src/feature/dirauth/include.am6
-rw-r--r--src/feature/dirauth/shared_random.c4
-rw-r--r--src/feature/dirauth/shared_random_state.c6
-rw-r--r--src/feature/dirauth/voting_schedule.c (renamed from src/feature/dircommon/voting_schedule.c)113
-rw-r--r--src/feature/dirauth/voting_schedule.h (renamed from src/feature/dircommon/voting_schedule.h)39
-rw-r--r--src/feature/dircommon/include.am6
-rw-r--r--src/feature/hs_common/shared_random_client.c82
-rw-r--r--src/feature/hs_common/shared_random_client.h2
-rw-r--r--src/feature/nodelist/networkstatus.c48
-rw-r--r--src/feature/nodelist/networkstatus.h3
12 files changed, 205 insertions, 116 deletions
diff --git a/src/feature/dirauth/dirauth_config.c b/src/feature/dirauth/dirauth_config.c
index ca16dc8424..38d2a8bc5a 100644
--- a/src/feature/dirauth/dirauth_config.c
+++ b/src/feature/dirauth/dirauth_config.c
@@ -21,7 +21,7 @@
#include "core/or/or.h"
#include "app/config/config.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/stats/rephist.h"
#include "feature/dirauth/authmode.h"
@@ -305,7 +305,7 @@ options_act_dirauth(const or_options_t *old_options)
/* We may need to reschedule some dirauth stuff if our status changed. */
if (old_options) {
if (options_transition_affects_dirauth_timing(old_options, options)) {
- voting_schedule_recalculate_timing(options, time(NULL));
+ dirauth_sched_recalculate_timing(options, time(NULL));
reschedule_dirvote(options);
}
}
diff --git a/src/feature/dirauth/dirvote.c b/src/feature/dirauth/dirvote.c
index 90e82a4ee8..cc0850ab59 100644
--- a/src/feature/dirauth/dirvote.c
+++ b/src/feature/dirauth/dirvote.c
@@ -36,7 +36,7 @@
#include "feature/stats/rephist.h"
#include "feature/client/entrynodes.h" /* needed for guardfraction methods */
#include "feature/nodelist/torcert.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/dirauth/dirvote.h"
#include "feature/dirauth/authmode.h"
@@ -2850,7 +2850,7 @@ dirvote_act(const or_options_t *options, time_t now)
"Mine is %s.",
keys, hex_str(c->cache_info.identity_digest, DIGEST_LEN));
tor_free(keys);
- voting_schedule_recalculate_timing(options, now);
+ dirauth_sched_recalculate_timing(options, now);
}
#define IF_TIME_FOR_NEXT_ACTION(when_field, done_field) \
@@ -2896,7 +2896,7 @@ dirvote_act(const or_options_t *options, time_t now)
networkstatus_get_latest_consensus_by_flavor(FLAV_NS));
/* XXXX We will want to try again later if we haven't got enough
* signatures yet. Implement this if it turns out to ever happen. */
- voting_schedule_recalculate_timing(options, now);
+ dirauth_sched_recalculate_timing(options, now);
return voting_schedule.voting_starts;
} ENDIF
@@ -4628,7 +4628,7 @@ dirserv_generate_networkstatus_vote_obj(crypto_pk_t *private_key,
else
last_consensus_interval = options->TestingV3AuthInitialVotingInterval;
v3_out->valid_after =
- voting_schedule_get_start_of_next_interval(now,
+ voting_sched_get_start_of_interval_after(now,
(int)last_consensus_interval,
options->TestingV3AuthVotingStartOffset);
format_iso_time(tbuf, v3_out->valid_after);
diff --git a/src/feature/dirauth/include.am b/src/feature/dirauth/include.am
index 2ef629ae35..e26f120d4e 100644
--- a/src/feature/dirauth/include.am
+++ b/src/feature/dirauth/include.am
@@ -19,7 +19,8 @@ MODULE_DIRAUTH_SOURCES = \
src/feature/dirauth/recommend_pkg.c \
src/feature/dirauth/shared_random.c \
src/feature/dirauth/shared_random_state.c \
- src/feature/dirauth/voteflags.c
+ src/feature/dirauth/voteflags.c \
+ src/feature/dirauth/voting_schedule.c
# ADD_C_FILE: INSERT HEADERS HERE.
noinst_HEADERS += \
@@ -43,7 +44,8 @@ noinst_HEADERS += \
src/feature/dirauth/shared_random.h \
src/feature/dirauth/shared_random_state.h \
src/feature/dirauth/vote_microdesc_hash_st.h \
- src/feature/dirauth/voteflags.h
+ src/feature/dirauth/voteflags.h \
+ src/feature/dirauth/voting_schedule.h
if BUILD_MODULE_DIRAUTH
LIBTOR_APP_A_SOURCES += $(MODULE_DIRAUTH_SOURCES)
diff --git a/src/feature/dirauth/shared_random.c b/src/feature/dirauth/shared_random.c
index 48e2147ea6..fd55008242 100644
--- a/src/feature/dirauth/shared_random.c
+++ b/src/feature/dirauth/shared_random.c
@@ -99,7 +99,7 @@
#include "feature/nodelist/dirlist.h"
#include "feature/hs_common/shared_random_client.h"
#include "feature/dirauth/shared_random_state.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/dirauth/dirvote.h"
#include "feature/dirauth/authmode.h"
@@ -1261,7 +1261,7 @@ sr_act_post_consensus(const networkstatus_t *consensus)
}
/* Prepare our state so that it's ready for the next voting period. */
- sr_state_update(voting_schedule_get_next_valid_after_time());
+ sr_state_update(dirauth_sched_get_next_valid_after_time());
}
/** Initialize shared random subsystem. This MUST be called early in the boot
diff --git a/src/feature/dirauth/shared_random_state.c b/src/feature/dirauth/shared_random_state.c
index 1792d540c6..c15f8c9937 100644
--- a/src/feature/dirauth/shared_random_state.c
+++ b/src/feature/dirauth/shared_random_state.c
@@ -20,7 +20,7 @@
#include "feature/dirauth/shared_random.h"
#include "feature/hs_common/shared_random_client.h"
#include "feature/dirauth/shared_random_state.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "lib/encoding/confline.h"
#include "lib/version/torversion.h"
@@ -139,7 +139,7 @@ get_state_valid_until_time(time_t now)
voting_interval = get_voting_interval();
/* Find the time the current round started. */
- beginning_of_current_round = get_start_time_of_current_round();
+ beginning_of_current_round = dirauth_sched_get_cur_valid_after_time();
/* Find how many rounds are left till the end of the protocol run */
current_round = (now / voting_interval) % total_rounds;
@@ -1330,7 +1330,7 @@ sr_state_init(int save_to_disk, int read_from_disk)
/* We have a state in memory, let's make sure it's updated for the current
* and next voting round. */
{
- time_t valid_after = voting_schedule_get_next_valid_after_time();
+ time_t valid_after = dirauth_sched_get_next_valid_after_time();
sr_state_update(valid_after);
}
return 0;
diff --git a/src/feature/dircommon/voting_schedule.c b/src/feature/dirauth/voting_schedule.c
index 389f7f6b5d..efc4a0b316 100644
--- a/src/feature/dircommon/voting_schedule.c
+++ b/src/feature/dirauth/voting_schedule.c
@@ -3,12 +3,11 @@
/**
* \file voting_schedule.c
- * \brief This file contains functions that are from the directory authority
- * subsystem related to voting specifically but used by many part of
- * tor. The full feature is built as part of the dirauth module.
+ * \brief Compute information about our voting schedule as a directory
+ * authority.
**/
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "core/or/or.h"
#include "app/config/config.h"
@@ -20,55 +19,11 @@
* Vote scheduling
* ===== */
-/** Return the start of the next interval of size <b>interval</b> (in
- * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always
- * starts a fresh interval, and if the last interval of a day would be
- * truncated to less than half its size, it is rolled into the
- * previous interval. */
-time_t
-voting_schedule_get_start_of_next_interval(time_t now, int interval,
- int offset)
-{
- struct tm tm;
- time_t midnight_today=0;
- time_t midnight_tomorrow;
- time_t next;
-
- tor_gmtime_r(&now, &tm);
- tm.tm_hour = 0;
- tm.tm_min = 0;
- tm.tm_sec = 0;
-
- if (tor_timegm(&tm, &midnight_today) < 0) {
- // LCOV_EXCL_START
- log_warn(LD_BUG, "Ran into an invalid time when trying to find midnight.");
- // LCOV_EXCL_STOP
- }
- midnight_tomorrow = midnight_today + (24*60*60);
-
- next = midnight_today + ((now-midnight_today)/interval + 1)*interval;
-
- /* Intervals never cross midnight. */
- if (next > midnight_tomorrow)
- next = midnight_tomorrow;
-
- /* If the interval would only last half as long as it's supposed to, then
- * skip over to the next day. */
- if (next + interval/2 > midnight_tomorrow)
- next = midnight_tomorrow;
-
- next += offset;
- if (next - interval > now)
- next -= interval;
-
- return next;
-}
-
/* Populate and return a new voting_schedule_t that can be used to schedule
* voting. The object is allocated on the heap and it's the responsibility of
* the caller to free it. Can't fail. */
static voting_schedule_t *
-get_voting_schedule(const or_options_t *options, time_t now, int severity)
+create_voting_schedule(const or_options_t *options, time_t now, int severity)
{
int interval, vote_delay, dist_delay;
time_t start;
@@ -95,14 +50,15 @@ get_voting_schedule(const or_options_t *options, time_t now, int severity)
}
tor_assert(interval > 0);
+ new_voting_schedule->interval = interval;
if (vote_delay + dist_delay > interval/2)
vote_delay = dist_delay = interval / 4;
start = new_voting_schedule->interval_starts =
- voting_schedule_get_start_of_next_interval(now,interval,
+ voting_sched_get_start_of_interval_after(now,interval,
options->TestingV3AuthVotingStartOffset);
- end = voting_schedule_get_start_of_next_interval(start+1, interval,
+ end = voting_sched_get_start_of_interval_after(start+1, interval,
options->TestingV3AuthVotingStartOffset);
tor_assert(end > start);
@@ -139,9 +95,13 @@ voting_schedule_free_(voting_schedule_t *voting_schedule_to_free)
voting_schedule_t voting_schedule;
-/* Using the time <b>now</b>, return the next voting valid-after time. */
-time_t
-voting_schedule_get_next_valid_after_time(void)
+/**
+ * Return the current voting schedule, recreating it if necessary.
+ *
+ * Dirauth only.
+ **/
+static const voting_schedule_t *
+dirauth_get_voting_schedule(void)
{
time_t now = approx_time();
bool need_to_recalculate_voting_schedule = false;
@@ -167,27 +127,62 @@ voting_schedule_get_next_valid_after_time(void)
done:
if (need_to_recalculate_voting_schedule) {
- voting_schedule_recalculate_timing(get_options(), approx_time());
+ dirauth_sched_recalculate_timing(get_options(), approx_time());
voting_schedule.created_on_demand = 1;
}
- return voting_schedule.interval_starts;
+ return &voting_schedule;
+}
+
+/** Return the next voting valid-after time.
+ *
+ * Dirauth only. */
+time_t
+dirauth_sched_get_next_valid_after_time(void)
+{
+ return dirauth_get_voting_schedule()->interval_starts;
+}
+
+/**
+ * Return our best idea of what the valid-after time for the _current_
+ * consensus, whether we have one or not.
+ *
+ * Dirauth only.
+ **/
+time_t
+dirauth_sched_get_cur_valid_after_time(void)
+{
+ const voting_schedule_t *sched = dirauth_get_voting_schedule();
+ time_t next_start = sched->interval_starts;
+ int interval = sched->interval;
+ int offset = get_options()->TestingV3AuthVotingStartOffset;
+ return voting_sched_get_start_of_interval_after(next_start - interval - 1,
+ interval,
+ offset);
+}
+
+/** Return the voting interval that we are configured to use.
+ *
+ * Dirauth only. */
+int
+dirauth_sched_get_configured_interval(void)
+{
+ return get_options()->V3AuthVotingInterval;
}
/** Set voting_schedule to hold the timing for the next vote we should be
* doing. All type of tor do that because HS subsystem needs the timing as
* well to function properly. */
void
-voting_schedule_recalculate_timing(const or_options_t *options, time_t now)
+dirauth_sched_recalculate_timing(const or_options_t *options, time_t now)
{
voting_schedule_t *new_voting_schedule;
/* get the new voting schedule */
- new_voting_schedule = get_voting_schedule(options, now, LOG_INFO);
+ new_voting_schedule = create_voting_schedule(options, now, LOG_INFO);
tor_assert(new_voting_schedule);
/* Fill in the global static struct now */
memcpy(&voting_schedule, new_voting_schedule, sizeof(voting_schedule));
voting_schedule_free(new_voting_schedule);
}
-
diff --git a/src/feature/dircommon/voting_schedule.h b/src/feature/dirauth/voting_schedule.h
index e4c6210087..9e2ac29c75 100644
--- a/src/feature/dircommon/voting_schedule.h
+++ b/src/feature/dirauth/voting_schedule.h
@@ -11,6 +11,8 @@
#include "core/or/or.h"
+#ifdef HAVE_MODULE_DIRAUTH
+
/** Scheduling information for a voting interval. */
typedef struct {
/** When do we generate and distribute our vote for this interval? */
@@ -26,6 +28,9 @@ typedef struct {
/** When do we publish the consensus? */
time_t interval_starts;
+ /** Our computed dirauth interval */
+ int interval;
+
/** True iff we have generated and distributed our vote. */
int have_voted;
/** True iff we've requested missing votes. */
@@ -53,12 +58,36 @@ typedef struct {
extern voting_schedule_t voting_schedule;
-void voting_schedule_recalculate_timing(const or_options_t *options,
+void dirauth_sched_recalculate_timing(const or_options_t *options,
time_t now);
-time_t voting_schedule_get_start_of_next_interval(time_t now,
- int interval,
- int offset);
-time_t voting_schedule_get_next_valid_after_time(void);
+time_t dirauth_sched_get_next_valid_after_time(void);
+time_t dirauth_sched_get_cur_valid_after_time(void);
+int dirauth_sched_get_configured_interval(void);
+
+#else /* !defined(HAVE_MODULE_DIRAUTH) */
+
+#define dirauth_sched_recalculate_timing(opt,now) \
+ ((void)(opt), (void)(now))
+
+static inline time_t
+dirauth_sched_get_next_valid_after_time(void)
+{
+ tor_assert_unreached();
+ return 0;
+}
+static inline time_t
+dirauth_sched_get_cur_valid_after_time(void)
+{
+ tor_assert_unreached();
+ return 0;
+}
+static inline int
+dirauth_sched_get_configured_interval(void)
+{
+ tor_assert_unreached();
+ return 1;
+}
+#endif /* defined(HAVE_MODULE_DIRAUTH) */
#endif /* !defined(TOR_VOTING_SCHEDULE_H) */
diff --git a/src/feature/dircommon/include.am b/src/feature/dircommon/include.am
index f0f0323d12..87850ce183 100644
--- a/src/feature/dircommon/include.am
+++ b/src/feature/dircommon/include.am
@@ -3,8 +3,7 @@
LIBTOR_APP_A_SOURCES += \
src/feature/dircommon/consdiff.c \
src/feature/dircommon/directory.c \
- src/feature/dircommon/fp_pair.c \
- src/feature/dircommon/voting_schedule.c
+ src/feature/dircommon/fp_pair.c
# ADD_C_FILE: INSERT HEADERS HERE.
noinst_HEADERS += \
@@ -12,5 +11,4 @@ noinst_HEADERS += \
src/feature/dircommon/dir_connection_st.h \
src/feature/dircommon/directory.h \
src/feature/dircommon/fp_pair.h \
- src/feature/dircommon/vote_timing_st.h \
- src/feature/dircommon/voting_schedule.h
+ src/feature/dircommon/vote_timing_st.h
diff --git a/src/feature/hs_common/shared_random_client.c b/src/feature/hs_common/shared_random_client.c
index a46666ab50..3f46321be4 100644
--- a/src/feature/hs_common/shared_random_client.c
+++ b/src/feature/hs_common/shared_random_client.c
@@ -11,7 +11,8 @@
#include "feature/hs_common/shared_random_client.h"
#include "app/config/config.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/authmode.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/nodelist/networkstatus.h"
#include "lib/encoding/binascii.h"
@@ -31,6 +32,24 @@ srv_to_control_string(const sr_srv_t *srv)
return srv_str;
}
+/**
+ * If we have no consensus and we are not an authority, assume that this is
+ * the voting interval. We should never actually use this: only authorities
+ * should be trying to figure out the schedule when they don't have a
+ * consensus.
+ **/
+#define DEFAULT_NETWORK_VOTING_INTERVAL (3600)
+
+/* This is an unpleasing workaround for tests. Our unit tests assume that we
+ * are scheduling all of our shared random stuff as if we were a directory
+ * authority, but they do not always set V3AuthoritativeDir.
+ */
+#ifdef TOR_UNIT_TESTS
+#define ASSUME_AUTHORITY_SCHEDULING 1
+#else
+#define ASSUME_AUTHORITY_SCHEDULING 0
+#endif
+
/** Return the voting interval of the tor vote subsystem. */
int
get_voting_interval(void)
@@ -39,40 +58,27 @@ get_voting_interval(void)
networkstatus_t *consensus = networkstatus_get_live_consensus(time(NULL));
if (consensus) {
+ /* Ideally we have a live consensus and we can just use that. */
+ interval = (int)(consensus->fresh_until - consensus->valid_after);
+ } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) {
+ /* If we don't have a live consensus and we're an authority,
+ * we should believe our own view of what the schedule ought to be. */
+ interval = dirauth_sched_get_configured_interval();
+ } else if ((consensus = networkstatus_get_latest_consensus())) {
+ /* If we're a client, then maybe a latest consensus is good enough?
+ * It's better than falling back to the non-consensus case. */
interval = (int)(consensus->fresh_until - consensus->valid_after);
} else {
- /* Same for both a testing and real network. We voluntarily ignore the
- * InitialVotingInterval since it complexifies things and it doesn't
- * affect the SR protocol. */
- interval = get_options()->V3AuthVotingInterval;
+ /* We should never be reaching this point, since a client should never
+ * call this code unless they have some kind of a consensus. All we can
+ * do is hope that this network is using the default voting interval. */
+ tor_assert_nonfatal_unreached_once();
+ interval = DEFAULT_NETWORK_VOTING_INTERVAL;
}
tor_assert(interval > 0);
return interval;
}
-/** Given the current consensus, return the start time of the current round of
- * the SR protocol. For example, if it's 23:47:08, the current round thus
- * started at 23:47:00 for a voting interval of 10 seconds.
- *
- * This function uses the consensus voting schedule to derive its results,
- * instead of the actual consensus we are currently using, so it should be used
- * for voting purposes. */
-time_t
-get_start_time_of_current_round(void)
-{
- const or_options_t *options = get_options();
- int voting_interval = get_voting_interval();
- /* First, get the start time of the next round */
- time_t next_start = voting_schedule_get_next_valid_after_time();
- /* Now roll back next_start by a voting interval to find the start time of
- the current round. */
- time_t curr_start = voting_schedule_get_start_of_next_interval(
- next_start - voting_interval - 1,
- voting_interval,
- options->TestingV3AuthVotingStartOffset);
- return curr_start;
-}
-
/*
* Public API
*/
@@ -237,13 +243,27 @@ sr_state_get_start_time_of_current_protocol_run(void)
time_t beginning_of_curr_round;
/* This function is not used for voting purposes, so if we have a live
- consensus, use its valid-after as the beginning of the current round,
- otherwise resort to the voting schedule which should always exist. */
+ consensus, use its valid-after as the beginning of the current round.
+ If we have no consensus but we're an authority, use our own
+ schedule. Otherwise, try using our view of the voting interval
+ to figure out when the current round _should_ be starting.
+ */
networkstatus_t *ns = networkstatus_get_live_consensus(approx_time());
if (ns) {
beginning_of_curr_round = ns->valid_after;
+ } else if (authdir_mode(get_options()) || ASSUME_AUTHORITY_SCHEDULING) {
+ beginning_of_curr_round = dirauth_sched_get_cur_valid_after_time();
} else {
- beginning_of_curr_round = get_start_time_of_current_round();
+ /* voting_interval comes from get_voting_interval(), so if we're in
+ * this case as a client, we already tried to get the voting interval
+ * from the latest_consensus and gave a bug warning if we couldn't.
+ *
+ * We wouldn't want to look at the latest consensus's valid_after time,
+ * since that would be out of date. */
+ beginning_of_curr_round = voting_sched_get_start_of_interval_after(
+ approx_time() - voting_interval,
+ voting_interval,
+ 0);
}
/* Get current SR protocol round */
diff --git a/src/feature/hs_common/shared_random_client.h b/src/feature/hs_common/shared_random_client.h
index 3031a2bb9a..37a086d590 100644
--- a/src/feature/hs_common/shared_random_client.h
+++ b/src/feature/hs_common/shared_random_client.h
@@ -38,11 +38,9 @@ time_t sr_state_get_start_time_of_current_protocol_run(void);
time_t sr_state_get_start_time_of_previous_protocol_run(void);
unsigned int sr_state_get_phase_duration(void);
unsigned int sr_state_get_protocol_run_duration(void);
-time_t get_start_time_of_current_round(void);
#ifdef TOR_UNIT_TESTS
#endif /* TOR_UNIT_TESTS */
#endif /* !defined(TOR_SHARED_RANDOM_CLIENT_H) */
-
diff --git a/src/feature/nodelist/networkstatus.c b/src/feature/nodelist/networkstatus.c
index cc4b8e1c34..2188e47177 100644
--- a/src/feature/nodelist/networkstatus.c
+++ b/src/feature/nodelist/networkstatus.c
@@ -66,7 +66,7 @@
#include "feature/dirclient/dirclient_modes.h"
#include "feature/dirclient/dlstatus.h"
#include "feature/dircommon/directory.h"
-#include "feature/dircommon/voting_schedule.h"
+#include "feature/dirauth/voting_schedule.h"
#include "feature/dirparse/ns_parse.h"
#include "feature/hibernate/hibernate.h"
#include "feature/hs/hs_dos.h"
@@ -2120,7 +2120,7 @@ networkstatus_set_current_consensus(const char *consensus,
* the first thing we need to do is recalculate the voting schedule static
* object so we can use the timings in there needed by some subsystems
* such as hidden service and shared random. */
- voting_schedule_recalculate_timing(options, now);
+ dirauth_sched_recalculate_timing(options, now);
reschedule_dirvote(options);
nodelist_set_consensus(c);
@@ -2765,3 +2765,47 @@ networkstatus_free_all(void)
}
}
}
+
+/** Return the start of the next interval of size <b>interval</b> (in
+ * seconds) after <b>now</b>, plus <b>offset</b>. Midnight always
+ * starts a fresh interval, and if the last interval of a day would be
+ * truncated to less than half its size, it is rolled into the
+ * previous interval. */
+time_t
+voting_sched_get_start_of_interval_after(time_t now, int interval,
+ int offset)
+{
+ struct tm tm;
+ time_t midnight_today=0;
+ time_t midnight_tomorrow;
+ time_t next;
+
+ tor_gmtime_r(&now, &tm);
+ tm.tm_hour = 0;
+ tm.tm_min = 0;
+ tm.tm_sec = 0;
+
+ if (tor_timegm(&tm, &midnight_today) < 0) {
+ // LCOV_EXCL_START
+ log_warn(LD_BUG, "Ran into an invalid time when trying to find midnight.");
+ // LCOV_EXCL_STOP
+ }
+ midnight_tomorrow = midnight_today + (24*60*60);
+
+ next = midnight_today + ((now-midnight_today)/interval + 1)*interval;
+
+ /* Intervals never cross midnight. */
+ if (next > midnight_tomorrow)
+ next = midnight_tomorrow;
+
+ /* If the interval would only last half as long as it's supposed to, then
+ * skip over to the next day. */
+ if (next + interval/2 > midnight_tomorrow)
+ next = midnight_tomorrow;
+
+ next += offset;
+ if (next - interval > now)
+ next -= interval;
+
+ return next;
+}
diff --git a/src/feature/nodelist/networkstatus.h b/src/feature/nodelist/networkstatus.h
index 5e8c8a9e57..ce050aeadc 100644
--- a/src/feature/nodelist/networkstatus.h
+++ b/src/feature/nodelist/networkstatus.h
@@ -153,6 +153,9 @@ void vote_routerstatus_free_(vote_routerstatus_t *rs);
void set_routerstatus_from_routerinfo(routerstatus_t *rs,
const node_t *node,
const routerinfo_t *ri);
+time_t voting_sched_get_start_of_interval_after(time_t now,
+ int interval,
+ int offset);
#ifdef NETWORKSTATUS_PRIVATE
#ifdef TOR_UNIT_TESTS