tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

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:
Achanges/bug41191 | 10++++++++++
Msrc/core/or/or_circuit_st.h | 5+++++
Msrc/core/or/sendme.c | 12++++++++++--
Msrc/core/or/sendme.h | 2+-
Msrc/feature/dircache/dirserv.c | 9+++++++--
Msrc/feature/dircommon/directory.c | 32++++++++++++++++++++++++++++++++
Msrc/feature/dircommon/directory.h | 1+
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);