Skip to content

feat(http-tracker): support I2P peers - #2059

Draft
josecelano wants to merge 9 commits into
torrust:developfrom
josecelano:2050-i2p-peer-support-reviewed
Draft

feat(http-tracker): support I2P peers#2059
josecelano wants to merge 9 commits into
torrust:developfrom
josecelano:2050-i2p-peer-support-reviewed

Conversation

@josecelano

@josecelano josecelano commented Aug 17, 2026

Copy link
Copy Markdown
Member

Supersedes

Supersedes #2050 with a rebased, GPG-signed history while preserving Frigyes Erdosi Szucs as the author of the two implementation commits.

Included

  • I2P peer announce and swarm support over the HTTP tracker.
  • GPG-signed replacement implementation commits.
  • Clippy fixes required by the current lint policy.
  • Manual review report, reproducible test evidence, and a Destination spoofing threat analysis under docs/pr-reviews-manual/pr-2050/.

Validation

  • ./contrib/dev-tools/git/hooks/pre-commit.sh --format=json
  • cargo test --package torrust-tracker-http-protocol --lib
  • linter all

Review status

This draft is intentionally not ready to merge. The manual review identifies the following required work before merge:

  1. A1 — Percent-decode Destination query values: Decode query names and values exactly once so valid percent-encoded Base64 padding such as %3D%3D is accepted without changing raw binary info_hash or peer_id handling.
  2. A2 — I2P-aware compact deserialization: Represent 32-byte I2P Destination hashes explicitly, or clearly reject them from clearnet-only parsed compact response types.
  3. A3 — Structured I2P parse errors: Preserve bounded, actionable parse-error context instead of collapsing invalid Base64, short Destinations, and certificate failures into generic InvalidParam.
  4. A4 — Bounded, structural Destination validation: Apply a compatibility-aware size limit before decoding, validate supported Certificate types and payload layouts, and never reflect a full untrusted Destination in failures.
  5. A5 — Network-scoped announce statistics: Ensure complete and incomplete describe peers reachable by the requester, while preserving intentional aggregate administrative statistics.
  6. A6 — Malformed suffixless candidates: Define a suffixless-I2P candidate rule and reject candidate values that fail validation instead of registering the requester as clearnet.
  7. A7 — Destination identity and abuse controls: Do not treat the HTTP ip query value as authenticated I2P identity. Before enabling I2P announces, either enforce a trusted transport-derived Destination on a dedicated listener or reject I2P announces until that enforcement exists. The policy must also account for fabricated-Destination spam and swarm pollution with bounded input, rate limits keyed by trusted identity/source, admission/peer and response limits, expiry/cleanup, and privacy-aware metrics.

A7 is a merge blocker. Details, acceptance criteria, and the trust-boundary deployment model are documented in docs/pr-reviews-manual/pr-2050/destination-spoofing-analysis.md and review-pass-1.md.

The contributor may use this branch as the signed/rebased base for follow-up fixes. REST API I2P representation is explicitly deferred to the REST API overhaul epic #144 and its ADR.

@codecov

codecov Bot commented Aug 17, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.64570% with 16 lines in your changes missing coverage. Please review.
✅ Project coverage is 82.18%. Comparing base (ceaf165) to head (883ab67).

Files with missing lines Patch % Lines
packages/primitives/src/peer.rs 76.74% 10 Missing ⚠️
packages/http-protocol/src/v1/requests/announce.rs 97.01% 0 Missing and 2 partials ⚠️
packages/udp-server/src/handlers/announce.rs 85.71% 0 Missing and 2 partials ⚠️
packages/http-core/src/services/announce.rs 98.03% 0 Missing and 1 partial ⚠️
...ent-repository-benchmarking/src/entry/peer_list.rs 90.90% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2059      +/-   ##
===========================================
+ Coverage    81.97%   82.18%   +0.21%     
===========================================
  Files          349      349              
  Lines        24849    25076     +227     
  Branches     24849    25076     +227     
===========================================
+ Hits         20369    20608     +239     
+ Misses        4173     4160      -13     
- Partials       307      308       +1     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds I2P peer announcing and matchmaking to the HTTP tracker through a cross-cutting peer-address model. The draft still contains unresolved parsing, validation, deserialization, statistics, and REST contract issues.

Changes:

  • Adds validated I2P destinations and network-isolated swarm coordination.
  • Encodes I2P peers in compact and non-compact HTTP responses.
  • Updates dependent packages, tests, documentation, and review evidence.

Reviewed changes

