diff --git a/changes/ticket15358 b/changes/ticket15358
new file mode 100644
index 0000000000..8c85d51007
--- /dev/null
+++ b/changes/ticket15358
@@ -0,0 +1,3 @@
+ o Features (control protocl):
+ - Support network-liveness GETINFO key and NETWORK_LIVENESS events in the
+ control protocol. Resolves ticket #15358.
diff --git a/src/or/circuitstats.c b/src/or/circuitstats.c
index 7b3ad56537..3ced5afad5 100644
--- a/src/or/circuitstats.c
+++ b/src/or/circuitstats.c
@@ -1232,6 +1232,9 @@ circuit_build_times_network_is_live(circuit_build_times_t *cbt)
}
cbt->liveness.network_last_live = now;
cbt->liveness.nonlive_timeouts = 0;
+
+ /* Tell control.c */
+ control_event_network_liveness_update(1);
}
/**
@@ -1316,6 +1319,9 @@ circuit_build_times_network_close(circuit_build_times_t *cbt,
"Tor has not observed any network activity for the past %d "
"seconds. Disabling circuit build timeout recording.",
(int)(now - cbt->liveness.network_last_live));
+
+ /* Tell control.c */
+ control_event_network_liveness_update(0);
} else {
log_info(LD_CIRC,
"Got non-live timeout. Current count is: %d",
diff --git a/src/or/control.c b/src/or/control.c
index b8b29d7134..de24294efc 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -178,6 +178,9 @@ static int write_stream_target_to_buf(entry_connection_t *conn, char *buf,
static void orconn_target_get_name(char *buf, size_t len,
or_connection_t *conn);
+static int get_cached_network_liveness(void);
+static void set_cached_network_liveness(int liveness);
+
/** Given a control event code for a message event, return the corresponding
* log severity. */
static INLINE int
@@ -958,6 +961,7 @@ static const struct control_event_t control_event_table[] = {
{ EVENT_TRANSPORT_LAUNCHED, "TRANSPORT_LAUNCHED" },
{ EVENT_HS_DESC, "HS_DESC" },
{ EVENT_HS_DESC_CONTENT, "HS_DESC_CONTENT" },
+ { EVENT_NETWORK_LIVENESS, "NETWORK_LIVENESS" },
{ 0, NULL },
};
@@ -2205,6 +2209,24 @@ getinfo_helper_onions(control_connection_t *control_conn,
return 0;
}
+/** Implementation helper for GETINFO: answers queries about network
+ * liveness. */
+static int
+getinfo_helper_liveness(control_connection_t *control_conn,
+ const char *question, char **answer,
+ const char **errmsg)
+{
+ if (strcmp(question, "network-liveness") == 0) {
+ if (get_cached_network_liveness()) {
+ *answer = tor_strdup("up");
+ } else {
+ *answer = tor_strdup("down");
+ }
+ }
+
+ return 0;
+}
+
/** Callback function for GETINFO: on a given control connection, try to
* answer the question q and store the newly-allocated answer in
* *a. If an internal error occurs, return -1 and optionally set
@@ -2289,6 +2311,8 @@ static const getinfo_item_t getinfo_items[] = {
"Information about and from the ns consensus."),
ITEM("network-status", dir,
"Brief summary of router status (v1 directory format)"),
+ ITEM("network-liveness", liveness,
+ "Current opinion on whether the network is live"),
ITEM("circuit-status", events, "List of current circuits originating here."),
ITEM("stream-status", events,"List of current streams."),
ITEM("orconn-status", events, "A list of current OR connections."),
@@ -5094,6 +5118,52 @@ control_event_or_authdir_new_descriptor(const char *action,
return 0;
}
+/** Cached liveness for network liveness events and GETINFO
+ */
+
+static int network_is_live = 0;
+
+static int
+get_cached_network_liveness(void)
+{
+ return network_is_live;
+}
+
+static void
+set_cached_network_liveness(int liveness)
+{
+ network_is_live = liveness;
+}
+
+/** The network liveness has changed; this is called from circuitstats.c
+ * whenever we receive a cell, or when timeout expires and we assume the
+ * network is down. */
+int
+control_event_network_liveness_update(int liveness)
+{
+ if (liveness > 0) {
+ if (get_cached_network_liveness() <= 0) {
+ /* Update cached liveness */
+ set_cached_network_liveness(1);
+ log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS UP");
+ send_control_event_string(EVENT_NETWORK_LIVENESS, ALL_FORMATS,
+ "650 NETWORK_LIVENESS UP\r\n");
+ }
+ /* else was already live, no-op */
+ } else {
+ if (get_cached_network_liveness() > 0) {
+ /* Update cached liveness */
+ set_cached_network_liveness(0);
+ log_debug(LD_CONTROL, "Sending NETWORK_LIVENESS DOWN");
+ send_control_event_string(EVENT_NETWORK_LIVENESS, ALL_FORMATS,
+ "650 NETWORK_LIVENESS DOWN\r\n");
+ }
+ /* else was already dead, no-op */
+ }
+
+ return 0;
+}
+
/** Helper function for NS-style events. Constructs and sends an event
* of type event with string event_string out of the set of
* networkstatuses statuses. Currently it is used for NS events
diff --git a/src/or/control.h b/src/or/control.h
index a9f2725878..2d02443834 100644
--- a/src/or/control.h
+++ b/src/or/control.h
@@ -67,6 +67,7 @@ int control_event_or_authdir_new_descriptor(const char *action,
size_t desclen,
const char *msg);
int control_event_my_descriptor_changed(void);
+int control_event_network_liveness_update(int liveness);
int control_event_networkstatus_changed(smartlist_t *statuses);
int control_event_newconsensus(const networkstatus_t *consensus);
@@ -174,7 +175,8 @@ void control_free_all(void);
#define EVENT_TRANSPORT_LAUNCHED 0x0020
#define EVENT_HS_DESC 0x0021
#define EVENT_HS_DESC_CONTENT 0x0022
-#define EVENT_MAX_ 0x0022
+#define EVENT_NETWORK_LIVENESS 0x0023
+#define EVENT_MAX_ 0x0023
/* sizeof(control_connection_t.event_mask) in bits, currently a uint64_t */
#define EVENT_CAPACITY_ 0x0040