{"id":51268491,"url":"https://github.com/viperadnan-git/sudoratio","last_synced_at":"2026-06-29T16:30:51.029Z","repository":{"id":356008738,"uuid":"1230558222","full_name":"viperadnan-git/sudoratio","owner":"viperadnan-git","description":"Self-hosted BitTorrent tracker-protocol simulator and research toolkit with a modern web UI.","archived":false,"fork":false,"pushed_at":"2026-05-06T07:03:47.000Z","size":283,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-06T09:09:58.696Z","etag":null,"topics":["bittorrent","educational","protocol","protocol-analysis","react","research","rust","security-research","self-hosted","simulation","tracker"],"latest_commit_sha":null,"homepage":"https://hub.docker.com/r/viperadnan/sudoratio","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/viperadnan-git.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T05:38:16.000Z","updated_at":"2026-05-06T07:03:51.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/viperadnan-git/sudoratio","commit_stats":null,"previous_names":["viperadnan-git/sudoratio"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/viperadnan-git/sudoratio","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viperadnan-git%2Fsudoratio","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viperadnan-git%2Fsudoratio/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viperadnan-git%2Fsudoratio/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viperadnan-git%2Fsudoratio/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/viperadnan-git","download_url":"https://codeload.github.com/viperadnan-git/sudoratio/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/viperadnan-git%2Fsudoratio/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34935223,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-29T02:00:05.398Z","response_time":58,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bittorrent","educational","protocol","protocol-analysis","react","research","rust","security-research","self-hosted","simulation","tracker"],"created_at":"2026-06-29T16:30:48.484Z","updated_at":"2026-06-29T16:30:51.023Z","avatar_url":"https://github.com/viperadnan-git.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"web/public/sudoratio.png\" alt=\"sudoratio\" width=\"128\" height=\"128\" /\u003e\n\n# sudoratio\n\n**A self-hosted BitTorrent tracker-protocol simulator and research toolkit, with a modern web UI.**\n\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE)\n[![Rust](https://img.shields.io/badge/rust-1.93+-orange.svg?style=flat-square)](https://www.rust-lang.org)\n[![Build](https://img.shields.io/github/actions/workflow/status/viperadnan-git/sudoratio/docker.yml?branch=main\u0026style=flat-square\u0026label=build)](https://github.com/viperadnan-git/sudoratio/actions/workflows/docker.yml)\n[![Docker Image Size](https://img.shields.io/docker/image-size/viperadnan/sudoratio/latest?style=flat-square\u0026logo=docker\u0026label=image%20size)](https://hub.docker.com/r/viperadnan/sudoratio)\n\n[Quick Start](#quick-start) · [Configuration](#configuration) · [Client Profiles](#client-profiles) · [Architecture](#architecture) · [API](#http-api) · [Development](#development)\n\n\u003c/div\u003e\n\n---\n\nsudoratio is a research-oriented reference implementation of the BitTorrent tracker-announce and peer-wire handshake protocols. It speaks to trackers on behalf of `.torrent` files you load in, exercising the full announce flow with a configurable bandwidth model — without ever transferring real piece data. It also runs a TCP listener that completes BEP-3 handshakes and dials outbound peers from announce responses, providing a controlled environment to study how trackers and peers observe a participant whose announce traffic is decoupled from actual data exchange.\n\nIt is intended for **protocol learners, tracker operators stress-testing their own detection heuristics, and security researchers studying BitTorrent client fingerprinting**. Built in Rust as a single static binary with an embedded SPA and a typed HTTP API.\n\n## Highlights\n\n- **Single binary, no runtime deps.** Rust workspace compiled to a stripped distroless image; the Vite/React SPA is baked into the binary at build time via `rust-embed`.\n- **Realistic peer presence.** TCP listener that completes BEP-3 handshakes, paired with an outbound dialer fired against the tracker's `peers` response. Silent after handshake by design — no advertised pieces it won't serve.\n- **Per-torrent bandwidth simulation.** Each torrent samples a cap inside `[min, max]` after every announce, with per-tick ±10% jitter for natural variance and hard zero-gating when the swarm has no seeders (download) or no leechers (upload).\n- **Layered client profiles.** 24 bundled client/version pairs, plus a TOML schema for adding your own variants on top of any bundled client family.\n- **Embeddable core.** `sudoratio-core` is a standalone crate; `sudoratio-server` is a thin axum wrapper. Build your own frontend or embed the engine in a desktop app.\n- **Persistent sessions.** Torrent state, announce traces, and user-registered profiles survive restarts via SQLite + a small filesystem layout under `--config-dir`.\n- **BEP-27 compliant.** Never participates in DHT, PEX, or LSD — they're forbidden for private torrents and adding them would be a fingerprint, not a feature.\n\n## Quick Start\n\n### Docker (recommended)\n\n```bash\necho \"SUDORATIO_PASSWORD=$(openssl rand -hex 24)\" \u003e .env\ndocker compose up -d\n```\n\nOpen \u003chttp://localhost:8787\u003e and sign in with the password you set.\n\nThe compose file pulls `viperadnan/sudoratio:latest` from Docker Hub by default. To pull from GitHub Container Registry instead, set `SUDORATIO_IMAGE=ghcr.io/viperadnan-git/sudoratio:latest` in `.env`. Either path also accepts a local rebuild via `docker compose up -d --build`.\n\n### From source\n\n```bash\nSUDORATIO_BUILD_WEB=1 cargo build --release --bin sudoratio-server\n\n./target/release/sudoratio-server \\\n  --config-dir ./data \\\n  --listen 127.0.0.1:8787 \\\n  --password changeme\n```\n\n## Configuration\n\nAll flags accept the matching `SUDORATIO_*` environment variable. The most useful knobs:\n\n| Flag | Env | Default | Purpose |\n| --- | --- | --- | --- |\n| `--listen` | `SUDORATIO_LISTEN` | `0.0.0.0:8787` | HTTP API + UI bind address |\n| `--config-dir` | `SUDORATIO_CONFIG_DIR` | `./data` | session.sqlite3, config.json, clients/*.toml live here |\n| `--peer-listen` | `SUDORATIO_PEER_LISTEN` | `[::]:51413` | BT peer listener (dual-stack v4+v6); the bound port is what gets announced to trackers (empty disables; `:0` picks an ephemeral port) |\n| `--announce-port` | `SUDORATIO_ANNOUNCE_PORT` | bound port | Advanced: override the `port=` sent to trackers when port-mapped NAT exposes a different external port |\n| `--max-upload-speed` | `SUDORATIO_MAX_UPLOAD_SPEED` | profile | Cap on simulated upload (bytes/s) |\n| `--upload-ratio-target` | `SUDORATIO_UPLOAD_RATIO_TARGET` | profile | Stop simulating uploads past this ratio |\n| `--max-active-torrents` | `SUDORATIO_MAX_ACTIVE_TORRENTS` | profile | Concurrency ceiling |\n| `--pause-torrent-with-zero-leechers` | `SUDORATIO_PAUSE_TORRENT_WITH_ZERO_LEECHERS` | `true` | Skip torrents nobody is leeching |\n| `--max-concurrent-announces` | `SUDORATIO_MAX_CONCURRENT_ANNOUNCES` | `0` | Tracker HTTP fan-out limit |\n\nRun `sudoratio-server --help` for the full set, including HTTP client tuning (timeouts, pool sizing, redirects, keepalive). Most of these knobs are also live-tunable through `PATCH /api/config` without a restart.\n\n\u003e **Connectability.** Trackers and peers reach you on the peer-listener port. For that to work it must be open at every layer between the public internet and the process: cloud security list (Oracle VCN / AWS SG / etc.), host firewall (`ufw` / `iptables`), router port-forward (home networks), and any container port mapping. If your ISP uses **CGNAT** (common on mobile/4G/5G and some fiber), no amount of port-forwarding can make you connectable — you'll need a VPS, a VPN with port-forwarding, or to accept reduced peer reachability.\n\n## Client Profiles\n\nProfiles control how sudoratio emulates a real BitTorrent client on every announce — peer-id format, tracker key generator, query parameter order, HTTP headers, URL-encoding rules, and reserved handshake bits. They're declarative TOML files at `sudoratio-core/src/profile/bundled/files/*.toml`.\n\n### Bundled\n\n| Family | Variants |\n| --- | --- |\n| qBittorrent | 3.3.16, 4.0.4, 4.1.9, 4.2.5, 4.3.9, 4.4.5, 4.5.4, 4.6.7, 5.2.0 |\n| Transmission | 2.82, 2.92, 2.93, 2.94, 3.00, 4.05, 4.1.1 |\n| Deluge | 1.3.15, 2.0.3, 2.1.1, 2.2.0 |\n| rTorrent | 0.9.6 / libtorrent 0.13.6, 0.16.11 |\n| BiglyBT | 4.0.0.0 |\n| Vuze | 5.7.5.0 (project frozen) |\n| BitTorrent Classic | 7.10.3_44429 |\n| uTorrent | 3.2.2_28500, 3.5.4_44498 |\n| Leap | 2.6.0.1 (discontinued) |\n\n24 variants across 8 client families. Each profile id is `client@version` (e.g. `qbittorrent@5.2.0`).\n\n### Schema model\n\nOne TOML file per family. The file declares the **base** config (query template, header set, peer-id algorithm, key generator, URL encoder) plus N `[[variant]]` blocks — one per shipped version — that overlay deltas on top of the base via RFC 7396 JSON Merge Patch (with a strategic-merge `headers_patch` for header upserts). Adding a new qBittorrent version is a 5-line `[[variant]]` block that supplies just the fields that changed (peer-id pattern, User-Agent string).\n\nUsers can register their own client docs through `POST /api/clients`, or extend a bundled client by posting a doc that contains *only* `[[variant]]` blocks against an existing client name (for example, adding `qbittorrent@5.3.0` once it ships, without re-shipping the binary). User extensions live alongside bundled variants under the same client tile in the UI.\n\n## Persistence\n\n```\n\u003cconfig-dir\u003e/\n├── config.json              # EngineConfig snapshot, written on every PATCH /api/config\n├── session.sqlite3          # torrent rows + last 64 announce traces per torrent\n└── clients/\n    └── \u003cclient\u003e.toml        # one file per user-registered client doc\n```\n\nThe Docker compose recipe mounts this directory as the `sudoratio-data` named volume — back it up to preserve torrent state, identity caches, and user profiles across container rebuilds.\n\n## Architecture\n\n```\n┌──────────────────────────┐      ┌──────────────────────────────┐\n│  web/  (React + Vite)    │  →   │  sudoratio-server  (axum)    │\n│  embedded via rust-embed │      │  auth · routes · sqlite      │\n└──────────────────────────┘      └────────────┬─────────────────┘\n                                               │\n                                  ┌────────────▼─────────────────┐\n                                  │  sudoratio-core              │\n                                  │  engine · scheduler · wire   │\n                                  │  announce · bandwidth · BT   │\n                                  └──────────────────────────────┘\n```\n\n- **`sudoratio-core`** — the engine. Owns the announce scheduler, bandwidth simulator, BT wire protocol (handshake responder, outbound dialer), profile registry, and torrent state. No HTTP, no UI, no global state.\n- **`sudoratio-server`** — axum router exposing `/api/{torrents,clients,stats,config,health,auth}`, password-derived bearer auth, per-row SQLite persistence, and the embedded SPA.\n- **`web`** — TanStack Router + React 19 + Tailwind v4, built with Vite and Biome, served from the binary in production.\n\n## Protocol coverage\n\n| BEP | Title | Status | Why |\n| --- | --- | --- | --- |\n| BEP-3 | The BitTorrent Protocol | implemented | Tracker announce, peer handshake, swarm gating. |\n| BEP-7 | IPv6 tracker extension | partial | We parse `peers6` from announce responses; no v6 listener yet. |\n| BEP-10 | Extension Protocol (LTEP) | declared | Reserved bit set on handshake to match real clients; we don't reciprocate extended messages. |\n| BEP-12 | Multitracker Metadata | implemented | Per-session tier shuffle + success-promotion to head of tier. |\n| BEP-23 | Tracker peers list (compact) | implemented | Both `peers` (compact 6-byte) and the BEP-3 dict form are decoded. |\n| BEP-27 | Private Torrents | respected | We never participate in DHT, PEX, or LSD — running them on `private=1` torrents would be a fingerprint that no compliant client produces. |\n| BEP-5 | Mainline DHT | out of scope | Forbidden by BEP-27 on private torrents (the whole target audience). |\n| BEP-9 / BEP-11 | Metadata exchange / PEX | out of scope | Not useful without a real wire-protocol backend, and BEP-11 is forbidden on private torrents. |\n| BEP-14 | LSD | out of scope | Forbidden by BEP-27. |\n| BEP-15 / BEP-29 / BEP-52 | UDP tracker / uTP / v2 | out of scope | Outside the project's scope (HTTP-only, no piece exchange). |\n\n## Behavioural-fidelity status\n\nFor research and self-testing purposes, sudoratio's announce-side behaviour matches the on-the-wire output of the emulated client closely enough to pass passive announce-log inspection on a typical private tracker. It is **not** a complete behavioural twin: known gaps that an active or sophisticated tracker can use to distinguish it from a real client include JA3/JA4 TLS Client Hello fingerprinting (the underlying `rustls` handshake differs from the libcurl/OpenSSL stacks real clients ship with) and per-tracker announce-timing entropy (exact-cadence announces over long windows are themselves a fingerprint).\n\nTracker operators evaluating their own detection posture should treat these gaps as the easiest tells and the highest-signal places to look. Researchers comparing tracker-side anti-cheat heuristics should not expect this implementation to evade adversarial trackers and should design experiments accordingly.\n\n## HTTP API\n\nJSON under `/api/v1`, behind a bearer token issued by `POST /api/v1/auth/login`.\n\n| Method | Path | Notes |\n| --- | --- | --- |\n| `GET` | `/api/v1/health` | Liveness |\n| `GET` / `PATCH` | `/api/v1/config` | Live engine config (PATCH applies in-process) |\n| `GET` / `POST` | `/api/v1/clients` | List variants / register a client doc |\n| `GET` | `/api/v1/clients/{client}/source` | Raw TOML for a client family |\n| `DELETE` | `/api/v1/clients/{client}` | Remove user variants of a client (bundled stay) |\n| `POST` | `/api/v1/clients/variants/{id}/activate` | Activate one variant by `client@version` |\n| `GET` / `POST` | `/api/v1/torrents` | List with live stats / upload `.torrent` (multipart) |\n| `GET` / `PATCH` / `DELETE` | `/api/v1/torrents/{info_hash}` | Single torrent lifecycle |\n| `POST` | `/api/v1/torrents/{info_hash}/{pause,resume,announce}` | Manual control |\n| `GET` | `/api/v1/torrents/{info_hash}/announces` | Announce trace history (`?limit=\u0026offset=`) |\n| `GET` | `/api/v1/stats` | Aggregate counters |\n\n## Embeddable core\n\nThe engine is a separate crate. To embed it in your own program:\n\n```rust\nuse sudoratio_core::{Engine, EngineConfig};\n\nlet engine: std::sync::Arc\u003cEngine\u003e = Engine::new(EngineConfig::default());\nengine.register_builtin_client(QBT_TOML).await?;\nengine.set_active_profile(\"qbittorrent@5.2.0\".into()).await?;\nengine.start_peer_listener(\"0.0.0.0:0\".parse()?).await?;\n\n// add / pause / resume / remove torrents:\nlet id = engine.add_torrent_metainfo(meta).await?;\nengine.pause_torrent(id).await?;\nengine.announce_torrent(id, AnnounceEvent::None).await?;\n\n// subscribe to announce traces (for your own persistence layer):\nlet mut rx = engine.subscribe_announces();\nwhile let Some((tid, trace)) = rx.recv().await {\n    persist(tid, trace);\n}\n```\n\nPublic API surface lives on `Engine` — the crate's lib.rs re-exports the few types you need (`AnnounceEvent`, `MetainfoTorrent`, `Torrent`, `EngineConfig`, etc.). `parse_metainfo` and `parse_client_doc` are exposed for integrations that don't go through the engine handle.\n\n## Development\n\n```bash\n# Backend\ncargo run -p sudoratio-server -- --config-dir ./data --password dev\n\n# Frontend (dev server proxies /api to :8787)\ncd web \u0026\u0026 bun install \u0026\u0026 bun run dev\n```\n\nVerification:\n\n```bash\ncargo test --workspace\ncargo clippy --workspace --all-targets -- -D warnings\ncd web \u0026\u0026 bun run tsc --noEmit\n```\n\nThe frontend is rebuilt into the release binary by `sudoratio-server/build.rs` whenever `SUDORATIO_BUILD_WEB=1` is set or a release profile is in use.\n\n## Disclaimer\n\nsudoratio is published for **educational and research purposes only**. It exists to study how the BitTorrent tracker protocol, peer-wire handshake, and tracker-side anti-cheat heuristics behave when announce traffic is decoupled from real piece exchange — a useful reference implementation for protocol learners, tracker operators evaluating their own detection posture, and security researchers exploring BitTorrent client fingerprinting.\n\nThe author does not endorse using this software to cheat ratio on private trackers, evade account requirements, or otherwise violate the terms of service of any tracker, public or private. Ratio manipulation is against the rules of essentially every private tracker and can result in warnings, throttling, or permanent bans — sometimes across linked-tracker networks. No detection-avoidance guarantees are made or implied.\n\nYou are solely responsible for how you use this software and for ensuring your usage complies with the rules of any tracker you connect it to and the laws of your jurisdiction. The author accepts no liability for account bans, lost upload credit, legal consequences, or any other damages arising from use of this project.\n\n## License\n\n[MIT](LICENSE).\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fviperadnan-git%2Fsudoratio","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fviperadnan-git%2Fsudoratio","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fviperadnan-git%2Fsudoratio/lists"}