commit 3a7bb70e0141c109c14337a0fc3c0ba7d9632119
parent 2a7cd20405fb72118ab180142cded240288f26e2
Author: David Goulet <dgoulet@torproject.org>
Date: Thu, 22 Jan 2026 12:29:36 -0500
Merge branch 'maint-0.4.8'
Diffstat:
7 files changed, 66 insertions(+), 5 deletions(-)
diff --git a/changes/bug41191 b/changes/bug41191
@@ -0,0 +1,10 @@
+ o Major bugfixes (directory servers):
+ - Allow old clients to fetch the consensus even if they use version 0
+ of the SENDME protocol. In mid 2025 we changed the required
+ minimum version of the "FlowCtrl" protocol to 1, meaning directory
+ caches hang up on clients that send a version 0 SENDME cell. Since
+ old clients were no longer able to retrieve the consensus, they
+ couldn't learn about this required minimum version -- meaning
+ we've had many many old clients loading down directory servers
+ for the past months. Fixes bug 41191; bugfix on 0.4.1.1-alpha.
+
diff --git a/src/core/or/or_circuit_st.h b/src/core/or/or_circuit_st.h
@@ -81,6 +81,11 @@ struct or_circuit_t {
* circuit. */
bool used_legacy_circuit_handshake;
+ /** True if we received a version 0 sendme on this circuit, and it came
+ * on a legacy (CREATE_FAST) circuit so we allowed it. We track this
+ * state so we can avoid counting those directory requests for geoip. */
+ bool used_obsolete_sendme;
+
/** Number of cells that were removed from circuit queue; reset every
* time when writing buffer stats to disk. */
uint32_t processed_cells;
diff --git a/src/core/or/sendme.c b/src/core/or/sendme.c
@@ -184,7 +184,7 @@ cell_version_can_be_handled(uint8_t cell_version)
* of expected tags for each layer!
*/
STATIC bool
-sendme_is_valid(const circuit_t *circ,
+sendme_is_valid(circuit_t *circ,
const crypt_path_t *layer_hint,
const uint8_t *cell_payload,
size_t cell_payload_len)
@@ -211,7 +211,15 @@ sendme_is_valid(const circuit_t *circ,
}
/* Validate that we can handle this cell version. */
- if (!cell_version_can_be_handled(cell_version)) {
+ if (CIRCUIT_IS_ORCIRC(circ) &&
+ TO_OR_CIRCUIT(circ)->used_legacy_circuit_handshake &&
+ cell_version == 0) {
+ /* exception, allow v0 sendmes on circuits made with CREATE_FAST */
+ log_info(LD_CIRC, "Permitting sendme version 0 on legacy circuit.");
+ /* Record this choice on the circuit, so we can avoid counting
+ * directory fetches on this circuit toward our geoip stats. */
+ TO_OR_CIRCUIT(circ)->used_obsolete_sendme = 1;
+ } else if (!cell_version_can_be_handled(cell_version)) {
goto invalid;
}
diff --git a/src/core/or/sendme.h b/src/core/or/sendme.h
@@ -68,7 +68,7 @@ STATIC bool cell_version_can_be_handled(uint8_t cell_version);
STATIC ssize_t build_cell_payload_v1(const uint8_t *cell_tag,
size_t tag_len,
uint8_t *payload);
-STATIC bool sendme_is_valid(const circuit_t *circ,
+STATIC bool sendme_is_valid(circuit_t *circ,
const crypt_path_t *layer_hint,
const uint8_t *cell_payload,
size_t cell_payload_len);
diff --git a/src/feature/dircache/dirserv.c b/src/feature/dircache/dirserv.c
@@ -779,10 +779,15 @@ connection_dirserv_flushed_some(dir_connection_t *conn)
tor_compress_free(conn->compress_state);
conn->compress_state = NULL;
}
+
+ /* only count networkstatus serves as successful when the spool runs dry */
if (conn->should_count_geoip_when_finished) {
- /* only count successfully networkstatus serves when the spool runs dry */
tor_addr_t addr;
- if (tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) {
+ /* but as a special case, check if conn is on a circuit that used a
+ * version-0 sendme (bugs 41191 and 41192), because we don't want to
+ * count clients that should exit after they receive our consensus. */
+ if (!connection_dir_used_obsolete_sendme(conn) &&
+ tor_addr_parse(&addr, (TO_CONN(conn))->address) >= 0) {
geoip_note_client_seen(GEOIP_CLIENT_NETWORKSTATUS,
&addr, NULL,
time(NULL));
diff --git a/src/feature/dircommon/directory.c b/src/feature/dircommon/directory.c
@@ -264,6 +264,38 @@ connection_dir_is_anonymous(const dir_connection_t *dir_conn)
return !channel_is_client(CONST_TO_OR_CIRCUIT(circ)->p_chan);
}
+/** Did <b>conn</b> ever send us a version 0 sendme cell and we allowed
+ * it? Used to decide whether to count consensus fetches from it in our
+ * geoip stats.
+ *
+ * Note that this function might have false negatives in some cases, i.e.
+ * it could tell us that the conn never sent a v0 sendme when actually it
+ * did but its linked edge connection or OR connection got broken before
+ * we called this function. For our geoip stats these false negatives
+ * would mean overcounting users by including some of the v0-using
+ * clients.
+ *
+ * We think these false positives should be unlikely or maybe even
+ * impossible when called from connection_dirserv_flushed_some(), but
+ * be careful calling it from elsewhere.
+ * */
+bool
+connection_dir_used_obsolete_sendme(const dir_connection_t *conn)
+{
+ const edge_connection_t *edge_conn = NULL;
+ const circuit_t *circ = NULL;
+ bool used_obsolete_sendme = 0;
+ const connection_t *linked_conn = TO_CONN(conn)->linked_conn;
+ if (linked_conn)
+ edge_conn = CONST_TO_EDGE_CONN(linked_conn);
+ if (edge_conn)
+ circ = edge_conn->on_circuit;
+ if (circ && CIRCUIT_IS_ORCIRC(circ))
+ used_obsolete_sendme = CONST_TO_OR_CIRCUIT(circ)->used_obsolete_sendme;
+
+ return used_obsolete_sendme;
+}
+
/** Parse an HTTP request line at the start of a headers string. On failure,
* return -1. On success, set *<b>command_out</b> to a copy of the HTTP
* command ("get", "post", etc), set *<b>url_out</b> to a copy of the URL, and
diff --git a/src/feature/dircommon/directory.h b/src/feature/dircommon/directory.h
@@ -95,6 +95,7 @@ char *http_get_header(const char *headers, const char *which);
int connection_dir_is_encrypted(const dir_connection_t *conn);
bool connection_dir_is_anonymous(const dir_connection_t *conn);
+bool connection_dir_used_obsolete_sendme(const dir_connection_t *conn);
int connection_dir_reached_eof(dir_connection_t *conn);
int connection_dir_process_inbuf(dir_connection_t *conn);
int connection_dir_finished_flushing(dir_connection_t *conn);