Technical correction — October 20, 2026: Terminology and counter interpretations have been corrected. Charts and summaries were recomputed from retained June–July telemetry using the method below; the observation window remains historical.
Part 2 of a five-part series on the June–July 2026 circuit-building DoS. 1. The Tor Network’s Circuit-Building DoS Wave in June–July 2026 — the network-wide view from Tor's public data. 2. Anatomy of the Attack (you are here) — the four waves and how each one works. 3. What It Actually Costs — the CPU and memory bill, and the public comparisons. 4. The Flood Below Tor — the TCP listener pressure Tor's own counters cannot see. 5. Defending Against Circuit-Building DoS — better metrics and the protocol fixes we're asking for.
Beginning June 25, 2026, 23:00 UTC, 1AEO's guard-relay family — about 10% of the Tor network — recorded a sustained circuit-building denial-of-service wave: open circuits reached 18.5x baseline peak and averaged 8.6x in the June 25–July 10 comparison, while aggregate bandwidth grew much less. This post is about the attack itself — what a circuit-building DoS is, and how four distinct signals peaked at different times. Their timing does not establish a common attacker or how the busiest relays were selected. A companion post covers what absorbing it cost — the CPU and memory pressure, and the public comparison of 1AEO's fleet with the rest of the network: What a Circuit-Building DoS Actually Costs: How 1AEO's Guard Fleet Held. Figures use fleet MetricsPort telemetry and the public datasets identified below; times are UTC.
Building a Tor circuit requires cryptographic handshakes at successive relay hops, and each relay retains state for its part of an open circuit. Built at scale and left idle, circuits load a relay's CPU and RAM while moving almost no traffic. We run Guard-flagged relays, eligible to serve as the first hop. Tor's resource-pressure indicators are documented in the Tor Project's relay overload guide (dropped ntor onionskins, OOM invocations, TCP exhaustion), and the "introduction" load maps to the introduction-point step of the onion service protocol.
The four signals peaked at different times. Onion-service introduction rejections rose first, followed by handshake-assignment activity and open-circuit counts. Protocol-error close requests (Wave 3 below) rose later. Weeks later came an outbound-byte surge (Wave 4 below). These are distinct observations; different peak times do not prove that one attacker switched tools.
The timeline below lays out the four phases in chronological order; the sub-sections that follow give the supporting data for each. Times UTC; metrics from MetricsPort, fleet-wide.
| When (UTC) | What we observed |
|---|---|
| Jun 22, 12:00–24:00 | Precursor rise in the open-circuit gauge then two quiet days. |
| Jun 25, 23:00 | Onset: worker-assignment activity and open-circuit counts rise around the onset. |
| Jun 26–27 | Wave 1 crests: introduce2_rejected reaches about 12,000 rejections/second (highest hourly sample Jun 27 04:00); connection-rate rejections also rose. |
| Jun 26–28 | Host resources start failing: large-host CPU climbs from ~30% to 90%+; per-relay RAM begins its upward ratchet; our smallest host records onionskin queue-overflow drops (the large hosts record none in the cited window); Running membership also changed, but flags alone do not establish the cause. |
| Jul 1–5 | Free memory reaches zero on the hardest-hit hosts and the kernel OOM-killer begins terminating relay processes (each auto-restarting) — repeatedly reaping relays that had bloated to many times their baseline size. |
| Jul 5, 09:00 | The 18.5x circuit peak — the highest hourly sample in the early window — and the protocol-error close-request surge (Wave 3) begins. |
| Jul 6–10 | Circuit inventory remained above baseline; protocol-error close-request activity remained elevated. |
The first wave hit Tor's onion-service machinery. The INTRODUCE2 rejected counter records introduction requests refused before an introduction point forwards them to the service. Its highest sampled one-hour rate in the June 25–July 10 window was about 12,000/second at June 27, 04:00 UTC — equivalent to 43.1 million/hour. These are the same peak expressed in two units.
INTRODUCE1 and forwards accepted requests as INTRODUCE2. In six-hour intervals from June 25, 23:00 UTC through July 13, a median of 90 relay instances recorded at least one rejection; 874 distinct instances did so at least once, among 980 that reported. The changing activity does not by itself establish why introduction points changed or how targets were chosen.The chart below shows instances recording rejections in each six-hour interval and the cumulative number observed. Missing reports cannot establish inactivity.
Why these relays, and not others? It comes down to how onion services choose the relays they lean on — three roles that are easy to conflate but are selected in completely different ways:
INTRODUCE1 cells from the connecting side and forwards them as INTRODUCE2 to the service. Chosen by the onion service, normally three at a time, normally requesting Stable relays and applying other node and path constraints and bandwidth weighting; neither the HSDir nor Guard flag alone determines this role.Tor's introduction-point selection normally asks for Stable relays, with descriptor, protocol and path constraints and bandwidth weighting; the fallback can relax the uptime requirement. Neither the HSDir nor Guard flag is required. A service retires an introduction point after a service-side introduction-count or age threshold; the Tor 0.4.9.6 compiled fallback ranges are 16,384–32,768 introductions and 18–24 hours, subject to consensus parameters. Our rejection counter does not measure that accepted count or establish why a particular introduction point rotated.
The roles are distinct: a relay can serve as an introduction point without an HSDir or Guard flag. A current Onionoo snapshot does not establish which flags it held during the historical observation window.
The late-June period also produced pruning notices for the HSDir onion-service descriptor cache, a separate memory cost. In Tor 0.4.9.6, its default limit is one-fifth of MaxMemInQueues unless MaxHSDirCacheBytes overrides it. The roughly 50 MB freed per prune reflects an implementation spare-room target, not a fixed upload size or proof of abuse. The earlier pruning-event total is withdrawn: rate-limited notices do not directly establish event totals.
The second wave is the main event: sustained circuit-creation work. The worker-assignment counter averaged 7.9x baseline and reached 14.0x at the highest hourly sample, June 29, 05:00 UTC. ntor_v3 supplied 96.6% of assignments at that peak and 89.7% across the early event. This identifies the relay handshake protocol, not the software, tool or operator that originated the traffic. The open-circuit gauge peaked later, on July 5, 09:00 UTC. The companion cost post covers the resource costs.
tor_relay_load_onionskins_total{action="dropped"} labels every ntor-family drop as type="ntor" — it never reports ntor_v3 drops separately, even when ntor_v3 is what actually overflowed. The tor_relay_load_onionskins_total{action="processed"} counter records the true handshake type; the same metric with action="dropped" records the (collapsed) queue type. Don't read the drop-side type label as the sender's handshake version. In the cited Tor code, action="processed" exposes the worker-assignment counter, incremented before dispatch; action="dropped" records queue-admission rejections. Both omit channels Tor classifies as clients, a classification that also includes unauthenticated bridge connections. Neither measures successful circuit completion.
Tor's tor_relay_circ_proto_violation_total counts calls to close a circuit with the END_CIRC_REASON_TORPROTOCOL reason, across several error paths. It does not count unique malformed circuits or identify the original sender. The early window's highest hourly rate was 167x baseline on July 6, 05:00 UTC; the expanded window's maximum was about 17,182/second on July 20, 13:00 UTC, followed by a sharp decline that night. Different timing does not establish a new attack tool or common controller. This signal is available in relay MetricsPort telemetry, not CollecTor or Onionoo.
Three weeks after the onset, outbound traffic rose sharply. The highest sampled written-to-read byte ratio in the reanalysis was 2.22 at July 23, 11:00 UTC: the fleet was transmitting more than twice what it received. The ratio later fell back toward one. This measures total relay traffic, not directory traffic alone.
The fleet increase coincided with a public directory-traffic surge. Tor's dirreq-write-history records bytes sent for directory requests, not descriptor uploads to an HSDir, and does not isolate onion-service descriptor fetches. The Tor Metrics aggregate for July 22 reports about 70.4 Gbit/s of outbound directory traffic from mirrors and authorities, equivalent to 761 decimal TB/day. This replaces the earlier 808 TB/day estimate; missing dates in the public series limit baseline comparisons.
These measurements do not identify which directory resources were requested, whether requests were abusive, or how much of the traffic was onion-service descriptor traffic. The concurrent fall in the HSDir population does not establish that descriptor demand concentrated on the remaining HSDirs or caused this byte surge. The cause remains unresolved.
On 07-23, the outbound surge coincided with increased 1AEO overload reports and Tor memory-handler activity. The handler's removed-byte accounting does not by itself isolate outbound cell queues. overload-general is a server-descriptor report of a recent threshold crossing, not proof of a failed relay or uninterrupted user traffic. We cover this episode and the separate July 27 memory event in the year-long view.
One more pattern only per-relay telemetry can show: circuit state was highly concentrated. From June 25, 23:00 UTC through July 11, the ten busiest reporting instances held a median 69.4% of the open-circuit gauge total. Consensus flags describe eligibility for relay roles; they do not identify where those circuits placed each relay.
And the cast rotates. Across retained hourly samples, 495 distinct relay instances appeared in the top ten, with a median of three new members per hour. That measures top-ten membership changes, not the duration of every relay's heavy load or whether an attacker deliberately selected it.
Open-circuit inventories need not be proportional to a relay's consensus weight: bandwidth weighting, guard reuse, path constraints and circuit lifetimes all affect the count. The concentration above describes where circuit state accumulated; it does not measure each circuit's position or show how the busiest relays were chosen.
Vanguards-Lite is one mechanism that can concentrate load: it reuses a small set of second-hop relays for onion-client and onion-service circuits. The Tor 0.4.9.6 implementation keeps one set per Tor instance, with a compiled fallback of four relays; selection still uses bandwidth weighting. Our aggregate circuit counts do not establish that this mechanism caused the concentration. Circuit handshakes also occur beyond the first hop, so the network-wide flag comparison cannot locate the CPU cost on the guard position.
Tor's tor_relay_dos_total counters show several defenses acting during the event: introduction requests rejected before forwarding, circuit-creation rejections, concurrent-connection rejections and connection-rate rejections. The connection labels are distinct: concurrent_conn_rejected records the concurrent-count defense and conn_rejected the connection-rate defense. These are defense actions, not a count of all incoming attempts or distinct attack modes:
Relay operators can override the circuit and connection DoS settings; otherwise Tor uses consensus parameters and compiled fallbacks. Noon UTC consensuses sampled for June 25, June 27, July 13 and July 23 specified a circuit rate of 2 per second, burst 60, minimum two concurrent connections for detection, and a 50-connection threshold per address per process. These are sampled consensus values, not universal compiled defaults. The circuit-rate defense applies to addresses on channels Tor classifies as clients; it is not an aggregate cap on all circuit work. An onion service can supply introduction limits through DOS_PARAMS; otherwise consensus parameters and compiled fallbacks apply, including whether the defense is enabled. The defenses post discusses the limits of per-process accounting.
These defenses reject some work; they do not impose a single ceiling on all circuit work or retained state at a relay. Accepted handshakes and idle circuits can still consume CPU and memory. The counters do not establish how many source addresses supplied that work. The next post covers the recorded resource costs and public flag-group comparisons: What a Circuit-Building DoS Actually Costs: How 1AEO's Guard Fleet Held.
For the network-wide picture from Tor's public data alone, see The Tor Network’s Circuit-Building DoS Wave in June–July 2026; for the monitoring lessons and the protocol fixes we're asking for, see Defending Against Circuit-Building DoS.
Historical reanalysis, October 20, 2026: We use only job="tor_relays", excluding duplicate legacy scrape jobs. Counter rates use rate(...[1h]) per series before summation, sampled hourly; circuit counts are hourly gauges. The baseline is the mean from June 10, 00:00 through June 25, 22:00 UTC; the early event runs June 25, 23:00 through July 10, 23:00. Hours with fewer than 883 reporting native instances (90% of the baseline maximum of 981) are omitted, not replaced with zero. Reported maxima are sampled one-hour rates or hourly gauges. The introduction-rotation chart instead uses six-hour increase values and a stated positive-rejection threshold; concentration uses hourly instance rankings. These aggregate queries do not reveal individual users or circuit positions.