mirror of
https://github.com/transmission/transmission.git
synced 2026-09-27 10:02:29 +02:00
* feat: add BEP 55 message encode/decode helpers and introducer store Adds bep55-holepunch.cc/h with encode/decode for MsgRendezvous, MsgConnect, and MsgError (IPv4: 12 bytes, IPv6: 24 bytes), and bep55-introducer-store.h for tracking PEX-provenance relay candidates per swarm. Design notes: - Always emits the 12/24-byte form (err_code=0 appended) even for non-error messages. The spec allows an 8/20-byte form without err_code, but libtorrent always emits 12/24 and anacrolix's decoder expects it. Strict spec compliance breaks interop with the majority of the swarm. - Decoder accepts a zero trailing err_code on non-error messages but rejects a non-zero one. Accommodates libtorrent's always-12/24 encoding without being fully permissive. - Introducer store is owned per-swarm, not session-global. The store is accessed under the swarm lock; a global store would require additional locking we chose to avoid. Per-swarm scoping is also semantically correct — introducers are swarm-specific relationships. * feat: advertise ut_holepunch in LTEP handshake and parse incoming messages Adds the ut_holepunch advertisement to the outgoing LTEP handshake and dispatches incoming MsgRendezvous, MsgConnect, and MsgError to peer-mgr. Widens tr_peerMsgs with remote_ut_holepunch_id() and send_ltep_message() so peer-mgr can orchestrate cross-peer sends for the relay role. Design notes: - Advertisement gated on is_public() && allowsUTP(), compiled out entirely without WITH_UTP. Holepunch is a uTP mechanism (simultaneous open requires uTP's connection-less framing); advertising without uTP would be misleading. Private torrents have no PEX introducer path. - MsgError is intentionally dropped without penalising the relay. A misbehaving relay could forge errors to poison peer reputation; relying on standard connection timeouts removes that vector. Matches libtorrent's behaviour. - tr_peerMsgs widened with remote_ut_holepunch_id() and send_ltep_message() so peer-mgr can drive cross-peer LTEP sends for the relay role. The relay must send MsgConnect to a different peer than the rendezvous sender; peer-msgs cannot reach siblings, so the send is orchestrated from peer-mgr. Minimal interface change that keeps BEP 55 state out of tr_peerMsgs. * feat: implement relay, holepunch connect, and PEX provenance Implements all three BEP 55 roles in peer-mgr: - Relay: on MsgRendezvous, locate both peers and send MsgConnect to each. - Initiator/responder: on MsgConnect, fire a uTP holepunch via tr_peerMgrConnectHolepunch. The role is symmetric — both sides execute the same code path. - Provenance recording: track which relay introduced a target via PEX for future rendezvous requests. Design notes: - Holepunch connect hard-codes utp=true, no TCP fallback. TCP cannot punch through NAT (requires a listening socket on one side) and a fallback would wrongly mark the peer non-uTP-capable on failure. - Holepunch failure calls on_fruitless_connection() for retry backoff but skips set_connectable(false). Mirrors libtorrent: a NAT traversal failure says nothing about reachability via direct or future holepunch. - Only PEX-introduced relays are eligible. anacrolix allows any connected holepunch-capable peer; we follow libtorrent's conservative policy. A PEX introducer has vouched for the target, reducing relay amplification surface. - Failed holepunch does not re-trigger rendezvous (was_holepunch_attempt guard). Without this, failure loops: rendezvous → connect → fail → rendezvous. Mirrors libtorrent's !m_holepunch_mode guard. - Dual-stack relay target lookup falls back to port-only matching when address families mismatch, but only when exactly one peer matches. The single-match constraint is the safety bound: ambiguous ports are dropped rather than guessed. - Relay fires MsgConnect unconditionally once it holds live connections to both peers — no rate limiting. Per-target rate limiting and a per-swarm inflight cap were both implemented and removed: holding live connections to both peers is itself a strong admission bound. - is_in_use() (is_connected() || has_handshake()) guards initiate_connection so the normal candidate scan skips peers already engaged by a racing holepunch. Without this, start_handshake could be called with outgoing_handshake_ already set, violating TR_ASSERT(!outgoing_handshake_) at peer-mgr.h:285. tr_peerMgrConnectHolepunch has a symmetric has_handshake() check for the other direction. * feat: expose holepunch capability in RPC and frontends Adds the supports_holepunch RPC field and 'h' peer flag across GTK, Qt, and web frontends. Documents both in rpc-spec.md and Peer-Status-Text.md. Design notes: - 'h' is lowercase to avoid collision with 'H' (DHT-discovered peers). The flag table already overloads same-letter pairs for related-but- distinct meanings (D/d, U/u); h/H reuses that convention for capability vs source. * build: add BEP 55 source files to Xcode project Registers bep55-holepunch.cc/h, bep55-introducer-store.h, and their test files in Transmission.xcodeproj so the Mac app build includes the new extension. * fix: address BEP 55 relay review findings - Use pointer identity (target == sender) as primary ErrNoSelf guard in the rendezvous relay handler; the address comparison missed inbound peers resolved via listen_socket_address() whose ephemeral source port differs from the PEX-advertised listen port - Replace reinterpret_cast/const_cast in make_ipv4 test helper with htonl(); remove mislabeled NOLINT comments that were suppressing an unrelated warning while leaving the live const_cast warning unsuppressed - Factor the duplicated make_ipv4 helper from both bep55 test files into a shared bep55-test-utils.h header * fix: remove redundant BEP 55 self-target check * docs: clarify BEP 55 edge cases and remove what-comments * style: fix HeaderSize comment, remove forward decl and dead comments * fix: fast-retry holepunch connections on failure * fix: use fast_reconnect() to properly rewind holepunch attempt timer Instead of setting connection_attempt_time to 0, set it to now minus reconnect interval so the scheduler retries immediately. * Fix: crash when socket_ is nullptr * fix: add checks for `socket_` * code review: default to false * Update libtransmission/bep55-introducer-store.h Apply tearfur suggestion so intention is clearer. Co-authored-by: Yat Ho <lagoho7@gmail.com> * refactor: remove unnecessary _APICOMPAT quarks for new holepunch keys New quarks never had legacy camelCase names in any prior release, so no backward-compat shims are needed. * docs: clarify BEP 55 introducer store comment * refactor: use value_if<uint8_t> for ut_holepunch LTEP parsing Co-authored-by: Yat Ho <lagoho7@gmail.com> * refactor: merge two loops in find_connected_peer * refactor: pass sender by reference to tr_peerMgrHandleHolepunchRendezvous * refactor: add holepunch allow helper * refactor: extract ut holepunch support check * docs: clarify advertised peer port check * fix: prefer peer ID over address heuristic for self-rendezvous guard When result.peer_id is available, compare it against tor->peer_id() instead of using is_local_peer_endpoint(). Peer ID comparison is unambiguous; the address check can miss self during the startup window before global_address is populated asynchronously. Falls back to is_local_peer_endpoint() when the handshake did not reach peer ID exchange (e.g. connection dropped at TCP level). * docs: explain ErrNoSelf follows libtorrent's target==sender semantics BEP 55's literal wording ('target belongs to the relay') would map to an is_local_peer_endpoint(target) check, but that is unreachable: we never connect to ourselves (handshake.cc), so a self-endpoint target resolves to ErrNotConnected before reaching this guard. Document that we deliberately follow libtorrent, whose check has fired against real peers in live swarms. * fix: bound BEP 55 holepunch fast-retry and keep it uTP-only A failed holepunch reset is_holepunch_attempt_ before the fast retry, so the retry went through the normal candidate path: it could degrade to TCP (which opens no uTP NAT mapping) and, no longer flagged as a punch, sent a fresh rendezvous on failure. That closed a rendezvous->connect->fail loop. Keep the punch sticky across a bounded number of fast retries instead. While under the cap the retry stays uTP-forced (routed through tr_peerMgrConnectHolepunch) and keeps skipping the rendezvous; past the cap we give up and fall back to normal backoff. The counter resets on a successful connect. MaxHolepunchFastRetries mirrors libtorrent's effective fast_reconnect budget. * fix: remove incomplete BEP 55 dual-stack relay fallback * refactor: subsume is_holepunch_attempt_ into holepunch_retries_ Encode holepunch mode in the existing counter: 0 = inactive, >=1 = active. Removes is_holepunch_attempt_ and the fast_reconnect() timestamp hack. * Simplify find_connected_peer * refactor: remove unused overload * refactor: self-documenting bep55 constants * refactor: decode takes BufferReader * refactor: encode takes BufferWriter * refactor: use std::span * chore: housekeeping * refactor: tidy up find_connected_peer * refactor: tidy up peer_info holepunch bookkeeping - Removed `set_holepunch_attempt()` - Updated logic in `on_handshake_done()` and `tr_peerMgrConnectHolepunch()` to reflect updated bookkeeping * refactor: set utp supported if holepunch connect succeeded * refactor: remove `tr_peerMgrFindHolepunchIntroducer()` from external linkage * fix: incorrect fast retry check * fix: check holepunch id instead of pex flags Even if the peer itself did not advertise its holepunch ID in the LTEP handshake, other peers might falsely advertise this peer supports holepunch in PEX flags. * fix: always increment holepunch attempt when trying holepunch connect * fix: don't mark peer as not connectible if failed to create io Failing to create a uTP peer io object does not rule out TCP connectivity to the peer. * fix: clarify relay endpoint comment in tr_peerMgrHandleHolepunchRendezvous * fix: mark fresh BEP 55 peers as holepunch attempts * fix: clarify BEP 55 connected peer lookup comment * Remove unrelated formatting changes * Sort BEP 55 source entries * Reject trailing bytes in BEP 55 errors * docs: add BEP 55 RPC changelog entries * Use named error code when decoding holepunch messages Co-authored-by: Yat Ho <lagoho7@gmail.com> --------- Co-authored-by: Cœur <coeur@gmx.fr> Co-authored-by: Yat Ho <lagoho7@gmail.com>
VOLUNTEERS WANTED
- Qt developers and translators are needed
- If you find a bug, please report it at https://github.com/transmission/transmission
ABOUT TRANSMISSION-QT
Transmission-qt is a GUI for Transmission loosely based on the GTK+ client.
This is the only Transmission client that can act as its own self-contained
session (as the GTK+ and Mac clients do), and can also connect to a remote
session (as the web client and transmission-remote terminal client do).
Use Case 1: If you like to run BitTorrent for awhile from your desktop,
then the Mac, GTK+, and Qt clients are a good match.
Use Case 2: If you like to leave BitTorrent running nonstop on your
computer or router, and want to control it from your desktop or
from a remote site, then transmission-remote and the web and Qt clients
are a good match.
To use the Qt client as a remote, in the menu go to Edit > Change Session
The Qt client is also the most likely to wind up running on Windows,
though that's not a high priority at the moment...
BUILDING ON WINDOWS
rb07 has a writeup of this on the Transmission wiki:
https://trac.transmissionbt.com/wiki/BuildingTransmissionQtWindows
BUILDING ON MACOS
nnc has a writeup of this on the Transmission wiki:
https://trac.transmissionbt.com/wiki/BuildingTransmissionQtMac
BUILDING ON UNIX
1. Prerequisites: Qt >= 4.8 and its development packages
2. Build Transmission as normal
3. In the qt/ directory, type "qmake qtr.pro" (or "qmake-qt4 qtr.pro", or "qmake-qt5 qtr.pro")
4. In the qt/ directory, type "make"
5. In the qt/ directory, as root, type "INSTALL_ROOT=/usr make install"
(Feel free to replace /usr with /usr/local or /opt or whatever)