diff --git a/Cargo.lock b/Cargo.lock index 8ba818230..4279ad5b9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,10 +14,10 @@ dependencies = [ [[package]] name = "aquatic_udp_protocol" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c3026324bc5073042edfbc47699cc60c1a9cf24849f5b104c18f98d2ad4175" +source = "git+https://github.com/greatest-ape/aquatic#065e007ede84de20f20983b4b504471bbda2fdf2" dependencies = [ "byteorder", + "either", ] [[package]] @@ -26,40 +26,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" -[[package]] -name = "async-trait" -version = "0.1.51" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44318e776df68115a881de9a8fd1b9e53368d7a4a5ce4cc48517da3393233a5e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "attohttpc" -version = "0.16.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdb8867f378f33f78a811a8eb9bf108ad99430d7aad43315dd9319c827ef6247" -dependencies = [ - "http", - "log", - "url", - "wildmatch", -] - -[[package]] -name = "atty" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" -dependencies = [ - "hermit-abi", - "libc", - "winapi", -] - [[package]] name = "autocfg" version = "1.0.1" @@ -201,22 +167,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" -[[package]] -name = "core-foundation" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b" - [[package]] name = "cpufeatures" version = "0.2.1" @@ -226,12 +176,6 @@ dependencies = [ "libc", ] -[[package]] -name = "data-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ee2393c4a91429dffb4bedf19f4d6abf27d8a732c8ce4980305d782e5426d57" - [[package]] name = "derive_more" version = "0.99.17" @@ -264,45 +208,10 @@ dependencies = [ ] [[package]] -name = "enum-as-inner" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c5f0096a91d210159eceb2ff5e1c4da18388a170e1e3ce948aac9c8fdbbf595" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "env_logger" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" -dependencies = [ - "atty", - "humantime", - "log", - "regex", - "termcolor", -] - -[[package]] -name = "external-ip" -version = "4.1.0" +name = "either" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2406194c5c4be3678bd7c1c128237ec589a6a3b7a3b05786971998bda7693c27" -dependencies = [ - "futures", - "http", - "hyper", - "hyper-tls", - "igd", - "log", - "rand 0.8.4", - "trust-dns-resolver", -] +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" [[package]] name = "fake-simd" @@ -337,21 +246,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - [[package]] name = "form_urlencoded" version = "1.0.0" @@ -364,9 +258,9 @@ dependencies = [ [[package]] name = "futures" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da9052a1a50244d8d5aa9bf55cbc2fb6f357c86cc52e46c62ed390a7180cf150" +checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" dependencies = [ "futures-channel", "futures-core", @@ -379,9 +273,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" +checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" dependencies = [ "futures-core", "futures-sink", @@ -389,15 +283,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79e5145dde8da7d1b3892dad07a9c98fc04bc39892b1ecc9692cf53e2b780a65" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" [[package]] name = "futures-executor" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9e59fdc009a4b3096bf94f740a0f2424c082521f20a9b08c5c07c48d90fd9b9" +checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" dependencies = [ "futures-core", "futures-task", @@ -406,17 +300,16 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28be053525281ad8259d47e4de5de657b25e7bac113458555bb4b70bc6870500" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" [[package]] name = "futures-macro" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c287d25add322d9f9abdcdc5927ca398917996600182178774032e9f8258fedd" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" dependencies = [ - "proc-macro-hack", "proc-macro2", "quote", "syn", @@ -424,24 +317,21 @@ dependencies = [ [[package]] name = "futures-sink" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "caf5c69029bda2e743fddd0582d1083951d65cc9539aebf8812f36c3491342d6" +checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" [[package]] name = "futures-task" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13de07eb8ea81ae445aca7b69f5f7bf15d7bf4912d8ca37d6645c77ae8a58d86" -dependencies = [ - "once_cell", -] +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" [[package]] name = "futures-util" -version = "0.3.12" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "632a8cd0f2a4b3fdea1657f08bde063848c3bd00f9bbf6e256b8be78802e624b" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" dependencies = [ "futures-channel", "futures-core", @@ -452,8 +342,6 @@ dependencies = [ "memchr", "pin-project-lite", "pin-utils", - "proc-macro-hack", - "proc-macro-nested", "slab", ] @@ -548,15 +436,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "hermit-abi" version = "0.1.18" @@ -572,17 +451,6 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" -[[package]] -name = "hostname" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" -dependencies = [ - "libc", - "match_cfg", - "winapi", -] - [[package]] name = "http" version = "0.2.3" @@ -616,12 +484,6 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" -[[package]] -name = "humantime" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" - [[package]] name = "hyper" version = "0.14.2" @@ -646,19 +508,6 @@ dependencies = [ "want", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "idna" version = "0.2.3" @@ -670,19 +519,6 @@ dependencies = [ "unicode-normalization", ] -[[package]] -name = "igd" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c4e7ee8b51e541486d7040883fe1f00e2a9954bcc24fd155b7e4f03ed4b93dd" -dependencies = [ - "attohttpc", - "log", - "rand 0.8.4", - "url", - "xmltree", -] - [[package]] name = "indexmap" version = "1.6.1" @@ -711,24 +547,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "ipconfig" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" -dependencies = [ - "socket2", - "widestring", - "winapi", - "winreg", -] - -[[package]] -name = "ipnet" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9" - [[package]] name = "itoa" version = "0.4.7" @@ -812,12 +630,6 @@ dependencies = [ "linked-hash-map", ] -[[package]] -name = "match_cfg" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" - [[package]] name = "matches" version = "0.1.8" @@ -887,24 +699,6 @@ dependencies = [ "twoway", ] -[[package]] -name = "native-tls" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", -] - [[package]] name = "nom" version = "5.1.2" @@ -981,39 +775,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" -[[package]] -name = "openssl" -version = "0.10.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9facdb76fec0b73c406f125d44d86fdad818d66fef0531eec9233ca425ff4a" -dependencies = [ - "bitflags", - "cfg-if 1.0.0", - "foreign-types", - "libc", - "once_cell", - "openssl-sys", -] - -[[package]] -name = "openssl-probe" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" - -[[package]] -name = "openssl-sys" -version = "0.9.66" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1996d2d305e561b70d1ee0c53f1542833f4e1ac6ce9a6708b6ff2738ca67dc82" -dependencies = [ - "autocfg", - "cc", - "libc", - "pkg-config", - "vcpkg", -] - [[package]] name = "parking_lot" version = "0.11.1" @@ -1089,18 +850,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" -[[package]] -name = "proc-macro-hack" -version = "0.5.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" - -[[package]] -name = "proc-macro-nested" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc881b2c22681370c6a780e47af9840ef841837bc98118431d4e1868bd0c1086" - [[package]] name = "proc-macro2" version = "1.0.24" @@ -1262,16 +1011,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "resolv-conf" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" -dependencies = [ - "hostname", - "quick-error", -] - [[package]] name = "ring" version = "0.16.20" @@ -1343,16 +1082,6 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - [[package]] name = "scheduled-thread-pool" version = "0.2.5" @@ -1384,29 +1113,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "security-framework" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23a2ac85147a3a11d77ecf1bc7166ec0b92febfa4461c37944e180f319ece467" -dependencies = [ - "bitflags", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19133a286e494cc3311c165c4676ccb1fd47bed45b55f9d71fbd784ad4cea6f8" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "semver" version = "1.0.4" @@ -1587,15 +1293,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "termcolor" -version = "1.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" -dependencies = [ - "winapi-util", -] - [[package]] name = "thiserror" version = "1.0.26" @@ -1672,16 +1369,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.22.0" @@ -1742,7 +1429,7 @@ dependencies = [ [[package]] name = "torrust-tracker" -version = "2.1.2" +version = "2.2.0" dependencies = [ "aquatic_udp_protocol", "binascii", @@ -1750,9 +1437,8 @@ dependencies = [ "chrono", "config", "derive_more", - "env_logger", - "external-ip", "fern", + "futures", "hex", "log", "percent-encoding", @@ -1796,51 +1482,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "trust-dns-proto" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0d7f5db438199a6e2609debe3f69f808d074e0a2888ee0bccb45fe234d03f4" -dependencies = [ - "async-trait", - "cfg-if 1.0.0", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "lazy_static", - "log", - "rand 0.8.4", - "smallvec", - "thiserror", - "tinyvec", - "tokio", - "url", -] - -[[package]] -name = "trust-dns-resolver" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ad17b608a64bd0735e67bde16b0636f8aa8591f831a25d18443ed00a699770" -dependencies = [ - "cfg-if 1.0.0", - "futures-util", - "ipconfig", - "lazy_static", - "log", - "lru-cache", - "parking_lot", - "resolv-conf", - "smallvec", - "thiserror", - "tokio", - "trust-dns-proto", -] - [[package]] name = "try-lock" version = "0.2.3" @@ -1905,12 +1546,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - [[package]] name = "unicode-xid" version = "0.2.1" @@ -2079,18 +1714,6 @@ dependencies = [ "untrusted", ] -[[package]] -name = "widestring" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168940144dd21fd8046987c16a46a33d5fc84eec29ef9dcddc2ac9e31526b7c" - -[[package]] -name = "wildmatch" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f44b95f62d34113cf558c93511ac93027e03e9c29a60dd0fd70e6e025c7270a" - [[package]] name = "winapi" version = "0.3.9" @@ -2107,45 +1730,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" -[[package]] -name = "winapi-util" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "winreg" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9" -dependencies = [ - "winapi", -] - -[[package]] -name = "xml-rs" -version = "0.8.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2d7d3948613f75c98fd9328cfdcc45acc4d360655289d0a7d4ec931392200a3" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - [[package]] name = "yaml-rust" version = "0.4.5" diff --git a/Cargo.toml b/Cargo.toml index 01e3dbb76..084a7cfb5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "torrust-tracker" -version = "2.1.2" +version = "2.2.0" license = "AGPL-3.0" authors = ["Mick van Dijke "] description = "A feature rich BitTorrent tracker." @@ -24,13 +24,11 @@ log = {version = "0.4", features = ["release_max_level_info"]} fern = "0.6" chrono = "0.4" byteorder = "1" -external-ip = "4.1.0" r2d2_sqlite = "0.16.0" r2d2 = "0.8.8" rand = "0.8.4" -env_logger = "0.9.0" config = "0.11" derive_more = "0.99" thiserror = "1.0" - -aquatic_udp_protocol = "0.1.0" +aquatic_udp_protocol = { git = "https://github.com/greatest-ape/aquatic" } +futures = "0.3.21" diff --git a/README.md b/README.md index 64f88abc6..929585c11 100644 --- a/README.md +++ b/README.md @@ -6,12 +6,14 @@ Torrust Tracker is a lightweight but incredibly powerful and feature-rich BitTor ### Features -* [X] UDP server -* [X] HTTP (optional SSL) server +* [X] Multiple UDP server and HTTP(S) server blocks for socket binding possible +* [X] Full IPv4 and IPv6 support for both UDP and HTTP(S) * [X] Private & Whitelisted mode * [X] Built-in API * [X] Torrent whitelisting * [X] Peer authentication using time-bound keys +* [X] newTrackon check supported for both HTTP, UDP, where IPv4 and IPv6 is properly handled +* [X] SQLite3 Persistent loading and saving of the torrent hashes and completed count ### Implemented BEPs * [BEP 3](https://www.bittorrent.org/beps/bep_0003.html): The BitTorrent Protocol @@ -46,22 +48,31 @@ cargo build --release * Edit the newly created config.toml file according to your liking, see [configuration documentation](https://torrust.github.io/torrust-documentation/torrust-tracker/config/). Eg: ```toml -log_level = "trace" +log_level = "info" mode = "public" db_path = "data.db" +persistence = false cleanup_interval = 600 -external_ip = "YOUR_EXTERNAL_IP" +cleanup_peerless = true +external_ip = "0.0.0.0" +announce_interval = 120 +announce_interval_min = 900 +peer_timeout = 900 +on_reverse_proxy = false -[udp_tracker] +[[udp_trackers]] +enabled = false bind_address = "0.0.0.0:6969" -announce_interval = 120 -[http_tracker] +[[udp_trackers]] +enabled = true +bind_address = "[::]:6969" + +[[http_trackers]] enabled = true bind_address = "0.0.0.0:6969" -on_reverse_proxy = false -announce_interval = 120 ssl_enabled = false +ssl_bind_address = "0.0.0.0:6868" ssl_cert_path = "" ssl_key_path = "" @@ -80,7 +91,7 @@ admin = "MyAccessToken" ``` ### Tracker URL -Your tracker announce URL will be **udp://{tracker-ip:port}** or **https://{tracker-ip:port}/announce** depending on your tracker mode. +Your tracker announce URL will be **udp://{tracker-ip:port}** and/or **http://{tracker-ip:port}/announce** and/or **https://{tracker-ip:port}/announce** depending on your bindings. In private & private_listed mode, tracker keys are added after the tracker URL like: **https://{tracker-ip:port}/announce/{key}**. ### Built-in API @@ -89,3 +100,4 @@ Read the API documentation [here](https://torrust.github.io/torrust-documentatio ### Credits This project was a joint effort by [Nautilus Cyberneering GmbH](https://nautilus-cyberneering.de/) and [Dutch Bits](https://dutchbits.nl). Also thanks to [Naim A.](https://github.com/naim94a/udpt) and [greatest-ape](https://github.com/greatest-ape/aquatic) for some parts of the code. +Further added features and functions thanks to [Power2All](https://github.com/power2all). diff --git a/src/common.rs b/src/common.rs index 0037f058f..4d2f5ec71 100644 --- a/src/common.rs +++ b/src/common.rs @@ -84,7 +84,6 @@ impl serde::ser::Serialize for InfoHash { let mut buffer = [0u8; 40]; let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap(); let str_out = std::str::from_utf8(bytes_out).unwrap(); - serializer.serialize_str(str_out) } } @@ -131,8 +130,12 @@ pub struct PeerId(pub [u8; 20]); impl PeerId { pub fn to_string(&self) -> String { let mut buffer = [0u8; 20]; - let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok().unwrap(); - String::from(std::str::from_utf8(bytes_out).unwrap()) + let bytes_out = binascii::bin2hex(&self.0, &mut buffer).ok(); + return if let Some(bytes_out) = bytes_out { + String::from(std::str::from_utf8(bytes_out).unwrap()) + } else { + "".to_string() + } } } diff --git a/src/config.rs b/src/config.rs index 429997fcc..94b37464d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,17 +17,16 @@ pub enum TrackerServer { #[derive(Serialize, Deserialize)] pub struct UdpTrackerConfig { + pub enabled: bool, pub bind_address: String, - pub announce_interval: u32, } #[derive(Serialize, Deserialize)] pub struct HttpTrackerConfig { pub enabled: bool, pub bind_address: String, - pub on_reverse_proxy: bool, - pub announce_interval: u32, pub ssl_enabled: bool, + pub ssl_bind_address: String, #[serde(serialize_with = "none_as_empty_string")] pub ssl_cert_path: Option, #[serde(serialize_with = "none_as_empty_string")] @@ -52,10 +51,16 @@ pub struct Configuration { pub log_level: Option, pub mode: TrackerMode, pub db_path: String, + pub persistence: bool, pub cleanup_interval: Option, + pub cleanup_peerless: bool, pub external_ip: Option, - pub udp_tracker: UdpTrackerConfig, - pub http_tracker: HttpTrackerConfig, + pub announce_interval: u32, + pub announce_interval_min: u32, + pub peer_timeout: u32, + pub on_reverse_proxy: bool, + pub udp_trackers: Vec, + pub http_trackers: Vec, pub http_api: HttpApiConfig, } @@ -124,36 +129,48 @@ impl Configuration { impl Configuration { pub fn default() -> Configuration { - Configuration { + let mut configuration = Configuration { log_level: Option::from(String::from("info")), mode: TrackerMode::PublicMode, db_path: String::from("data.db"), + persistence: false, cleanup_interval: Some(600), + cleanup_peerless: true, external_ip: Some(String::from("0.0.0.0")), - udp_tracker: UdpTrackerConfig { - bind_address: String::from("0.0.0.0:6969"), - announce_interval: 120, - }, - http_tracker: HttpTrackerConfig { + announce_interval: 120, + announce_interval_min: 120, + peer_timeout: 900, + on_reverse_proxy: false, + udp_trackers: Vec::new(), + http_trackers: Vec::new(), + http_api: HttpApiConfig { + enabled: true, + bind_address: String::from("127.0.0.1:1212"), + access_tokens: [(String::from("admin"), String::from("MyAccessToken"))].iter().cloned().collect(), + } + }; + configuration.udp_trackers.push( + UdpTrackerConfig{ + enabled: false, + bind_address: String::from("0.0.0.0:6969") + } + ); + configuration.http_trackers.push( + HttpTrackerConfig{ enabled: false, bind_address: String::from("0.0.0.0:6969"), - on_reverse_proxy: false, - announce_interval: 120, ssl_enabled: false, + ssl_bind_address: String::from("0.0.0.0:6868"), ssl_cert_path: None, ssl_key_path: None - }, - http_api: HttpApiConfig { - enabled: true, - bind_address: String::from("127.0.0.1:1212"), - access_tokens: [(String::from("admin"), String::from("MyAccessToken"))].iter().cloned().collect(), - }, - } + } + ); + configuration } pub fn verify(&self) -> Result<(), ConfigurationError> { // UDP is not secure for sending private keys - if (self.mode == TrackerMode::PrivateMode || self.mode == TrackerMode::PrivateListedMode) && self.get_tracker_server() == TrackerServer::UDP { + if self.mode == TrackerMode::PrivateMode || self.mode == TrackerMode::PrivateListedMode { return Err(ConfigurationError::TrackerModeIncompatible) } @@ -188,12 +205,4 @@ impl Configuration { fs::write("config.toml", toml_string).expect("Could not write to file!"); Ok(()) } - - pub fn get_tracker_server(&self) -> TrackerServer { - if self.http_tracker.enabled { - TrackerServer::HTTP - } else { - TrackerServer::UDP - } - } } diff --git a/src/database.rs b/src/database.rs index fbec824a0..82d26d6d8 100644 --- a/src/database.rs +++ b/src/database.rs @@ -1,4 +1,5 @@ -use crate::{InfoHash, AUTH_KEY_LENGTH}; +use std::collections::BTreeMap; +use crate::{InfoHash, AUTH_KEY_LENGTH, TorrentEntry}; use log::debug; use r2d2_sqlite::{SqliteConnectionManager, rusqlite}; use r2d2::{Pool}; @@ -32,6 +33,13 @@ impl SqliteDatabase { info_hash VARCHAR(20) NOT NULL UNIQUE );".to_string(); + let create_torrents_table = " + CREATE TABLE IF NOT EXISTS torrents ( + id integer PRIMARY KEY AUTOINCREMENT, + info_hash VARCHAR(20) NOT NULL UNIQUE, + completed INTEGER DEFAULT 0 NOT NULL + );".to_string(); + let create_keys_table = format!(" CREATE TABLE IF NOT EXISTS keys ( id integer PRIMARY KEY AUTOINCREMENT, @@ -43,7 +51,15 @@ impl SqliteDatabase { match conn.execute(&create_whitelist_table, NO_PARAMS) { Ok(updated) => { match conn.execute(&create_keys_table, NO_PARAMS) { - Ok(updated2) => Ok(updated + updated2), + Ok(updated2) => { + match conn.execute(&create_torrents_table, NO_PARAMS) { + Ok(updated3) => Ok(updated + updated2 + updated3), + Err(e) => { + debug!("{:?}", e); + Err(e) + } + } + } Err(e) => { debug!("{:?}", e); Err(e) @@ -57,6 +73,36 @@ impl SqliteDatabase { } } + pub async fn load_persistent_torrent_data(&self) -> Result, rusqlite::Error> { + let conn = self.pool.get().unwrap(); + let mut stmt = conn.prepare("SELECT info_hash, completed FROM torrents")?; + + let torrent_iter = stmt.query_map(NO_PARAMS, |row| { + let info_hash_string: String = row.get(0)?; + let info_hash = InfoHash::from_str(&info_hash_string).unwrap(); + let completed: u32 = row.get(1)?; + Ok((info_hash, completed)) + })?; + + let torrents: Vec<(InfoHash, u32)> = torrent_iter.filter_map(|x| x.ok() ).collect(); + + Ok(torrents) + } + + pub async fn save_persistent_torrent_data(&self, torrents: &BTreeMap) -> Result<(), rusqlite::Error> { + let mut conn = self.pool.get().unwrap(); + let db_transaction = conn.transaction()?; + + for (info_hash, torrent_entry) in torrents { + let (_seeders, completed, _leechers) = torrent_entry.get_stats(); + let _ = db_transaction.execute("INSERT OR REPLACE INTO torrents (info_hash, completed) VALUES (?, ?)", &[info_hash.to_string(), completed.to_string()]); + } + + let _ = db_transaction.commit(); + + Ok(()) + } + pub async fn get_info_hash_from_whitelist(&self, info_hash: &str) -> Result { let conn = self.pool.get().unwrap(); let mut stmt = conn.prepare("SELECT info_hash FROM whitelist WHERE info_hash = ?")?; diff --git a/src/http_api_server.rs b/src/http_api_server.rs index f87f460a4..a6bee4a14 100644 --- a/src/http_api_server.rs +++ b/src/http_api_server.rs @@ -23,6 +23,26 @@ struct Torrent<'a> { peers: Option>, } +#[derive(Serialize)] +struct Stats { + torrents: u32, + seeders: u32, + completed: u32, + leechers: u32, + tcp4_connections_handled: u32, + tcp4_announces_handled: u32, + tcp4_scrapes_handled: u32, + tcp6_connections_handled: u32, + tcp6_announces_handled: u32, + tcp6_scrapes_handled: u32, + udp4_connections_handled: u32, + udp4_announces_handled: u32, + udp4_scrapes_handled: u32, + udp6_connections_handled: u32, + udp6_announces_handled: u32, + udp6_scrapes_handled: u32, +} + #[derive(Serialize, Debug)] #[serde(tag = "status", rename_all = "snake_case")] enum ActionStatus<'a> { @@ -64,13 +84,13 @@ fn authenticate(tokens: HashMap) -> impl Filter) -> Server + Clone + Send + Sync + 'static> { // GET /api/torrents?offset=:u32&limit=:u32 // View torrent list - let t1 = tracker.clone(); + let api_torrents = tracker.clone(); let view_torrent_list = filters::method::get() .and(filters::path::path("torrents")) .and(filters::path::end()) .and(filters::query::query()) .map(move |limits| { - let tracker = t1.clone(); + let tracker = api_torrents.clone(); (limits, tracker) }) .and_then(|(limits, tracker): (TorrentInfoQuery, Arc)| { @@ -99,6 +119,65 @@ pub fn build_server(tracker: Arc) -> Server| { + async move { + let mut results = Stats{ + torrents: 0, + seeders: 0, + completed: 0, + leechers: 0, + tcp4_connections_handled: 0, + tcp4_announces_handled: 0, + tcp4_scrapes_handled: 0, + tcp6_connections_handled: 0, + tcp6_announces_handled: 0, + tcp6_scrapes_handled: 0, + udp4_connections_handled: 0, + udp4_announces_handled: 0, + udp4_scrapes_handled: 0, + udp6_connections_handled: 0, + udp6_announces_handled: 0, + udp6_scrapes_handled: 0 + }; + let db = tracker.get_torrents().await; + let _: Vec<_> = db + .iter() + .map(|(_info_hash, torrent_entry)| { + let (seeders, completed, leechers) = torrent_entry.get_stats(); + results.seeders += seeders; + results.completed += completed; + results.leechers += leechers; + results.torrents += 1; + }) + .collect(); + let stats = tracker.get_stats().await; + results.tcp4_connections_handled = stats.tcp4_connections_handled as u32; + results.tcp4_announces_handled = stats.tcp4_announces_handled as u32; + results.tcp4_scrapes_handled = stats.tcp4_scrapes_handled as u32; + results.tcp6_connections_handled = stats.tcp6_connections_handled as u32; + results.tcp6_announces_handled = stats.tcp6_announces_handled as u32; + results.tcp6_scrapes_handled = stats.tcp6_scrapes_handled as u32; + results.udp4_connections_handled = stats.udp4_connections_handled as u32; + results.udp4_announces_handled = stats.udp4_announces_handled as u32; + results.udp4_scrapes_handled = stats.udp4_scrapes_handled as u32; + results.udp6_connections_handled = stats.udp6_connections_handled as u32; + results.udp6_announces_handled = stats.udp6_announces_handled as u32; + results.udp6_scrapes_handled = stats.udp6_scrapes_handled as u32; + + Result::<_, warp::reject::Rejection>::Ok(reply::json(&results)) + } + }); + // GET /api/torrent/:info_hash // View torrent info let t2 = tracker.clone(); @@ -219,6 +298,7 @@ pub fn build_server(tracker: Arc) -> Server Arc::new(config), Err(error) => { @@ -14,10 +15,21 @@ async fn main() { } }; + // the singleton torrent tracker that gets passed to the HTTP and UDP server + let tracker = Arc::new(TorrentTracker::new(config.clone()).unwrap_or_else(|e| { + panic!("{}", e) + })); + logging::setup_logging(&config); - // the singleton torrent tracker that gets passed to the HTTP and UDP server - let tracker = Arc::new(TorrentTracker::new(config.clone())); + // load persistent torrents if enabled + if config.persistence { + info!("Loading persistent torrents into memory..."); + if tracker.load_torrents().await.is_err() { + panic!("Could not load persistent torrents.") + }; + info!("Persistent torrents loaded."); + } // start torrent cleanup job (periodically removes old peers) let _torrent_cleanup_job = start_torrent_cleanup_job(config.clone(), tracker.clone()).unwrap(); @@ -27,20 +39,44 @@ async fn main() { let _api_server = start_api_server(&config.http_api, tracker.clone()); } - // check which tracker to run, UDP (Default) or HTTP - let _tracker_server = match config.get_tracker_server() { - TrackerServer::UDP => { - start_udp_tracker_server(&config.udp_tracker, tracker.clone()).await - } - TrackerServer::HTTP => { - start_http_tracker_server(&config.http_tracker, tracker.clone()) + let (tx, rx) = tokio::sync::watch::channel(false); + let mut udp_server_handles = Vec::new(); + + // start the udp blocks + for udp_tracker in &config.udp_trackers { + // used to send kill signal to thread + + if udp_tracker.enabled { + udp_server_handles.push( + start_udp_tracker_server(&udp_tracker, tracker.clone(), rx.clone()).await + ) } - }; + } - let ctrl_c = tokio::signal::ctrl_c(); + // start the http blocks + for http_tracker in &config.http_trackers { + let _ = start_http_tracker_server(&http_tracker, tracker.clone(), true); + let _ = start_http_tracker_server(&http_tracker, tracker.clone(), false); + } + + // handle the signals here tokio::select! { - _ = _tracker_server => { panic!("Tracker server exited.") }, - _ = ctrl_c => { info!("Torrust shutting down..") } + _ = tokio::signal::ctrl_c() => { + info!("Torrust shutting down.."); + + // send kill signal + let _ = tx.send(true); + + // await for all udp servers to shutdown + futures::future::join_all(udp_server_handles).await; + + // Save torrents if enabled + if config.persistence { + info!("Saving torrents into SQL from memory..."); + let _ = tracker.save_torrents().await; + info!("Torrents saved"); + } + } } } @@ -61,7 +97,7 @@ fn start_torrent_cleanup_job(config: Arc, tracker: Arc) -> JoinHandle<()> { @@ -70,37 +106,39 @@ fn start_api_server(config: &HttpApiConfig, tracker: Arc) -> Joi tokio::spawn(async move { let server = http_api_server::build_server(tracker); - server.bind(bind_addr).await; + let _ = server.bind(bind_addr).await; }) } -fn start_http_tracker_server(config: &HttpTrackerConfig, tracker: Arc) -> JoinHandle<()> { +fn start_http_tracker_server(config: &HttpTrackerConfig, tracker: Arc, ssl: bool) -> JoinHandle<()> { let http_tracker = HttpServer::new(tracker); + let enabled = config.enabled; let bind_addr = config.bind_address.parse::().unwrap(); let ssl_enabled = config.ssl_enabled; + let ssl_bind_addr = config.ssl_bind_address.parse::().unwrap(); let ssl_cert_path = config.ssl_cert_path.clone(); let ssl_key_path = config.ssl_key_path.clone(); - tokio::spawn(async move { // run with tls if ssl_enabled and cert and key path are set - if ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { - info!("Starting HTTPS server on: {} (TLS)", bind_addr); - http_tracker.start_tls(bind_addr, ssl_cert_path.as_ref().unwrap(), ssl_key_path.as_ref().unwrap()).await; - } else { + if ssl && ssl_enabled && ssl_cert_path.is_some() && ssl_key_path.is_some() { + info!("Starting HTTPS server on: {} (TLS)", ssl_bind_addr); + http_tracker.start_tls(ssl_bind_addr, ssl_cert_path.as_ref().unwrap(), ssl_key_path.as_ref().unwrap()).await; + } + if !ssl && enabled { info!("Starting HTTP server on: {}", bind_addr); http_tracker.start(bind_addr).await; } }) } -async fn start_udp_tracker_server(config: &UdpTrackerConfig, tracker: Arc) -> JoinHandle<()> { - let udp_server = UdpServer::new(tracker).await.unwrap_or_else(|e| { +async fn start_udp_tracker_server(config: &UdpTrackerConfig, tracker: Arc, rx: tokio::sync::watch::Receiver) -> JoinHandle<()> { + let udp_server = UdpServer::new(tracker, &config.bind_address).await.unwrap_or_else(|e| { panic!("Could not start UDP server: {}", e); }); info!("Starting UDP server on: {}", config.bind_address); tokio::spawn(async move { - udp_server.start().await; + udp_server.start(rx).await; }) } diff --git a/src/torrust_http_tracker/filters.rs b/src/torrust_http_tracker/filters.rs index 24d155238..61fa20a45 100644 --- a/src/torrust_http_tracker/filters.rs +++ b/src/torrust_http_tracker/filters.rs @@ -2,6 +2,7 @@ use std::convert::Infallible; use std::net::{IpAddr, SocketAddr}; use std::str::FromStr; use std::sync::Arc; +use log::debug; use warp::{Filter, reject, Rejection}; use crate::{InfoHash, MAX_SCRAPE_TORRENTS, PeerId, TorrentTracker}; use crate::key_manager::AuthKey; @@ -19,6 +20,50 @@ pub fn with_info_hash() -> impl Filter,), Error = Rejec .and_then(info_hashes) } +/// Check for PeerId +pub fn with_peer_id() -> impl Filter + Clone { + warp::filters::query::raw() + .and_then(peer_id) +} + +/// Pass Arc along +pub fn with_auth_key() -> impl Filter,), Error = Infallible> + Clone { + warp::path::param::() + .map(|key: String| { + AuthKey::from_string(&key) + }) + .or_else(|_| async { + Ok::<(Option,), Infallible>((None,)) + }) +} + +/// Check for PeerAddress +pub fn with_peer_addr(on_reverse_proxy: bool) -> impl Filter + Clone { + warp::addr::remote() + .and(warp::header::optional::("X-Forwarded-For")) + .map(move |remote_addr: Option, x_forwarded_for: Option| { + (on_reverse_proxy, remote_addr, x_forwarded_for) + }) + .and_then(peer_addr) +} + +/// Check for AnnounceRequest +pub fn with_announce_request(on_reverse_proxy: bool) -> impl Filter + Clone { + warp::filters::query::query::() + .and(with_info_hash()) + .and(with_peer_id()) + .and(with_peer_addr(on_reverse_proxy)) + .and_then(announce_request) +} + +/// Check for ScrapeRequest +pub fn with_scrape_request(on_reverse_proxy: bool) -> impl Filter + Clone { + warp::any() + .and(with_info_hash()) + .and(with_peer_addr(on_reverse_proxy)) + .and_then(scrape_request) +} + /// Parse InfoHash from raw query string async fn info_hashes(raw_query: String) -> WebResult> { let split_raw_query: Vec<&str> = raw_query.split("&").collect(); @@ -44,12 +89,6 @@ async fn info_hashes(raw_query: String) -> WebResult> { } } -/// Check for PeerId -pub fn with_peer_id() -> impl Filter + Clone { - warp::filters::query::raw() - .and_then(peer_id) -} - /// Parse PeerId from raw query string async fn peer_id(raw_query: String) -> WebResult { // put all query params in a vec @@ -67,7 +106,7 @@ async fn peer_id(raw_query: String) -> WebResult { let peer_id_bytes = percent_encoding::percent_decode_str(raw_peer_id).collect::>(); // peer_id must be 20 bytes - if peer_id_bytes.len() > 20 { + if peer_id_bytes.len() != 20 { return Err(reject::custom(ServerError::InvalidPeerId)); } @@ -87,64 +126,54 @@ async fn peer_id(raw_query: String) -> WebResult { } } -/// Pass Arc along -pub fn with_auth_key() -> impl Filter,), Error = Infallible> + Clone { - warp::path::param::() - .map(|key: String| { - AuthKey::from_string(&key) - }) - .or_else(|_| async { - Ok::<(Option,), Infallible>((None,)) - }) -} +/// Get PeerAddress from RemoteAddress or Forwarded +async fn peer_addr((on_reverse_proxy, remote_addr, x_forwarded_for): (bool, Option, Option)) -> WebResult { + if !on_reverse_proxy && remote_addr.is_none() { + return Err(reject::custom(ServerError::AddressNotFound)) + } -/// Check for AnnounceRequest -pub fn with_announce_request() -> impl Filter + Clone { - warp::filters::query::query::() - .and(with_info_hash()) - .and(with_peer_id()) - .and(warp::addr::remote()) - .and(warp::header::optional::("X-Forwarded-For")) - .and_then(announce_request) + if on_reverse_proxy && x_forwarded_for.is_none() { + return Err(reject::custom(ServerError::AddressNotFound)) + } + + match on_reverse_proxy { + true => { + let mut x_forwarded_for_raw = x_forwarded_for.unwrap(); + // remove whitespace chars + x_forwarded_for_raw.retain(|c| !c.is_whitespace()); + // get all forwarded ip's in a vec + let x_forwarded_ips: Vec<&str> = x_forwarded_for_raw.split(',').collect(); + // set client ip to last forwarded ip + let x_forwarded_ip = *x_forwarded_ips.last().unwrap(); + + IpAddr::from_str(x_forwarded_ip).or_else(|e| { + debug!("{}", e); + Err(reject::custom(ServerError::AddressNotFound)) + }) + }, + false => Ok(remote_addr.unwrap().ip()) + } } /// Parse AnnounceRequest from raw AnnounceRequestQuery, InfoHash and Option -async fn announce_request(announce_request_query: AnnounceRequestQuery, info_hashes: Vec, peer_id: PeerId, remote_addr: Option, forwarded_for: Option) -> WebResult { - if remote_addr.is_none() { return Err(reject::custom(ServerError::AddressNotFound)) } - - // get first forwarded ip - let forwarded_ip = match forwarded_for { - None => None, - Some(forwarded_for_str) => { - forwarded_for_str.split(",").next() - .and_then(|ip_str| IpAddr::from_str(ip_str).ok()) - } - }; - +async fn announce_request(announce_request_query: AnnounceRequestQuery, info_hashes: Vec, peer_id: PeerId, peer_addr: IpAddr) -> WebResult { Ok(AnnounceRequest { info_hash: info_hashes[0], - peer_addr: remote_addr.unwrap(), - forwarded_ip, - downloaded: announce_request_query.downloaded, - uploaded: announce_request_query.uploaded, + peer_addr, + downloaded: announce_request_query.downloaded.unwrap_or(0), + uploaded: announce_request_query.uploaded.unwrap_or(0), peer_id, port: announce_request_query.port, - left: announce_request_query.left, + left: announce_request_query.left.unwrap_or(0), event: announce_request_query.event, compact: announce_request_query.compact }) } -/// Check for ScrapeRequest -pub fn with_scrape_request() -> impl Filter + Clone { - warp::any() - .and(with_info_hash()) - .and_then(scrape_request) -} - /// Parse ScrapeRequest from InfoHash -async fn scrape_request(info_hashes: Vec) -> WebResult { +async fn scrape_request(info_hashes: Vec, peer_addr: IpAddr) -> WebResult { Ok(ScrapeRequest { info_hashes, + peer_addr, }) } diff --git a/src/torrust_http_tracker/handlers.rs b/src/torrust_http_tracker/handlers.rs index 922577c85..f15b7143a 100644 --- a/src/torrust_http_tracker/handlers.rs +++ b/src/torrust_http_tracker/handlers.rs @@ -3,7 +3,7 @@ use std::convert::Infallible; use std::sync::Arc; use log::debug; use warp::{reject, Rejection, Reply}; -use warp::http::{Response, StatusCode}; +use warp::http::{Response}; use crate::{InfoHash, TorrentError, TorrentPeer, TorrentStats, TorrentTracker}; use crate::key_manager::AuthKey; use crate::torrust_http_tracker::{AnnounceRequest, AnnounceResponse, ErrorResponse, Peer, ScrapeRequest, ScrapeResponse, ScrapeResponseEntry, ServerError, WebResult}; @@ -29,33 +29,41 @@ pub async fn authenticate(info_hash: &InfoHash, auth_key: &Option, trac } /// Handle announce request -pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option, tracker: Arc,) -> WebResult { +pub async fn handle_announce(announce_request: AnnounceRequest, auth_key: Option, tracker: Arc) -> WebResult { if let Err(e) = authenticate(&announce_request.info_hash, &auth_key, tracker.clone()).await { return Err(reject::custom(e)) } - if tracker.config.http_tracker.on_reverse_proxy && announce_request.forwarded_ip.is_none() { - return Err(reject::custom(ServerError::AddressNotFound)) - } - - let peer_ip = match tracker.config.http_tracker.on_reverse_proxy { - true => announce_request.forwarded_ip.unwrap(), - false => announce_request.peer_addr.ip() - }; + debug!("{:?}", announce_request); - let peer = TorrentPeer::from_http_announce_request(&announce_request, peer_ip, tracker.config.get_ext_ip()); + let peer = TorrentPeer::from_http_announce_request(&announce_request, announce_request.peer_addr, tracker.config.get_ext_ip()); let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&announce_request.info_hash, &peer).await; - // get all peers excluding the client_addr + + // get all torrent peers excluding the peer_addr let peers = tracker.get_torrent_peers(&announce_request.info_hash, &peer.peer_addr).await; - if peers.is_none() { return Err(reject::custom(ServerError::NoPeersFound)) } // success response - let announce_interval = tracker.config.http_tracker.announce_interval; - send_announce_response(&announce_request, torrent_stats, peers.unwrap(), announce_interval) + let tracker_copy = tracker.clone(); + let is_ipv4 = announce_request.peer_addr.is_ipv4(); + + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if is_ipv4 { + status_writer.tcp4_connections_handled += 1; + status_writer.tcp4_announces_handled += 1; + } else { + status_writer.tcp6_connections_handled += 1; + status_writer.tcp6_announces_handled += 1; + } + }); + + let announce_interval = tracker.config.announce_interval; + + send_announce_response(&announce_request, torrent_stats, peers, announce_interval, tracker.config.announce_interval_min) } /// Handle scrape request -pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option, tracker: Arc,) -> WebResult { +pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option, tracker: Arc) -> WebResult { let mut files: HashMap = HashMap::new(); let db = tracker.get_torrents().await; @@ -78,23 +86,24 @@ pub async fn handle_scrape(scrape_request: ScrapeRequest, auth_key: Option std::result::Result { - if let Some(e) = r.find::() { - debug!("{:?}", e); - let reply = warp::reply::json(&ErrorResponse { failure_reason: e.to_string() }); - Ok(warp::reply::with_status(reply, StatusCode::BAD_REQUEST)) - } else { - let reply = warp::reply::json(&ErrorResponse { failure_reason: "internal server error".to_string() }); - Ok(warp::reply::with_status(reply, StatusCode::INTERNAL_SERVER_ERROR)) - } + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if scrape_request.peer_addr.is_ipv4() { + status_writer.tcp4_connections_handled += 1; + status_writer.tcp4_scrapes_handled += 1; + } else { + status_writer.tcp6_connections_handled += 1; + status_writer.tcp6_scrapes_handled += 1; + } + }); + + send_scrape_response(files) } /// Send announce response -fn send_announce_response(announce_request: &AnnounceRequest, torrent_stats: TorrentStats, peers: Vec, interval: u32) -> WebResult { +fn send_announce_response(announce_request: &AnnounceRequest, torrent_stats: TorrentStats, peers: Vec, interval: u32, interval_min: u32) -> WebResult { let http_peers: Vec = peers.iter().map(|peer| Peer { peer_id: peer.peer_id.to_string(), ip: peer.peer_addr.ip(), @@ -103,6 +112,7 @@ fn send_announce_response(announce_request: &AnnounceRequest, torrent_stats: Tor let res = AnnounceResponse { interval, + interval_min, complete: torrent_stats.seeders, incomplete: torrent_stats.leechers, peers: http_peers @@ -123,3 +133,15 @@ fn send_announce_response(announce_request: &AnnounceRequest, torrent_stats: Tor fn send_scrape_response(files: HashMap) -> WebResult { Ok(Response::new(ScrapeResponse { files }.write())) } + +/// Handle all server errors and send error reply +pub async fn send_error(r: Rejection) -> std::result::Result { + let body = if let Some(server_error) = r.find::() { + debug!("{:?}", server_error); + ErrorResponse { failure_reason: server_error.to_string() }.write() + } else { + ErrorResponse { failure_reason: ServerError::InternalServerError.to_string() }.write() + }; + + Ok(Response::new(body)) +} diff --git a/src/torrust_http_tracker/request.rs b/src/torrust_http_tracker/request.rs index 4e40b604c..0fb316671 100644 --- a/src/torrust_http_tracker/request.rs +++ b/src/torrust_http_tracker/request.rs @@ -1,23 +1,23 @@ -use std::net::{IpAddr, SocketAddr}; +use std::net::{IpAddr}; use serde::{Deserialize}; use crate::{InfoHash, PeerId}; use crate::torrust_http_tracker::Bytes; #[derive(Deserialize)] pub struct AnnounceRequestQuery { - pub downloaded: Bytes, - pub uploaded: Bytes, - pub key: String, + pub downloaded: Option, + pub uploaded: Option, + pub key: Option, pub port: u16, - pub left: Bytes, + pub left: Option, pub event: Option, pub compact: Option, } +#[derive(Debug)] pub struct AnnounceRequest { pub info_hash: InfoHash, - pub peer_addr: SocketAddr, - pub forwarded_ip: Option, + pub peer_addr: IpAddr, pub downloaded: Bytes, pub uploaded: Bytes, pub peer_id: PeerId, @@ -29,4 +29,5 @@ pub struct AnnounceRequest { pub struct ScrapeRequest { pub info_hashes: Vec, + pub peer_addr: IpAddr, } diff --git a/src/torrust_http_tracker/response.rs b/src/torrust_http_tracker/response.rs index df039a1c2..d459a6fdd 100644 --- a/src/torrust_http_tracker/response.rs +++ b/src/torrust_http_tracker/response.rs @@ -14,6 +14,7 @@ pub struct Peer { #[derive(Serialize)] pub struct AnnounceResponse { pub interval: u32, + pub interval_min: u32, //pub tracker_id: String, pub complete: u32, pub incomplete: u32, @@ -45,6 +46,8 @@ impl AnnounceResponse { let mut bytes: Vec = Vec::new(); bytes.write(b"d8:intervali")?; bytes.write(&self.interval.to_string().as_bytes())?; + bytes.write(b"e12:min intervali")?; + bytes.write(&self.interval_min.to_string().as_bytes())?; bytes.write(b"e8:completei")?; bytes.write(&self.complete.to_string().as_bytes())?; bytes.write(b"e10:incompletei")?; @@ -85,3 +88,9 @@ impl ScrapeResponse { pub struct ErrorResponse { pub failure_reason: String } + +impl ErrorResponse { + pub fn write(&self) -> String { + serde_bencode::to_string(&self).unwrap() + } +} diff --git a/src/torrust_http_tracker/routes.rs b/src/torrust_http_tracker/routes.rs index ad873e83e..4b4de722f 100644 --- a/src/torrust_http_tracker/routes.rs +++ b/src/torrust_http_tracker/routes.rs @@ -2,21 +2,21 @@ use std::convert::Infallible; use std::sync::Arc; use warp::{Filter, Rejection}; use crate::TorrentTracker; -use crate::torrust_http_tracker::{handle_announce, handle_error, handle_scrape, with_announce_request, with_auth_key, with_scrape_request, with_tracker}; +use crate::torrust_http_tracker::{handle_announce, send_error, handle_scrape, with_announce_request, with_auth_key, with_scrape_request, with_tracker}; /// All routes pub fn routes(tracker: Arc,) -> impl Filter + Clone { root(tracker.clone()) .or(announce(tracker.clone())) .or(scrape(tracker.clone())) - .recover(handle_error) + .recover(send_error) } /// GET / or / fn root(tracker: Arc,) -> impl Filter + Clone { warp::any() .and(warp::filters::method::get()) - .and(with_announce_request()) + .and(with_announce_request(tracker.config.on_reverse_proxy)) .and(with_auth_key()) .and(with_tracker(tracker)) .and_then(handle_announce) @@ -26,7 +26,7 @@ fn root(tracker: Arc,) -> impl Filter,) -> impl Filter + Clone { warp::path::path("announce") .and(warp::filters::method::get()) - .and(with_announce_request()) + .and(with_announce_request(tracker.config.on_reverse_proxy)) .and(with_auth_key()) .and(with_tracker(tracker)) .and_then(handle_announce) @@ -36,7 +36,7 @@ fn announce(tracker: Arc,) -> impl Filter,) -> impl Filter + Clone { warp::path::path("scrape") .and(warp::filters::method::get()) - .and(with_scrape_request()) + .and(with_scrape_request(tracker.config.on_reverse_proxy)) .and(with_auth_key()) .and(with_tracker(tracker)) .and_then(handle_scrape) diff --git a/src/torrust_http_tracker/server.rs b/src/torrust_http_tracker/server.rs index 90f8a84d0..69811b3d9 100644 --- a/src/torrust_http_tracker/server.rs +++ b/src/torrust_http_tracker/server.rs @@ -18,16 +18,26 @@ impl HttpServer { /// Start the HttpServer pub async fn start(&self, socket_addr: SocketAddr) { - warp::serve(routes(self.tracker.clone())) - .run(socket_addr).await; + let (_addr, server) = warp::serve(routes(self.tracker.clone())) + .bind_with_graceful_shutdown(socket_addr, async move { + tokio::signal::ctrl_c() + .await + .expect("failed to listen to shutdown signal"); + }); + tokio::task::spawn(server); } /// Start the HttpServer in TLS mode pub async fn start_tls(&self, socket_addr: SocketAddr, ssl_cert_path: &str, ssl_key_path: &str) { - warp::serve(routes(self.tracker.clone())) + let (_addr, server) = warp::serve(routes(self.tracker.clone())) .tls() .cert_path(ssl_cert_path) .key_path(ssl_key_path) - .run(socket_addr).await; + .bind_with_graceful_shutdown(socket_addr, async move { + tokio::signal::ctrl_c() + .await + .expect("failed to listen to shutdown signal"); + }); + tokio::task::spawn(server); } } diff --git a/src/torrust_udp_tracker/handlers.rs b/src/torrust_udp_tracker/handlers.rs index e10001527..bf25a8861 100644 --- a/src/torrust_udp_tracker/handlers.rs +++ b/src/torrust_udp_tracker/handlers.rs @@ -1,4 +1,4 @@ -use std::net::SocketAddr; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::sync::Arc; use aquatic_udp_protocol::{AnnounceInterval, AnnounceRequest, AnnounceResponse, ConnectRequest, ConnectResponse, ErrorResponse, NumberOfDownloads, NumberOfPeers, Port, Request, Response, ResponsePeer, ScrapeRequest, ScrapeResponse, TorrentScrapeStatistics, TransactionId}; use crate::{InfoHash, MAX_SCRAPE_TORRENTS, TorrentError, TorrentPeer, TorrentTracker}; @@ -24,7 +24,7 @@ pub async fn authenticate(info_hash: &InfoHash, tracker: Arc) -> } } -pub async fn handle_packet(remote_addr: SocketAddr, payload: &[u8], tracker: Arc) -> Response { +pub async fn handle_packet(remote_addr: SocketAddr, payload: Vec, tracker: Arc) -> Response { match Request::from_bytes(&payload[..payload.len()], MAX_SCRAPE_TORRENTS).map_err(|_| ServerError::InternalServerError) { Ok(request) => { let transaction_id = match &request { @@ -52,18 +52,18 @@ pub async fn handle_packet(remote_addr: SocketAddr, payload: &[u8], tracker: Arc pub async fn handle_request(request: Request, remote_addr: SocketAddr, tracker: Arc) -> Result { match request { Request::Connect(connect_request) => { - handle_connect(remote_addr, &connect_request).await + handle_connect(remote_addr, &connect_request, tracker).await } Request::Announce(announce_request) => { handle_announce(remote_addr, &announce_request, tracker).await } Request::Scrape(scrape_request) => { - handle_scrape(&scrape_request, tracker).await + handle_scrape(remote_addr, &scrape_request, tracker).await } } } -pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest) -> Result { +pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc) -> Result { let connection_id = get_connection_id(&remote_addr); let response = Response::from(ConnectResponse { @@ -71,6 +71,16 @@ pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest) - connection_id, }); + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if remote_addr.is_ipv4() { + status_writer.udp4_connections_handled += 1; + } else { + status_writer.udp6_connections_handled += 1; + } + }); + Ok(response) } @@ -81,27 +91,64 @@ pub async fn handle_announce(remote_addr: SocketAddr, announce_request: &Announc let peer = TorrentPeer::from_udp_announce_request(&wrapped_announce_request.announce_request, remote_addr.ip(), tracker.config.get_ext_ip()); + //let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + let torrent_stats = tracker.update_torrent_with_peer_and_get_stats(&wrapped_announce_request.info_hash, &peer).await; + // get all peers excluding the client_addr - let peers = match tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await { - Some(v) => v, - None => { return Err(ServerError::NoPeersFound); } + let peers = tracker.get_torrent_peers(&wrapped_announce_request.info_hash, &peer.peer_addr).await; + + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if remote_addr.is_ipv4() { + status_writer.udp4_announces_handled += 1; + } else { + status_writer.udp6_announces_handled += 1; + } + }); + + let announce_response = if remote_addr.is_ipv4() { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V4(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()) + }) + } else { + None + } + ).collect() + }) + } else { + Response::from(AnnounceResponse { + transaction_id: wrapped_announce_request.announce_request.transaction_id, + announce_interval: AnnounceInterval(tracker.config.announce_interval as i32), + leechers: NumberOfPeers(torrent_stats.leechers as i32), + seeders: NumberOfPeers(torrent_stats.seeders as i32), + peers: peers.iter() + .filter_map(|peer| if let IpAddr::V6(ip) = peer.peer_addr.ip() { + Some(ResponsePeer:: { + ip_address: ip, + port: Port(peer.peer_addr.port()) + }) + } else { + None + } + ).collect() + }) }; - Ok(Response::from(AnnounceResponse { - transaction_id: wrapped_announce_request.announce_request.transaction_id, - announce_interval: AnnounceInterval(tracker.config.udp_tracker.announce_interval as i32), - leechers: NumberOfPeers(torrent_stats.leechers as i32), - seeders: NumberOfPeers(torrent_stats.seeders as i32), - peers: peers.iter().map(|peer| - ResponsePeer { - ip_address: peer.peer_addr.ip(), - port: Port(peer.peer_addr.port()) - }).collect() - })) + Ok(announce_response) } -pub async fn handle_scrape(request: &ScrapeRequest, tracker: Arc) -> Result { +// todo: refactor this, db lock can be a lot shorter +pub async fn handle_scrape(remote_addr: SocketAddr, request: &ScrapeRequest, tracker: Arc) -> Result { let db = tracker.get_torrents().await; let mut torrent_stats: Vec = Vec::new(); @@ -133,6 +180,16 @@ pub async fn handle_scrape(request: &ScrapeRequest, tracker: Arc torrent_stats.push(scrape_entry); } + let tracker_copy = tracker.clone(); + tokio::spawn(async move { + let mut status_writer = tracker_copy.set_stats().await; + if remote_addr.is_ipv4() { + status_writer.udp4_scrapes_handled += 1; + } else { + status_writer.udp6_scrapes_handled += 1; + } + }); + Ok(Response::from(ScrapeResponse { transaction_id: request.transaction_id, torrent_stats @@ -141,5 +198,5 @@ pub async fn handle_scrape(request: &ScrapeRequest, tracker: Arc fn handle_error(e: ServerError, transaction_id: TransactionId) -> Response { let message = e.to_string(); - Response::from(ErrorResponse { transaction_id, message }) + Response::from(ErrorResponse { transaction_id, message: message.into() }) } diff --git a/src/torrust_udp_tracker/mod.rs b/src/torrust_udp_tracker/mod.rs index cd4b99f5b..6aa5fbce0 100644 --- a/src/torrust_udp_tracker/mod.rs +++ b/src/torrust_udp_tracker/mod.rs @@ -12,5 +12,5 @@ pub type Bytes = u64; pub type Port = u16; pub type TransactionId = i64; -pub const MAX_PACKET_SIZE: usize = 0xffff; +pub const MAX_PACKET_SIZE: usize = 1496; pub const PROTOCOL_ID: i64 = 0x41727101980; diff --git a/src/torrust_udp_tracker/server.rs b/src/torrust_udp_tracker/server.rs index 0da4ce140..cae1e5b94 100644 --- a/src/torrust_udp_tracker/server.rs +++ b/src/torrust_udp_tracker/server.rs @@ -1,64 +1,72 @@ use std::io::Cursor; use std::net::{SocketAddr}; use std::sync::Arc; -use aquatic_udp_protocol::{IpVersion, Response}; -use log::debug; +use aquatic_udp_protocol::{Response}; +use log::{debug, info}; use tokio::net::UdpSocket; -use crate::TorrentTracker; +use crate::{TorrentTracker}; use crate::torrust_udp_tracker::{handle_packet, MAX_PACKET_SIZE}; pub struct UdpServer { - socket: UdpSocket, + socket: Arc, tracker: Arc, } impl UdpServer { - pub async fn new(tracker: Arc) -> Result { - let srv = UdpSocket::bind(&tracker.config.udp_tracker.bind_address).await?; + pub async fn new(tracker: Arc, bind_address: &str) -> tokio::io::Result { + let socket = UdpSocket::bind(bind_address).await?; Ok(UdpServer { - socket: srv, + socket: Arc::new(socket), tracker, }) } - pub async fn start(&self) { + pub async fn start(&self, rx: tokio::sync::watch::Receiver) { loop { + let mut rx = rx.clone(); let mut data = [0; MAX_PACKET_SIZE]; - if let Ok((valid_bytes, remote_addr)) = self.socket.recv_from(&mut data).await { - let data = &data[..valid_bytes]; - debug!("Received {} bytes from {}", data.len(), remote_addr); - let response = handle_packet(remote_addr, data, self.tracker.clone()).await; - self.send_response(remote_addr, response).await; + let socket = self.socket.clone(); + let tracker = self.tracker.clone(); + + tokio::select! { + _ = rx.changed() => { + info!("Stopping UDP server: {}...", socket.local_addr().unwrap()); + break; + } + Ok((valid_bytes, remote_addr)) = socket.recv_from(&mut data) => { + let payload = data[..valid_bytes].to_vec(); + + debug!("Received {} bytes from {}", payload.len(), remote_addr); + debug!("{:?}", payload); + + let response = handle_packet(remote_addr, payload, tracker).await; + UdpServer::send_response(socket, remote_addr, response).await; + } } } } - async fn send_response(&self, remote_addr: SocketAddr, response: Response) { + async fn send_response(socket: Arc, remote_addr: SocketAddr, response: Response) { debug!("sending response to: {:?}", &remote_addr); let buffer = vec![0u8; MAX_PACKET_SIZE]; let mut cursor = Cursor::new(buffer); - let ip_version = match remote_addr { - SocketAddr::V4(_) => IpVersion::IPv4, - SocketAddr::V6(_) => IpVersion::IPv6 - }; - - match response.write(&mut cursor, ip_version) { + match response.write(&mut cursor) { Ok(_) => { let position = cursor.position() as usize; let inner = cursor.get_ref(); debug!("{:?}", &inner[..position]); - self.send_packet(&remote_addr, &inner[..position]).await; + UdpServer::send_packet(socket, &remote_addr, &inner[..position]).await; } Err(_) => { debug!("could not write response to bytes."); } } } - async fn send_packet(&self, remote_addr: &SocketAddr, payload: &[u8]) { + async fn send_packet(socket: Arc, remote_addr: &SocketAddr, payload: &[u8]) { // doesn't matter if it reaches or not - let _ = self.socket.send_to(payload, remote_addr).await; + let _ = socket.send_to(payload, remote_addr).await; } } diff --git a/src/tracker.rs b/src/tracker.rs index 2a4f51189..98c5be730 100644 --- a/src/tracker.rs +++ b/src/tracker.rs @@ -2,7 +2,7 @@ use serde::{Deserialize, Serialize}; use serde; use std::borrow::Cow; use std::collections::BTreeMap; -use tokio::sync::RwLock; +use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use crate::common::{AnnounceEventDef, InfoHash, NumberOfBytesDef, PeerId}; use std::net::{IpAddr, SocketAddr}; use crate::{Configuration, key_manager, MAX_SCRAPE_TORRENTS}; @@ -10,14 +10,11 @@ use std::collections::btree_map::Entry; use crate::database::SqliteDatabase; use std::sync::Arc; use aquatic_udp_protocol::{AnnounceEvent, NumberOfBytes}; -use log::debug; +use log::{debug}; use crate::key_manager::AuthKey; use r2d2_sqlite::rusqlite; use crate::torrust_http_tracker::AnnounceRequest; -const TWO_HOURS: std::time::Duration = std::time::Duration::from_secs(3600 * 2); -const FIVE_MINUTES: std::time::Duration = std::time::Duration::from_secs(300); - #[derive(Serialize, Deserialize, Clone, PartialEq)] pub enum TrackerMode { // Will track every new info hash and serve every peer. @@ -149,7 +146,17 @@ impl TorrentEntry { for (_, peer) in self .peers .iter() - .filter(|e| e.1.peer_addr.is_ipv4()) + .filter(|e| match remote_addr { + // don't filter on ip_version + None => true, + // filter out different ip_version from remote_addr + Some(remote_address) => { + match e.1.peer_addr.ip() { + IpAddr::V4(_) => { remote_address.is_ipv4() } + IpAddr::V6(_) => { remote_address.is_ipv6() } + } + } + }) .take(MAX_SCRAPE_TORRENTS as usize) { @@ -239,23 +246,52 @@ pub enum TorrentError { InvalidInfoHash, } +#[derive(Debug)] +pub struct TrackerStats { + pub tcp4_connections_handled: u64, + pub tcp4_announces_handled: u64, + pub tcp4_scrapes_handled: u64, + pub tcp6_connections_handled: u64, + pub tcp6_announces_handled: u64, + pub tcp6_scrapes_handled: u64, + pub udp4_connections_handled: u64, + pub udp4_announces_handled: u64, + pub udp4_scrapes_handled: u64, + pub udp6_connections_handled: u64, + pub udp6_announces_handled: u64, + pub udp6_scrapes_handled: u64, +} + pub struct TorrentTracker { pub config: Arc, torrents: tokio::sync::RwLock>, database: SqliteDatabase, + stats: tokio::sync::RwLock, } impl TorrentTracker { - pub fn new(config: Arc) -> TorrentTracker { - let database = SqliteDatabase::new(&config.db_path).unwrap_or_else(|error| { - panic!("Could not create SQLite database. Reason: {}", error) - }); + pub fn new(config: Arc) -> Result { + let database = SqliteDatabase::new(&config.db_path)?; - TorrentTracker { + Ok(TorrentTracker { config, torrents: RwLock::new(std::collections::BTreeMap::new()), database, - } + stats: RwLock::new(TrackerStats { + tcp4_connections_handled: 0, + tcp4_announces_handled: 0, + tcp4_scrapes_handled: 0, + tcp6_connections_handled: 0, + tcp6_announces_handled: 0, + tcp6_scrapes_handled: 0, + udp4_connections_handled: 0, + udp4_announces_handled: 0, + udp4_scrapes_handled: 0, + udp6_connections_handled: 0, + udp6_announces_handled: 0, + udp6_scrapes_handled: 0, + }), + }) } fn is_public(&self) -> bool { @@ -316,6 +352,23 @@ impl TorrentTracker { Ok(()) } + // Loading the torrents into memory + pub async fn load_torrents(&self) -> Result<(), rusqlite::Error> { + let torrents = self.database.load_persistent_torrent_data().await?; + + for torrent in torrents { + self.add_torrent(torrent.0, 0, torrent.1, 0).await; + } + + Ok(()) + } + + // Saving the torrents from memory + pub async fn save_torrents(&self) -> Result<(), rusqlite::Error> { + let torrents = self.torrents.read().await; + self.database.save_persistent_torrent_data(&*torrents).await + } + // Adding torrents is not relevant to public trackers. pub async fn add_torrent_to_whitelist(&self, info_hash: &InfoHash) -> Result { self.database.add_info_hash_to_whitelist(info_hash.clone()).await @@ -338,14 +391,12 @@ impl TorrentTracker { &self, info_hash: &InfoHash, peer_addr: &SocketAddr - ) -> Option> { + ) -> Vec { let read_lock = self.torrents.read().await; match read_lock.get(info_hash) { - None => { - None - } + None => vec![], Some(entry) => { - Some(entry.get_peers(Some(peer_addr))) + entry.get_peers(Some(peer_addr)) } } } @@ -373,10 +424,37 @@ impl TorrentTracker { } } - pub async fn get_torrents(&self) -> tokio::sync::RwLockReadGuard<'_, BTreeMap> { + pub async fn add_torrent(&self, info_hash: InfoHash, seeders: u32, completed: u32, leechers: u32) -> TorrentStats { + let mut torrents = self.torrents.write().await; + + if !torrents.contains_key(&info_hash) { + let torrent_entry = TorrentEntry { + peers: Default::default(), + completed, + seeders + }; + torrents.insert(info_hash.clone(), torrent_entry); + } + + TorrentStats { + seeders, + completed, + leechers, + } + } + + pub async fn get_torrents(&self) -> RwLockReadGuard<'_, BTreeMap> { self.torrents.read().await } + pub async fn set_stats(&self) -> RwLockWriteGuard<'_, TrackerStats> { + self.stats.write().await + } + + pub async fn get_stats(&self) -> RwLockReadGuard<'_, TrackerStats> { + self.stats.read().await + } + // remove torrents without peers pub async fn cleanup_torrents(&self) { debug!("Cleaning torrents.."); @@ -392,12 +470,12 @@ impl TorrentTracker { for (peer_id, peer) in torrent_peers.iter() { if peer.is_seeder() { - if peer.updated.elapsed() > FIVE_MINUTES { + if peer.updated.elapsed() > std::time::Duration::from_secs(self.config.peer_timeout as u64) { // remove seeders after 5 minutes since last update... peers_to_remove.push(peer_id.clone()); torrent_entry.seeders -= 1; } - } else if peer.updated.elapsed() > TWO_HOURS { + } else if peer.updated.elapsed() > std::time::Duration::from_secs(self.config.peer_timeout as u64) { // remove peers after 2 hours since last update... peers_to_remove.push(peer_id.clone()); } @@ -408,7 +486,7 @@ impl TorrentTracker { } } - if self.config.mode.clone() == TrackerMode::PublicMode { + if self.config.mode.clone() == TrackerMode::PublicMode && self.config.cleanup_peerless && !self.config.persistence { // peer-less torrents.. if torrent_entry.peers.len() == 0 { torrents_to_remove.push(k.clone()); diff --git a/src/utils.rs b/src/utils.rs index 5790e6067..e3a8302df 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -2,7 +2,9 @@ use std::net::SocketAddr; use std::time::SystemTime; use std::error::Error; use std::fmt::Write; +use std::io::Cursor; use aquatic_udp_protocol::ConnectionId; +use byteorder::{BigEndian, ReadBytesExt}; pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId { match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) { @@ -29,3 +31,28 @@ pub fn url_encode_bytes(content: &[u8]) -> Result> { Ok(out) } + +// Function that will convert a small or big number into the smallest form of a byte array. +pub async fn convert_int_to_bytes(number: &u64) -> Vec { + let mut return_data: Vec = Vec::new(); + // return_data.extend(number.to_be_bytes().reverse()); + for i in 1..8 { + if number < &256u64.pow(i) { + let start: usize = 16usize - i as usize; + return_data.extend(number.to_be_bytes()[start..8].iter()); + return return_data; + } + } + return return_data; +} + +pub async fn convert_bytes_to_int(array: &Vec) -> u64 { + let mut array_fixed: Vec = Vec::new(); + let size = 8 - array.len(); + for _ in 0..size { + array_fixed.push(0); + } + array_fixed.extend(array); + let mut rdr = Cursor::new(array_fixed); + return rdr.read_u64::().unwrap(); +}