mse

package
v0.0.0-...-f2e5167 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: GPL-3.0 Imports: 13 Imported by: 0

Documentation

Overview

Package mse implements Message Stream Encryption / Protocol Encryption (the Vuze/Azureus MSE spec) - the obfuscation handshake every mainstream BitTorrent client ships. It wraps a peer connection in RC4 after a Diffie-Hellman exchange so the stream carries no plaintext "\x13BitTorrent protocol" banner, which is what lets us talk to the slice of a swarm configured to REQUIRE encryption (those peers accept our TCP connect and then hang up on a plaintext handshake - seen live as EOF/connection-reset handshake failures, see docs/adr/0025).

RC4 and a 768-bit DH group are cryptographically weak; that is fine and unfixable here - MSE is camouflage against DPI and a compatibility requirement, not confidentiality (every byte already rides the user's WireGuard tunnel for that). The spec is frozen; interop means implementing it exactly as written, weak primitives included.

Index

Constants

View Source
const (
	CryptoPlaintext uint32 = 0x01
	CryptoRC4       uint32 = 0x02
)

crypto_provide / crypto_select bits.

Variables

This section is empty.

Functions

func Client

func Client(conn net.Conn, skey [20]byte, provide uint32, ia []byte) (net.Conn, error)

Client runs the initiator side of the MSE handshake on conn for the torrent identified by skey (its info hash). provide is the crypto_provide bitfield (CryptoRC4, optionally |CryptoPlaintext); ia is the initial payload tucked encrypted into the last handshake message (typically the plaintext BitTorrent handshake, saving a round trip; nil is valid and means the caller sends it itself over the returned conn). On success the returned net.Conn speaks whichever stream the peer selected - RC4 or plaintext - transparently; the caller's protocol logic is unchanged. On error the conn is NOT closed (the caller owns it) but its stream is poisoned: a plaintext retry needs a fresh dial.

func Establish

func Establish(ctx context.Context, dial func(context.Context) (net.Conn, error), skey [20]byte, policy Policy, hsTimeout time.Duration) (net.Conn, error)

Establish dials a peer and negotiates the configured encryption on the resulting connection - the ONE place the try-encrypted/fall-back-to-plain decision lives, shared by every outbound dial site (worker, metadata fetch, outbound seeding). dial is called once, or twice under PolicyPrefer when the peer turns out not to speak MSE: a failed MSE attempt has already written key-exchange bytes down the stream, so a plaintext retry NEEDS the fresh dial. A dial failure itself is never retried here - encryption can't fix an unreachable address, and the caller's failure accounting (the per-IP bench) must see it as one attempt.

hsTimeout bounds only the MSE handshake (deadline set and cleared around it); the dial's own timeout lives inside the dial closure, and the caller's later protocol handshake keeps managing its own deadlines on the returned conn exactly as before.

func Server

func Server(conn net.Conn, lookup func(req2 [20]byte) ([20]byte, bool)) (net.Conn, [20]byte, error)

Server runs the receiver side of the handshake. lookup maps the wire's obfuscated torrent identifier - HASH('req2', SKEY), recoverable by xoring the received value with HASH('req3', S) - back to a known info hash; returning false rejects the connection (an unknown torrent). It returns the negotiated conn, the matched info hash, and any initial payload the initiator embedded (already decrypted, served as the conn's first read bytes). Not wired into the inbound Router yet (phase 2, docs/adr/0025); today it exists so the client side can be tested against a real counterpart and so fake peers in other packages' tests can require encryption.

Types

type Policy

type Policy string

Policy is the outbound encryption stance, configured as peer.encryption. The values mirror qBittorrent's modes.

const (
	// PolicyPrefer tries the MSE handshake first (providing RC4 and
	// plaintext framing, so one dial serves every MSE-speaking peer
	// regardless of its cipher preference) and falls back to a plain
	// connection when the peer doesn't speak MSE at all.
	PolicyPrefer Policy = "prefer"
	// PolicyRequire only talks to MSE peers with RC4; no fallback.
	PolicyRequire Policy = "require"
	// PolicyDisabled never attempts MSE - the pre-encryption behavior.
	// The empty string behaves the same, so zero-value configs (tests
	// building config.Peer literals) keep their historical behavior;
	// config.Defaults ships PolicyPrefer.
	PolicyDisabled Policy = "disabled"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL