Blind Relay Abuse Guard

AeroNyx8 min read

The limits an AeroNyx node applies before forwarding encrypted relay traffic: previous-hop rate limits and quarantine, restart-safe route replay suppression, bounded retries and CPU admission, using only signed metadata and aggregate counters.

The Blind Relay Abuse Guard is the set of limits an AeroNyx node applies before it forwards encrypted relay traffic for another node. It contains abusive or unstable peers using only signed routing metadata and local counters. It never parses ciphertext, keeps route histories or exposes who talks to whom.

Scope

ControlState
Opaque payload forwardingImplemented
Signed freshness checks and route replay suppressionImplemented. Replay state is restart-safe when the node's chat relay store is enabled
Previous-hop rate limiting and quarantineImplemented
Process-wide request and crypto admission limitsImplemented
Aggregate runtime evidence in node health and NodeboardImplemented
Inspection of plaintext or payloadsNot done, by design
Per-user, per-route or social-graph analyticsNot done, by design

The blind-node rule

A relay may authenticate routing metadata, apply bounded policy, forward an opaque envelope and return a signed receipt. It must never inspect the payload or expose metadata that could rebuild a route, a user identity or a relationship. In particular, relays do not record or expose:

  • message, packet, media or memory plaintext
  • DNS queries, destinations, domains, URLs or browsing history
  • route IDs, complete paths, endpoint URLs or client IP addresses
  • full public keys, receiver identities, message IDs or social-graph edges
  • private keys, voucher secrets, wallet-level traffic or decryption material

Admission pipeline

Each request passes these checks before it can use forwarding capacity. Steps 1 to 6 run in this order. In step 7, a request addressed to this node goes to its own terminal or onion handling, while a request for another node is reserved after its next hop is resolved.

text
1. request gate: process-wide admission window and in-flight limit (64 requests)
2. crypto admission: a free slot for signature and onion work, or Backpressure
3. authenticate the signed previous hop and envelope
4. previous-hop rate limit and quarantine check
5. envelope metadata: size, age and future-skew limits
6. self-loop check
7. route loop, TTL and next-hop descriptor checks, plus the route replay reservation
8. forward the opaque ciphertext, or peel the terminal layer and deliver

Every decision uses signed routing metadata and local aggregate state, never decrypted content.

Runtime limits

These are the current defaults in the node source. They are operating values, not permanent protocol promises.

ConstantDefault
BLIND_RELAY_ROUTE_REPLAY_CAPACITY8192 live routes
BLIND_RELAY_ROUTE_REPLAY_TTL_SECS600 seconds
PREVIOUS_HOP_RATE_LIMIT per PREVIOUS_HOP_RATE_WINDOW_SECS120 requests per 60 seconds
PREVIOUS_HOP_FAILURE_THRESHOLD per PREVIOUS_HOP_FAILURE_WINDOW_SECS12 scored failures per 300 seconds
PREVIOUS_HOP_QUARANTINE_SECS300 seconds
MAX_PREVIOUS_HOP_BUCKETS4096 buckets
BLIND_RELAY_MAX_ENVELOPE_AGE_SECS600 seconds
BLIND_RELAY_MAX_FUTURE_SKEW_SECS120 seconds
BLIND_RELAY_DELIVERY_RECEIPT_MAX_AGE_SECS120 seconds
DEFAULT_MAX_ATTEMPTS (forward retries)3 attempts
MAX_IN_FLIGHT_BLIND_RELAY_REQUESTS64 requests

Previous-hop rate limiting and quarantine

Rate limits are keyed by the signed identity of the previous hop. More than 120 requests from one previous hop within 60 seconds starts a five-minute local quarantine for that hop. A separate failure score starts the same quarantine after 12 scored failures within five minutes. Only these reasons count toward the score:

text
invalid_previous_hop | invalid_signature | self_loop | route_loop | ttl_exhausted

Transport timeouts, lost acknowledgements, duplicate retries and replay conflicts do not add to the score, so a healthy peer is not quarantined for network trouble. The bucket store is bounded, and idle buckets are evicted, so the guard cannot grow into a long-term communication graph.

Route replay and retries

Every relay request carries a route ID. When the node's chat relay store is enabled ([memchain.chat_relay] enabled = true), route reservations and completed responses are kept in its SQLite database, so replay protection survives a restart. Otherwise the node uses an in-memory registry with the same capacity and window. If all 8192 slots hold live routes, new routes are rejected with replay_capacity until slots expire.

A repeated route ID has one of four outcomes:

SituationResponseEffect on the previous hop
Same request; the first attempt has completedThe stored response is replayed, including any terminal-signed receipt. Nothing is delivered or forwarded a second time (duplicate_route)Counted as a success
Same request; the first attempt is still runningHTTP 503 route_in_flight. The sender may retryNone
Different request under the same route IDHTTP 400 replay_conflictRecorded in peer health. Does not add to the quarantine score
Same request, but the stored response is older than 120 secondsHTTP 409 replay_response_expired. The route stays reserved for the rest of its 600-second window, so it cannot run againNone

The last outcome applies to the durable store. Signed receipts are only valid as fresh evidence, so the node does not replay one after 120 seconds.

When forwarding to the next hop fails with a timeout, a connection error or a request error, the node retries up to three attempts in total. The short delay before each retry is derived from the route ID, next hop and attempt number. Failures that the downstream node reports as definite rejections are not retried.

Crypto admission (operator capacity note)