Copilot reviewed 48 out of 49 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
README.md Advertises I2P support.
project-words.txt Adds review and I2P terminology.
packages/udp-server/src/lib.rs Adapts UDP fixtures to PeerAddress.
packages/udp-server/src/handlers/announce.rs Excludes non-clearnet peers from UDP responses.
packages/udp-core/src/peer_builder.rs Wraps UDP addresses as clearnet peers.
packages/tracker-core/tests/common/test_env.rs Handles non-Copy peers.
packages/tracker-core/tests/common/fixtures.rs Updates peer fixtures.
packages/tracker-core/src/torrent/services.rs Clones generalized peer values.
packages/tracker-core/src/test_helpers.rs Updates test peers.
packages/tracker-core/src/peer_tests.rs Updates serialization fixture.
packages/tracker-core/src/announce_handler.rs Preserves I2P addresses during IP assignment.
packages/torrent-repository-benchmarking/tests/repository/mod.rs Updates benchmark tests for cloned peers.
packages/torrent-repository-benchmarking/tests/entry/mod.rs Adapts entry tests to PeerAddress.
packages/torrent-repository-benchmarking/src/entry/single.rs Supports generalized peer keys.
packages/torrent-repository-benchmarking/src/entry/peer_list.rs Excludes peers by PeerAddress.
packages/torrent-repository-benchmarking/benches/helpers/utils.rs Updates benchmark peer constant.
packages/swarm-coordination-registry/src/swarm/registry.rs Clones non-Copy announcements.
packages/swarm-coordination-registry/src/swarm/coordinator.rs Stores and isolates I2P and clearnet peers.
packages/swarm-coordination-registry/src/statistics/event/handler.rs Updates event tests for cloned peers.
packages/swarm-coordination-registry/src/lib.rs Updates swarm fixtures.
packages/swarm-coordination-registry/examples/bench_peers.rs Updates benchmark addresses.
packages/rest-api-runtime-adapter/src/v1/conversion.rs Converts peers by reference.
packages/rest-api-runtime-adapter/src/v1/adapters/torrent.rs Adapts torrent conversion call.
packages/primitives/src/peer.rs Introduces the cross-network PeerAddress.
packages/primitives/src/lib.rs Exports I2P primitives.
packages/primitives/src/i2p.rs Parses, validates, and hashes Destinations.
packages/primitives/Cargo.toml Adds Base64 and SHA-256 dependencies.
packages/http-protocol/src/v1/responses/announce/mod.rs Exports response address types.
packages/http-protocol/src/v1/responses/announce/encoding.rs Encodes I2P response forms.
packages/http-protocol/src/v1/responses/announce/data.rs Generalizes protocol peer addresses.
packages/http-protocol/src/v1/requests/announce.rs Parses I2P announce addresses.
packages/http-protocol/src/v1/query.rs Preserves = inside query values.
packages/http-protocol/Cargo.toml Adds the primitives dependency.
packages/http-core/src/services/scrape.rs Adapts scrape tests to optional IPs.
packages/http-core/src/services/announce.rs Builds I2P peer announcements.
packages/http-core/src/lib.rs Updates HTTP peer fixtures.
packages/http-core/benches/helpers/util.rs Adapts announce benchmark helpers.
packages/http-core/benches/helpers/sync.rs Updates benchmark invocation.
packages/axum-rest-api-server/tests/server/v1/contract/context/torrent.rs Adapts REST contract fixture conversion.
packages/axum-http-server/tests/server/v1/contract/for_all_config_modes/receiving_an_announce_request.rs Adds I2P HTTP contract coverage.
packages/axum-http-server/src/v1/handlers/announce.rs Maps domain I2P peers to wire responses.
packages/axum-http-server/src/v1/extractors/announce_request.rs Updates extractor expectations.
docs/pr-reviews-manual/README.md Documents manual-review artifacts.
docs/pr-reviews-manual/pr-2050/review-pass-1.md Records findings and required work.
docs/pr-reviews-manual/pr-2050/manual-test-evidence.md Records reproducible I2P tests.
docs/packages.md Documents HTTP I2P behavior.
docs/issues/open/1987-add-config-option-to-use-ip-from-announce-query-string/ISSUE.md Documents I2P address precedence.
cspell.json Excludes runtime storage from spelling checks.
Cargo.lock Locks added dependencies.
Suppressed comments (1)

packages/http-protocol/src/v1/requests/announce.rs:606

  • The full untrusted Destination is copied into InvalidParam, whose display text is returned in the bencoded failure response. An attacker can therefore reflect a very large query value into response/log output. Use a dedicated I2P error containing only a bounded reason and lengths, never the submitted Destination.
                    return Err(ParseAnnounceQueryError::InvalidParam {
                        param_name: IP.to_owned(),
                        param_value: raw_param,
                        location: Location::caller(),

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines 175 to +177
Ok(Self {
name: pair[0].to_owned(),
value: pair[1].to_owned(),
name: name.to_owned(),
value: value.to_owned(),
peers6.extend(u128::from(peer.ip).to_be_bytes());
peers6.extend(peer.port.to_be_bytes());
}
CompactPeer::I2p(hash) => peers.extend(hash),
Comment on lines +600 to +603
match I2pDestination::from_str(&raw_param) {
Ok(destination) => return Ok(Some(AnnounceAddress::I2p(destination))),
Err(_) if has_i2p_suffix => {
return Err(ParseAnnounceQueryError::InvalidParam {
Comment on lines +609 to +612
Err(_) => {}
}

Ok(None)
Comment on lines +46 to +49
let alphabet =
Alphabet::new(I2P_BASE64_ALPHABET).expect("the I2P Base64 alphabet must contain 64 unique ASCII characters");
let engine = GeneralPurpose::new(&alphabet, GeneralPurposeConfig::new());
let decoded = engine.decode(encoded).map_err(|_| ParseI2pDestinationError::InvalidBase64)?;
Comment on lines +55 to +59
let certificate_payload_length = usize::from(u16::from_be_bytes([
decoded[I2P_CERTIFICATE_LENGTH_OFFSET],
decoded[I2P_CERTIFICATE_LENGTH_OFFSET + 1],
]));
let expected_length = MIN_I2P_DESTINATION_BYTES + certificate_payload_length;
Comment on lines +71 to +72
.filter(|peer| peer::ReadInfo::get_address(peer.as_ref()) != peer_addr)
.filter(|peer| peer.peer_addr.is_i2p() == peer_addr.is_i2p());
Comment on lines +41 to +43
pub enum PeerAddress {
Clearnet(SocketAddr),
I2p(I2pPeerAddress),
@josecelano
josecelano force-pushed the 2050-i2p-peer-support-reviewed branch from c8d906f to 883ab67 Compare August 18, 2026 11:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants