From dd41f1961fcad47c2ad79c56a62ec2b64b12cb8b Mon Sep 17 00:00:00 2001 From: Alexander Date: Sat, 15 Aug 2026 00:17:09 +0200 Subject: [PATCH] feat(torad): upgrade librqbit to 9.0.0-rc.0 and add --bind-device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Pinned exactly (=9.0.0-rc.0) — no v9 stable exists yet, so a caret range would silently drift onto a future prerelease. Two source changes were needed for the upgrade: - `torrent_from_bytes_ext` moved to `librqbit_core::torrent_metainfo:: torrent_from_bytes` and no longer wraps the result in a `meta` field. - `peer_stats.live` / `.not_needed` are now `u32`, so the casts are redundant. v9 also adds `SessionOptions::bind_device_name`, which is the reason for the upgrade: it applies SO_BINDTODEVICE to every librqbit socket — peer connections, trackers, DHT and LSD. Binding those to a VPN interface means that when the interface goes away the sockets error out instead of falling back to the host route, giving a kernel-enforced kill switch. Exposed as --bind-device / TORAD_BIND_DEVICE; unset reproduces today's behaviour exactly. Note that `listen` stays at its default of None, so there is no listener and no uTP socket in either direction — torad is TCP-only and leech-only. That is unchanged from v8 but now written down, since incoming connections need NAT-PMP port forwarding that we have not built. Also drops torad's `nix` pin from 0.29 to 0.31.3 to match the sibling tora crate; the workspace was carrying three copies. Verified end-to-end against a real swarm rather than by compiling alone: a 755 MiB torrent added via HTTP .torrent URL, driven through pending -> downloading -> finished, with pause/resume on an active torrent, remove, and the notification stream all exercised. Evidence in .omo/evidence/task-5-torad-vpn-namespace.txt. Co-Authored-By: Claude Opus 5 --- Cargo.lock | 429 +++++++++++++++++++++------ crates/torad/Cargo.toml | 5 +- crates/torad/src/main.rs | 8 +- crates/torad/src/source/info_hash.rs | 6 +- crates/torad/src/torrents.rs | 36 ++- 5 files changed, 384 insertions(+), 100 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f0520a..26c9c49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -97,6 +97,12 @@ dependencies = [ "rustversion", ] +[[package]] +name = "arrayvec" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3fb67a6e08acf24fdeccbac2cb6ac4305825bd1f117462e0e6f2f193345ad56" + [[package]] name = "assert_cfg" version = "0.1.0" @@ -208,6 +214,8 @@ dependencies = [ "http", "http-body", "http-body-util", + "hyper", + "hyper-util", "itoa", "matchit", "memchr", @@ -216,9 +224,11 @@ dependencies = [ "pin-project-lite", "serde_core", "sync_wrapper", + "tokio", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -237,17 +247,43 @@ dependencies = [ "sync_wrapper", "tower-layer", "tower-service", + "tracing", ] [[package]] -name = "backoff" -version = "0.4.0" +name = "axum-extra" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ddb9cb1ec0a098ad4bbf9344d0713fa193ae1a80af55febcff2627b6a00c1" +checksum = "9963ff19f40c6102c76756ef0a46004c0d58957d87259fc9208ff8441c12ab96" dependencies = [ - "getrandom 0.2.17", - "instant", - "rand 0.8.6", + "axum", + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde_core", + "serde_html_form", + "serde_path_to_error", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "gloo-timers", + "tokio", ] [[package]] @@ -256,15 +292,6 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bincode" version = "2.0.1" @@ -397,6 +424,17 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "chardetng" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14b8f0b65b7b08ae3c8187e8d77174de20cb6777864c6b832d8ad365999cf1ea" +dependencies = [ + "cfg-if", + "encoding_rs", + "memchr", +] + [[package]] name = "chrono" version = "0.4.45" @@ -724,6 +762,18 @@ dependencies = [ "syn", ] +[[package]] +name = "dontfrag" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c117949f5a8b25ba471b4dfea927a07a2f8730c585532a2eb4294e18f5155397" +dependencies = [ + "cfg-if", + "libc", + "tokio", + "windows-sys 0.52.0", +] + [[package]] name = "dotenvy" version = "0.15.7" @@ -751,6 +801,15 @@ dependencies = [ "serde", ] +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.2" @@ -788,6 +847,12 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1011,6 +1076,18 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "governor" version = "0.10.4" @@ -1405,15 +1482,6 @@ dependencies = [ "serde_core", ] -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", -] - [[package]] name = "intervaltree" version = "0.2.7" @@ -1505,18 +1573,19 @@ dependencies = [ [[package]] name = "librqbit" -version = "8.1.1" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dadca8f521242010a4c846ef5f224c217009c92e272709cdc08ba9cdabe62983" +checksum = "b9451def41c18d1b8e7b68c097bcb0c9a0579cdc9b779803368a65b86cae03d2" dependencies = [ "anyhow", "arc-swap", "async-compression", "async-stream", "async-trait", - "backoff", + "axum-extra", + "backon", "base64", - "bincode 2.0.1", + "bincode", "bitvec", "byteorder", "bytes", @@ -1532,22 +1601,29 @@ dependencies = [ "librqbit-clone-to-owned", "librqbit-core", "librqbit-dht", + "librqbit-dualstack-sockets", + "librqbit-lsd", "librqbit-peer-protocol", "librqbit-sha1-wrapper", "librqbit-tracker-comms", "librqbit-upnp", + "librqbit-utp", "memmap2", "mime_guess", + "nix 0.30.1", "parking_lot", "rand 0.9.4", "regex", "reqwest", "rlimit", "serde", + "serde_derive", "serde_json", "serde_urlencoded", "serde_with", "size_format", + "socket2", + "thiserror 2.0.18", "tokio", "tokio-socks", "tokio-stream", @@ -1557,61 +1633,71 @@ dependencies = [ "urlencoding", "uuid", "walkdir", + "windows", ] [[package]] name = "librqbit-bencode" -version = "3.1.0" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "606dff526ba81e3eca33e2bb28b53afa2bc0b2c41d252333fa44e6c11abb37da" +checksum = "97490fb9b35fb575f498cc1e804b23be6bbf3603d5a2358733e5b867d1ee52e8" dependencies = [ "anyhow", + "arrayvec", + "atoi", "bytes", "librqbit-buffers", "librqbit-clone-to-owned", - "librqbit-sha1-wrapper", "serde", + "serde_derive", + "thiserror 2.0.18", ] [[package]] name = "librqbit-buffers" -version = "4.2.0" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d78c78b907d6171a7191c162b2b60db46d254ebde6a95282b77372af556c1463" +checksum = "e845dbd14174712471c3430c958d5c25e391561c2dd31f39f895bb60f63c46f0" dependencies = [ "bytes", "librqbit-clone-to-owned", "serde", + "serde_derive", ] [[package]] name = "librqbit-clone-to-owned" -version = "3.0.1" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbd1e66d773ba9c475ff89286dc1d6f9d167cbb898603797467dd0ea6844c445" +checksum = "76ef698eb8d7745f425f2b9a93e6a9d946bcb8bf84a559303e0fc3f01e8ad07c" dependencies = [ "bytes", ] [[package]] name = "librqbit-core" -version = "5.0.0" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55a02cc6fce6743ad38661ccd6fafc6cf1ae5e0106a9922836b0524dbe752378" +checksum = "a38e805aad201d96024849649174015d48a575348b35bbd927d99b47464e665e" dependencies = [ "anyhow", - "assert_cfg", "bytes", + "chardetng", "data-encoding", "directories", + "encoding_rs", "hex", "itertools", "librqbit-bencode", "librqbit-buffers", "librqbit-clone-to-owned", + "librqbit-sha1-wrapper", + "memchr", "parking_lot", "rand 0.9.4", "serde", + "serde_derive", + "thiserror 2.0.18", "tokio", "tokio-util", "tracing", @@ -1620,27 +1706,29 @@ dependencies = [ [[package]] name = "librqbit-dht" -version = "5.3.1" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7cc129194337771a86b0399956c4d9bf1cd97c5f24d14a50be38e170f76a54b" +checksum = "d59a9374b33a52a065171641ae518db97969d3db849a57824c72c53d2109918b" dependencies = [ "anyhow", - "backoff", - "byteorder", + "backon", "bytes", "chrono", "dashmap", "futures", - "hex", "indexmap 2.14.0", "leaky-bucket", "librqbit-bencode", + "librqbit-buffers", "librqbit-clone-to-owned", "librqbit-core", + "librqbit-dualstack-sockets", "parking_lot", "rand 0.9.4", "serde", + "serde_derive", "serde_json", + "thiserror 2.0.18", "tokio", "tokio-stream", "tokio-util", @@ -1648,13 +1736,49 @@ dependencies = [ ] [[package]] -name = "librqbit-peer-protocol" -version = "4.3.0" +name = "librqbit-dualstack-sockets" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a73129497b500505f33d1dc0426319b6a6a208f13fdfaae56224ab8c2346a773" +checksum = "f7189f1ff66ca75055137a38b062ba7df9691d071b7172e5090997c62eb4b4de" +dependencies = [ + "axum", + "backon", + "futures", + "libc", + "network-interface", + "socket2", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "librqbit-lsd" +version = "9.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e65ddc3b64392a71378944712c9830aac8ef98f860b180419dd42b4596e7faa" +dependencies = [ + "anyhow", + "atoi", + "bstr", + "futures", + "httparse", + "librqbit-core", + "librqbit-dualstack-sockets", + "parking_lot", + "rand 0.9.4", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "librqbit-peer-protocol" +version = "9.0.0-rc.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76ade1bbea8cf3e7eb51978b32a549787d17bbf872f3f0ed47d7c85e7377705a" dependencies = [ "anyhow", - "bincode 1.3.3", "bitvec", "byteorder", "bytes", @@ -1664,13 +1788,16 @@ dependencies = [ "librqbit-clone-to-owned", "librqbit-core", "serde", + "serde_derive", + "thiserror 2.0.18", + "tracing", ] [[package]] name = "librqbit-sha1-wrapper" -version = "4.1.0" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79373a02db73159e4de7ca5d27b6eeae2d540df66c6801db2b01c5513d087524" +checksum = "eeb119e5c05f1aa4011cf2e4dc19f83403064d2210804e2f35f06abac4e5b710" dependencies = [ "assert_cfg", "aws-lc-rs", @@ -1678,21 +1805,26 @@ dependencies = [ [[package]] name = "librqbit-tracker-comms" -version = "3.0.0" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08204944c5be677a5de8e1230e0249fce5c14abef23048e26452c6fb03f1b260" +checksum = "0ae0c520cc5f028fb50d746eaa6ba2aa082228db96c2baf1a312f9cacfceeb58" dependencies = [ "anyhow", "async-stream", + "backon", "byteorder", "futures", + "itertools", "librqbit-bencode", "librqbit-buffers", "librqbit-core", + "librqbit-dualstack-sockets", "parking_lot", "rand 0.9.4", "reqwest", "serde", + "serde_derive", + "serde_with", "tokio", "tokio-util", "tracing", @@ -1702,23 +1834,49 @@ dependencies = [ [[package]] name = "librqbit-upnp" -version = "1.0.0" +version = "9.0.0-rc.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "545aad6124c97201055983137e12a19f34acad565120c3cd30596cbd72e8fa86" +checksum = "058af0d5e2a99c567b73369455e3852c48addcf751836f08cfc25ca89dd16cc3" dependencies = [ "anyhow", "bstr", "futures", "httparse", + "librqbit-dualstack-sockets", "network-interface", "quick-xml", "reqwest", "serde", + "serde_derive", + "socket2", "tokio", "tracing", "url", ] +[[package]] +name = "librqbit-utp" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f3bfdc73944bc76cab24d5690a98816770040a654c449edf5ff2b9ba22626aa" +dependencies = [ + "bitvec", + "dontfrag", + "lazy_static", + "libc", + "librqbit-dualstack-sockets", + "metrics", + "parking_lot", + "rand 0.9.4", + "ringbuf", + "rustc-hash", + "socket2", + "thiserror 2.0.18", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "libsqlite3-sys" version = "0.37.0" @@ -1787,6 +1945,16 @@ dependencies = [ "libc", ] +[[package]] +name = "metrics" +version = "0.24.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2" +dependencies = [ + "portable-atomic", + "rapidhash", +] + [[package]] name = "mime" version = "0.3.17" @@ -1836,6 +2004,18 @@ dependencies = [ "winapi", ] +[[package]] +name = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "nix" version = "0.31.3" @@ -2033,6 +2213,15 @@ version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -2106,9 +2295,9 @@ dependencies = [ [[package]] name = "quick-xml" -version = "0.37.5" +version = "0.38.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "331e97a1af0bf59823e6eadffe373d7b27f485be8748f71471c662c1f269b7fb" +checksum = "b66c2058c55a409d601666cffe35f04333cf1013010882cec174a7467cd4e21c" dependencies = [ "memchr", "serde", @@ -2196,24 +2385,13 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" -dependencies = [ - "libc", - "rand_chacha 0.3.1", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ - "rand_chacha 0.9.0", + "rand_chacha", "rand_core 0.9.5", ] @@ -2228,16 +2406,6 @@ dependencies = [ "rand_core 0.10.1", ] -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core 0.6.4", -] - [[package]] name = "rand_chacha" version = "0.9.0" @@ -2248,15 +2416,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.17", -] - [[package]] name = "rand_core" version = "0.9.5" @@ -2272,6 +2431,15 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "rapidhash" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" +dependencies = [ + "rustversion", +] + [[package]] name = "raw-cpuid" version = "11.6.0" @@ -2405,6 +2573,17 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "ringbuf" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe47b720588c8702e34b5979cb3271a8b1842c7cb6f57408efa70c779363488c" +dependencies = [ + "crossbeam-utils", + "portable-atomic", + "portable-atomic-util", +] + [[package]] name = "rlimit" version = "0.10.2" @@ -2536,6 +2715,19 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_html_form" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2f2d7ff8a2140333718bb329f5c40fc5f0865b84c426183ce14c97d2ab8154f" +dependencies = [ + "form_urlencoded", + "indexmap 2.14.0", + "itoa", + "ryu", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.150" @@ -2549,6 +2741,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -3204,7 +3407,7 @@ dependencies = [ "clap", "colored", "hyper-util", - "nix", + "nix 0.31.3", "serde_json", "tokio", "tokio-stream", @@ -3234,6 +3437,7 @@ dependencies = [ "clap", "librqbit", "librqbit-core", + "nix 0.31.3", "reqwest", "sha1 0.10.7", "sqlx", @@ -3657,6 +3861,27 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" +dependencies = [ + "windows-core", +] + [[package]] name = "windows-core" version = "0.62.2" @@ -3670,6 +3895,17 @@ dependencies = [ "windows-strings", ] +[[package]] +name = "windows-future" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + [[package]] name = "windows-implement" version = "0.60.2" @@ -3698,6 +3934,16 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-numerics" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" +dependencies = [ + "windows-core", + "windows-link", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -3750,6 +3996,15 @@ dependencies = [ "windows_x86_64_msvc", ] +[[package]] +name = "windows-threading" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" +dependencies = [ + "windows-link", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" diff --git a/crates/torad/Cargo.toml b/crates/torad/Cargo.toml index e84806b..87efc97 100644 --- a/crates/torad/Cargo.toml +++ b/crates/torad/Cargo.toml @@ -7,8 +7,9 @@ edition = "2024" anyhow = "1.0.103" async-trait = "0.1" clap = { version = "4.6.1", features = ["derive", "env"] } -librqbit = { version = "8.1.1", default-features = false, features = ["rust-tls", "http-api-client"] } -librqbit-core = { version = "5.0.0", default-features = false } +librqbit = { version = "=9.0.0-rc.0", default-features = false, features = ["rust-tls", "http-api-client"] } +librqbit-core = { version = "=9.0.0-rc.0", default-features = false } +nix = { version = "0.31.3", features = ["user", "sched", "fs", "process"] } reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } sha1 = "0.10" sqlx = { version = "0.9.0", features = ["postgres", "runtime-tokio", "tls-rustls", "uuid"] } diff --git a/crates/torad/src/main.rs b/crates/torad/src/main.rs index 21c01a3..ac446c5 100644 --- a/crates/torad/src/main.rs +++ b/crates/torad/src/main.rs @@ -39,6 +39,12 @@ struct Args { /// (e.g. `kill $(cat /tmp/torad.pid)`). #[arg(long, env = "TORAD_PID_FILE")] pid_file: Option, + + /// Network device to bind torrent sockets to (Linux: SO_BINDTODEVICE). + /// When set, all BT peer, tracker, and DHT traffic egresses only through + /// this interface. When unset, librqbit uses default routing. + #[arg(long, env = "TORAD_BIND_DEVICE")] + bind_device: Option, } fn default_socket_path() -> PathBuf { @@ -70,7 +76,7 @@ async fn main() -> Result<()> { .context("failed to connect to Postgres")?; info!("connected to Postgres"); - let manager = TorrentManager::new(pool.clone(), download_dir) + let manager = TorrentManager::new(pool.clone(), download_dir, args.bind_device) .await .context("failed to start torrent manager")?; manager.clone().spawn_poller(); diff --git a/crates/torad/src/source/info_hash.rs b/crates/torad/src/source/info_hash.rs index d8766b2..c9e74d5 100644 --- a/crates/torad/src/source/info_hash.rs +++ b/crates/torad/src/source/info_hash.rs @@ -13,14 +13,14 @@ //! parsing it) yields the correct hash even for spec-violating files. use anyhow::{Context, Result}; -use librqbit::torrent_from_bytes_ext; +use librqbit_core::torrent_metainfo::torrent_from_bytes; use sha1::{Digest, Sha1}; /// Extracts the v1 info-hash from `.torrent` bytes. pub(super) fn from_bytes(bytes: &[u8]) -> Result { // Fast path: spec-compliant torrent, librqbit's parser handles it. - if let Ok(parsed) = torrent_from_bytes_ext::>(bytes) { - return Ok(parsed.meta.info_hash.as_string()); + if let Ok(parsed) = torrent_from_bytes(bytes) { + return Ok(parsed.info_hash.as_string()); } // Fallback: locate the `info` value's byte span and SHA1 it directly. let span = locate_info_value(bytes) diff --git a/crates/torad/src/torrents.rs b/crates/torad/src/torrents.rs index 81efc0a..69f7ac1 100644 --- a/crates/torad/src/torrents.rs +++ b/crates/torad/src/torrents.rs @@ -5,7 +5,9 @@ use std::time::{Duration, SystemTime, UNIX_EPOCH}; use anyhow::{Context, Result}; use librqbit::api::TorrentIdOrHash; -use librqbit::{AddTorrent, AddTorrentOptions, ManagedTorrent, Session, TorrentStatsState}; +use librqbit::{ + AddTorrent, AddTorrentOptions, ManagedTorrent, Session, SessionOptions, TorrentStatsState, +}; use librqbit_core::Id20; use sqlx::PgPool; use tokio::sync::{Mutex, broadcast}; @@ -52,16 +54,36 @@ pub struct TorrentManager { } impl TorrentManager { - pub async fn new(pool: PgPool, download_dir: PathBuf) -> Result> { + pub async fn new( + pool: PgPool, + download_dir: PathBuf, + bind_device: Option, + ) -> Result> { std::fs::create_dir_all(&download_dir).with_context(|| { format!( "failed to create download directory {}", download_dir.display() ) })?; - let session = Session::new(download_dir.clone()) - .await - .context("failed to create librqbit session")?; + // `bind_device` scopes every librqbit socket — peers, trackers, DHT, LSD — + // to one interface via SO_BINDTODEVICE. When the interface goes away those + // sockets error instead of falling back to the host route, which is the + // kill switch VPN mode relies on. `None` reproduces the default behaviour: + // `Session::new` is itself `new_with_opts(dir, SessionOptions::default())`. + // + // `listen` stays at its default of `None`, so there is no listener and no + // uTP socket in either direction — torad is TCP-only and leech-only. That + // is a deliberate limitation for now; incoming connections need NAT-PMP + // port forwarding, which is deferred. + let session = Session::new_with_opts( + download_dir.clone(), + SessionOptions { + bind_device_name: bind_device, + ..Default::default() + }, + ) + .await + .context("failed to create librqbit session")?; let source = SourceResolver::new().context("failed to build source resolver")?; let (events, _) = broadcast::channel(EVENT_CHANNEL_CAPACITY); Ok(Arc::new(Self { @@ -654,8 +676,8 @@ fn live_stats_from_stats(stats: &librqbit::TorrentStats) -> LiveTorrentStats { eta_seconds: eta, uploaded_bytes, upload_speed_bps: upload_bps, - peers: live.snapshot.peer_stats.live as u32, - seeds: live.snapshot.peer_stats.not_needed as u32, + peers: live.snapshot.peer_stats.live, + seeds: live.snapshot.peer_stats.not_needed, } }