Signature checks, onion peeling and receipt signing are CPU-bound, so each node process limits them:

  • Total slots: half the CPU threads the host reports, rounded up, never fewer than 4 and at most 8, so even a one- or two-thread host gets 4 slots.
  • Inbound requests: one slot fewer than the total, so outbound route creation and receipt completion can always make progress. On the smallest hosts, inbound requests therefore get 3 slots.
  • When all slots are busy: the request is rejected immediately with HTTP 429 and the backpressure reason. It does not wait in a queue.

On a small host, a relay therefore sheds load early under bursts. A rising backpressure_dropped counter means the node needs more CPU or less relay traffic.

Aggregate runtime counters

Nodes report coarse cumulative counters and freshness timestamps in their heartbeat under:

text
system_stats.discovery_status.peer_store.runtime.blind_relay
system_stats.discovery_status.peer_store.blind_relay_quality
system_stats.discovery_status.peer_store.peer_health_summary
Counter groupFields
Intake and dispositionreceived, terminal, forwarded, rejected
Validation and protectioninvalid_signature, envelope_too_large, ttl_exhausted, no_route, invalid_endpoint, loop_detected, replay_dropped, timestamp_rejected, rate_limited, quarantined, quarantine_started
Transport and retrybackpressure_dropped, forward_failed, retry_attempted, retry_succeeded, retry_exhausted
Synthetic probesprobe_attempted, probe_succeeded, probe_failed, two_hop_probe_attempted, two_hop_probe_succeeded, two_hop_probe_failed
Real delivery and freshnessverified_client_onion_deliveries, last_verified_client_onion_delivery_at, last_accepted_at, last_event_at

These counters describe node operation. They are not message logs or billing data, and they do not prove that any particular conversation took place.

Evidence quality

The quality summary keeps four kinds of evidence apart: accepted opaque relay work, synthetic reachability probes, synthetic two-hop probes and terminal-signed receipts for client-originated deliveries. real_relay_ready requires a fresh, authenticated client delivery receipt. Synthetic evidence is never reported as user traffic.

statusMeaning
idleNo relay or probe activity yet
observingSome evidence exists, but readiness is not established
staleEarlier successful evidence is no longer fresh
readyFresh accepted work or a qualifying proof, with no active transport problem
protectingAbuse protections have triggered while the relay keeps working
degradedForwarding or probe failures need investigation
attentionBackpressure or exhausted retries need prompt operator attention

proof_scope names the strongest evidence seen: client_message_delivery, relay_acceptance, message_delivery, control_plane, single_hop_control_plane, attempted or none.

Peer health without traffic relationships

peer_health_summary lets operators find a failing or quarantined peer without seeing who communicates with whom.

Included: a shortened node ID prefix, coarse health and descriptor state, gossip and route-success freshness, aggregate route success and failure counts, quarantine state, time remaining and coarse reason buckets.

Never included: full node public keys, route IDs, endpoint lists, encrypted blobs or payload hashes, message IDs, receiver identities, client IP addresses, destinations, DNS data or social-graph edges.

Source map

LayerPath in the node repositoryRole
Relay orchestrationcrates/aeronyx-server/src/api/chat_peer/blind_relay.rsRequest gate, crypto admission, authentication, loop and TTL checks, replay outcomes, forwarding and terminal handling
HTTP surface and limitscrates/aeronyx-server/src/api/chat_peer.rsRoutes, body and in-flight limits, error codes and reason buckets
Abuse policycrates/aeronyx-server/src/api/chat_peer_abuse_guard.rsPrevious-hop rate limits, failure score and quarantine
Replay registrycrates/aeronyx-server/src/api/chat_peer_replay.rsIn-memory registry and the stored-response format
Durable replay storecrates/aeronyx-server/src/services/chat_relay_blind_route_store.rsSQLite route reservations and responses
Retry policycrates/aeronyx-server/src/api/chat_peer_retry.rsRetryable versus definite failures and retry timing
Counters and qualitycrates/aeronyx-server/src/services/peer_store.rs and services/peer_store/status.rsAggregate counters, readiness and proof classification
Health reportingcrates/aeronyx-server/src/api/vpn_health.rs and management/reporter.rsLocal health JSON and the heartbeat payload

The node software is open source under AGPL-3.0 at github.com/AeroNyxNetwork/AeroNyx. See Node discovery and encrypted multi-hop relay for how routes are built.

<!-- faq:start -->

Frequently asked questions

Can a relay node read or log the traffic it forwards?

No. A relay checks signed routing metadata and forwards an opaque envelope. It keeps only aggregate counters, never payloads, route histories or receiver identities.

What happens when a node retries the same route?

If the first attempt has finished, the node replays its stored response and does not deliver anything a second time. If the first attempt is still running, the node returns a retryable route_in_flight. A different request under the same route ID is rejected as replay_conflict.

Does replay protection survive a node restart?

Yes, when the node's chat relay store is enabled. Route reservations and completed responses are then kept in SQLite. Without that store, the node uses an in-memory registry that is cleared on restart.

Why is a peer quarantined?

A peer is quarantined for five minutes after sending more than 120 requests in 60 seconds, or after 12 scored validation failures in five minutes. Timeouts, lost acknowledgements and duplicate retries do not count as failures.

Why does my node return backpressure under load?

All of its relay crypto slots were busy. A node allows about half its CPU threads (never fewer than 4, at most 8) for signature and onion work and rejects excess requests immediately instead of queuing them.

<!-- faq:end -->