mirror of
https://github.com/pradt2/always-online-stun.git
synced 2025-09-26 19:41:34 +08:00
Refactoring
This commit is contained in:
701
Cargo.lock
generated
701
Cargo.lock
generated
@@ -2,12 +2,25 @@
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "always-online-stun"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"futures",
|
||||
"log",
|
||||
"pretty_env_logger",
|
||||
"rand",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"stunclient",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
@@ -19,18 +32,41 @@ version = "1.0.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee10e43ae4a853c0a3591d4e2ada1719e553be18199d9da9d4a83f5927c2f5c7"
|
||||
|
||||
[[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"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "build_const"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7"
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899"
|
||||
|
||||
[[package]]
|
||||
name = "bytecodec"
|
||||
version = "0.4.15"
|
||||
@@ -47,6 +83,18 @@ version = "1.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
@@ -62,6 +110,44 @@ dependencies = [
|
||||
"build_const",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
version = "0.8.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "env_logger"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"humantime",
|
||||
"log",
|
||||
"regex",
|
||||
"termcolor",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.17"
|
||||
@@ -167,6 +253,40 @@ dependencies = [
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.3.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hmac-sha1"
|
||||
version = "0.1.3"
|
||||
@@ -177,20 +297,154 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.107"
|
||||
name = "http"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbe5e23404da5b4f555ef85ebed98fb4083e55a00c317800bc2a50ede9f3d219"
|
||||
checksum = "31f4c6746584866f0feabcc69893c5b51beef3831656a968ed7ae254cdc4fd03"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"fnv",
|
||||
"itoa",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9100414882e15fb7feccb4897e5f0ff0ff1ca7d1a86a23208ada4d7a18e6c6c4"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "humantime"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
|
||||
dependencies = [
|
||||
"quick-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b26ae0a80afebe130861d90abf98e3814a4f28a4c6ffeb5ab8ebb2be311e0ef2"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac"
|
||||
dependencies = [
|
||||
"http",
|
||||
"hyper",
|
||||
"rustls",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
|
||||
dependencies = [
|
||||
"matches",
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f647032dfaa1f8b6dc29bd3edb7bbef4861b8b8007ebb118d6db284fd59f6ee"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "35e70ee094dc02fd9c13fdad4940090f22dbd6ac7c9e7094a46cf0232a50bc7c"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.121"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.14"
|
||||
version = "0.4.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
|
||||
checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "md5"
|
||||
version = "0.7.0"
|
||||
@@ -203,6 +457,12 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.7.14"
|
||||
@@ -234,6 +494,18 @@ dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.7"
|
||||
@@ -252,6 +524,16 @@ version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed0cfbc8191465bed66e1718596ee0b0b35d5ee1f41c5df2189d0fe8bde535ba"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_env_logger"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
|
||||
dependencies = [
|
||||
"env_logger",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
@@ -273,6 +555,12 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quick-error"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.10"
|
||||
@@ -322,6 +610,142 @@ dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46a1f7aa4f35e5e8b4160449f51afc758f0ce6454315a9fa7d0d113e958c41eb"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls",
|
||||
"rustls-pemfile",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-rustls",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"webpki-roots",
|
||||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ring"
|
||||
version = "0.16.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"spin",
|
||||
"untrusted",
|
||||
"web-sys",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.20.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921"
|
||||
dependencies = [
|
||||
"log",
|
||||
"ring",
|
||||
"sct",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ee86d63972a7c661d1536fefe8c3c8407321c3df668891286de28abcd087360"
|
||||
dependencies = [
|
||||
"base64",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
|
||||
|
||||
[[package]]
|
||||
name = "sct"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.136"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha1"
|
||||
version = "0.2.0"
|
||||
@@ -334,6 +758,22 @@ version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5"
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "66d72b759436ae32898a2af0a14218dbf55efde3feeb170eb623637db85ee1e0"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spin"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "stun_codec"
|
||||
version = "0.1.13"
|
||||
@@ -373,6 +813,30 @@ dependencies = [
|
||||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "termcolor"
|
||||
version = "1.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c1c1d5a42b6245520c249549ec267180beaffcc0615401ac8e31853d4b6d8d2"
|
||||
dependencies = [
|
||||
"tinyvec_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec_macros"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.14.0"
|
||||
@@ -380,7 +844,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70e992e41e0d2fb9f755b37446f20900f64446ef54874f40a60c78f021ac6144"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
"libc",
|
||||
"memchr",
|
||||
"mio",
|
||||
"pin-project-lite",
|
||||
"tokio-macros",
|
||||
@@ -398,6 +864,17 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.23.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4151fda0cf2798550ad0b34bcfc9b9dcc2a9d2471c895c68f3a8818e54f2389e"
|
||||
dependencies = [
|
||||
"rustls",
|
||||
"tokio",
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.8"
|
||||
@@ -409,6 +886,58 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0edfdeb067411dba2044da6d1cb2df793dd35add7888d73c16e3381ded401764"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90442985ee2f57c9e1b548ee72ae842f4a9a20e3f417cc38dbc5dc684d9bb4ee"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "trackable"
|
||||
version = "0.2.24"
|
||||
@@ -438,18 +967,162 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
|
||||
|
||||
[[package]]
|
||||
name = "untrusted"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"idna",
|
||||
"matches",
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"wasm-bindgen-macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.56"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c060b319f29dd25724f09a2ba1418f142f539b2be99fbf4d2d5a8f7330afb8eb"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki"
|
||||
version = "0.22.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd"
|
||||
dependencies = [
|
||||
"ring",
|
||||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.22.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "552ceb903e957524388c4d3475725ff2c8b7960922063af6ce53c9a43da07449"
|
||||
dependencies = [
|
||||
"webpki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -466,8 +1139,26 @@ 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.10.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
@@ -7,7 +7,11 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
futures = { version = "0.3.17" }
|
||||
log = { version = "0.4.16" }
|
||||
pretty_env_logger = { version = "0.4.0" }
|
||||
rand = { version = "0.8.4", default-features = false }
|
||||
reqwest = { version = "0.11.10", default-features = false, features=["json", "rustls-tls"] }
|
||||
serde_json = { version = "1.0.70", default-features = false }
|
||||
stunclient = { version = "0.3.1", default-features = false, features = ["async"] }
|
||||
tokio = { version = "1.14.0", default-features = false, features = ["fs", "macros", "net", "rt"] }
|
||||
tokio-stream = { version = "0.1.8", default-features = false }
|
1
geoip_cache.txt
Normal file
1
geoip_cache.txt
Normal file
File diff suppressed because one or more lines are too long
@@ -1,38 +0,0 @@
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::net::ToSocketAddrs;
|
||||
use std::str::FromStr;
|
||||
use tokio::io;
|
||||
use tokio_stream::StreamExt;
|
||||
|
||||
pub type StunCandidate = String;
|
||||
const FILE_PATH: &str = "candidates.txt";
|
||||
|
||||
pub async fn clean_candidates() -> io::Result<()> {
|
||||
let candidates = get_candidates().await?;
|
||||
println!("Loaded candidates: {}", candidates.len());
|
||||
let mut candidates = remove_duplicates(candidates);
|
||||
candidates.sort();
|
||||
println!("Unique candidates: {}", candidates.len());
|
||||
let s = candidates.into_iter()
|
||||
.map(|candidate| candidate.to_string())
|
||||
.reduce(|a, b| format!("{}\n{}", a, b))
|
||||
.unwrap_or(String::from(""));
|
||||
tokio::fs::write(FILE_PATH, s).await
|
||||
}
|
||||
|
||||
pub async fn get_candidates() -> io::Result<Vec<StunCandidate>> {
|
||||
Ok(tokio::fs::read_to_string(FILE_PATH).await?
|
||||
.split('\n')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.starts_with('#'))
|
||||
.map(String::from)
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
|
||||
fn remove_duplicates(candidates: Vec<StunCandidate>) -> Vec<StunCandidate> {
|
||||
let mut set = HashSet::with_capacity(candidates.len());
|
||||
candidates.into_iter().for_each(|candidate| {
|
||||
set.insert(candidate);
|
||||
});
|
||||
set.into_iter().collect()
|
||||
}
|
115
src/geoip.rs
Normal file
115
src/geoip.rs
Normal file
@@ -0,0 +1,115 @@
|
||||
use std::collections::{HashMap};
|
||||
use std::io;
|
||||
use std::io::{ErrorKind};
|
||||
use std::io::ErrorKind::Other;
|
||||
use std::net::IpAddr;
|
||||
use std::time::Duration;
|
||||
|
||||
pub(crate) struct IpGeolocationIoClient {
|
||||
api_key: String,
|
||||
url: String,
|
||||
client: reqwest::Client,
|
||||
}
|
||||
|
||||
type GeoIpData = (f32, f32);
|
||||
|
||||
impl IpGeolocationIoClient {
|
||||
pub(crate) fn new(api_key: String) -> IpGeolocationIoClient {
|
||||
IpGeolocationIoClient {
|
||||
api_key,
|
||||
url: String::from("https://api.ipgeolocation.io/ipgeo"),
|
||||
client: reqwest::Client::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn default() -> IpGeolocationIoClient {
|
||||
IpGeolocationIoClient::new(std::env::var("IPGEOLOCATIONIO_API_KEY")
|
||||
.expect("Env var IPGEOLOCATIONIO_API_KEY required. Get a free API key at https://ipgeolocation.io"))
|
||||
}
|
||||
}
|
||||
|
||||
impl IpGeolocationIoClient {
|
||||
pub(crate) async fn get_hostname_geoip_info(&self, hostname: &str) -> io::Result<GeoIpData> {
|
||||
self.get_geoip_info(hostname).await
|
||||
}
|
||||
|
||||
async fn get_ip_geoip_info(&self, ip: IpAddr) -> io::Result<GeoIpData> {
|
||||
self.get_geoip_info(ip.to_string().as_str()).await
|
||||
}
|
||||
|
||||
async fn get_geoip_info(&self, hostname_or_ip: &str) -> io::Result<GeoIpData> {
|
||||
let response = self.client.get(self.url.as_str())
|
||||
.query(&[("apiKey", self.api_key.as_str())])
|
||||
.query(&[("ip", hostname_or_ip)])
|
||||
.query(&[("fields", "latitude,longitude")])
|
||||
.timeout(Duration::from_secs(1))
|
||||
.send()
|
||||
.await
|
||||
.map_err(|err| io::Error::new(ErrorKind::Other, err))?
|
||||
.json::<HashMap<String, String>>()
|
||||
.await
|
||||
.map_err(|err| io::Error::new(ErrorKind::Other, err))?;
|
||||
|
||||
let lat = response.get("latitude")
|
||||
.cloned()
|
||||
.map(|lat_str| lat_str.parse().unwrap())
|
||||
.unwrap_or(0 as f32);
|
||||
|
||||
let lon = response.get("longitude")
|
||||
.cloned()
|
||||
.map(|lon_str| lon_str.parse().unwrap())
|
||||
.unwrap_or(0 as f32);
|
||||
|
||||
Ok((lat, lon))
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CachedIpGeolocationIpClient {
|
||||
path: String,
|
||||
client: IpGeolocationIoClient,
|
||||
map: HashMap<String, GeoIpData>,
|
||||
}
|
||||
|
||||
impl CachedIpGeolocationIpClient {
|
||||
pub(crate) async fn new(filename: String) -> io::Result<CachedIpGeolocationIpClient> {
|
||||
let cache_str = tokio::fs::read_to_string(filename.as_str()).await?;
|
||||
|
||||
let map: HashMap<String, GeoIpData> = serde_json::de::from_str(cache_str.as_str())
|
||||
.map_err(|err| io::Error::new(Other, err))?;
|
||||
|
||||
let client = CachedIpGeolocationIpClient {
|
||||
path: filename,
|
||||
client: IpGeolocationIoClient::default(),
|
||||
map
|
||||
};
|
||||
|
||||
Ok(client)
|
||||
}
|
||||
|
||||
pub(crate) async fn default() -> io::Result<CachedIpGeolocationIpClient> {
|
||||
CachedIpGeolocationIpClient::new(String::from("geoip_cache.txt")).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_hostname_geoip_info(&mut self, hostname: &str) -> io::Result<GeoIpData> {
|
||||
self.get_geoip_info(hostname).await
|
||||
}
|
||||
|
||||
pub(crate) async fn get_ip_geoip_info(&mut self, ip: IpAddr) -> io::Result<GeoIpData> {
|
||||
self.get_geoip_info(ip.to_string().as_str()).await
|
||||
}
|
||||
|
||||
pub(crate) async fn save(&self) -> io::Result<()> {
|
||||
let str = serde_json::ser::to_string(&self.map)
|
||||
.map_err(|err| io::Error::new(Other, err))?;
|
||||
tokio::fs::write(self.path.as_str(), str).await
|
||||
}
|
||||
|
||||
async fn get_geoip_info(&mut self, hostname_or_ip: &str) -> io::Result<GeoIpData> {
|
||||
if let Some(geo_ip_data) = self.map.get(hostname_or_ip).cloned() {
|
||||
return Ok(geo_ip_data)
|
||||
}
|
||||
let geo_ip_data = self.client.get_geoip_info(hostname_or_ip).await?;
|
||||
self.map.insert(String::from(hostname_or_ip), geo_ip_data.clone());
|
||||
Ok(geo_ip_data)
|
||||
}
|
||||
}
|
179
src/main.rs
179
src/main.rs
@@ -1,98 +1,119 @@
|
||||
use std::collections::HashSet;
|
||||
use std::cell::RefCell;
|
||||
use std::io;
|
||||
use std::rc::Rc;
|
||||
use std::thread;
|
||||
use rand::seq::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use stunclient::Error;
|
||||
use tokio::{io};
|
||||
use tokio::macros::support::thread_rng_n;
|
||||
use tokio::sync::Semaphore;
|
||||
use std::time::Duration;
|
||||
use futures::StreamExt;
|
||||
use tokio::time::Instant;
|
||||
use tokio_stream::{iter, StreamExt};
|
||||
use crate::stun::CheckError;
|
||||
use crate::utils::join_all_with_semaphore;
|
||||
use crate::outputs::{ValidHosts, ValidIpV4s, ValidIpV6s};
|
||||
use crate::servers::StunServer;
|
||||
use crate::stun::{StunServerTestResult, StunSocketResponse};
|
||||
|
||||
mod candidates;
|
||||
extern crate pretty_env_logger;
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
mod servers;
|
||||
mod stun;
|
||||
mod utils;
|
||||
mod outputs;
|
||||
mod geoip;
|
||||
|
||||
const CONCURRENT_SOCKETS_USED_LIMIT: usize = 64;
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> io::Result<()> {
|
||||
let stream = candidates::get_candidates().await?.into_iter();
|
||||
let semaphore = Rc::new(Semaphore::new(100));
|
||||
let profiles = stream
|
||||
pretty_env_logger::init();
|
||||
|
||||
let client = Rc::new(RefCell::new(geoip::CachedIpGeolocationIpClient::default().await?));
|
||||
|
||||
let stun_servers = servers::get_stun_servers().await?;
|
||||
|
||||
let stun_servers_count = stun_servers.len();
|
||||
info!("Loaded {} stun server hosts", stun_servers.len());
|
||||
|
||||
let stun_server_test_results = stun_servers.into_iter()
|
||||
.map(|candidate| {
|
||||
let semaphore_local_ref = semaphore.clone();
|
||||
async move {
|
||||
let permit = semaphore_local_ref.acquire().await.expect("Semaphore to be operating");
|
||||
let res = stun::check_candidate(candidate.clone()).await;
|
||||
drop(permit);
|
||||
match &res {
|
||||
Ok(profile) => { println!("Success: {:?}", profile) }
|
||||
Err(err) => { println!("Failure: {:?}", err) }
|
||||
}
|
||||
res
|
||||
let test_result = stun::test_udp_stun_server(candidate).await;
|
||||
print_stun_server_status(&test_result);
|
||||
test_result
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let timestamp = Instant::now();
|
||||
let profiles = futures::future::join_all(profiles).await;
|
||||
let mut all_ok = 0;
|
||||
let mut dns_unresolved = 0;
|
||||
let mut partial_timeout = 0;
|
||||
let mut complete_timeout = 0;
|
||||
let mut incorrect_mapping_returned = 0;
|
||||
profiles.iter().for_each(|res| {
|
||||
match res {
|
||||
Ok(_) => { all_ok += 1; }
|
||||
Err(CheckError::DnsResolutionFailed) => { dns_unresolved += 1; }
|
||||
Err(CheckError::PartialTimeout) => { partial_timeout += 1; }
|
||||
Err(CheckError::Timeout) => { complete_timeout += 1; }
|
||||
Err(CheckError::IncorrectMappingReturned) => { incorrect_mapping_returned += 1; }
|
||||
}
|
||||
});
|
||||
println!(
|
||||
"OK {} , DNS failure {} , p/Timeout {} , Timeout {} , Incorrect {}",
|
||||
all_ok, dns_unresolved, partial_timeout, complete_timeout, incorrect_mapping_returned
|
||||
);
|
||||
let stun_server_test_results = join_all_with_semaphore(stun_server_test_results.into_iter(), CONCURRENT_SOCKETS_USED_LIMIT).await;
|
||||
|
||||
let mut output_hosts = profiles.iter()
|
||||
.filter_map(|res| res.as_ref().ok())
|
||||
.map(|profile| profile.candidate.clone())
|
||||
.collect::<Vec<_>>();
|
||||
output_hosts.shuffle(&mut thread_rng());
|
||||
let output_hosts = output_hosts.into_iter()
|
||||
.map(|candidate| String::from(candidate))
|
||||
.reduce(|a, b| format!("{}\n{}", a, b))
|
||||
.unwrap_or(String::from(""));
|
||||
tokio::fs::write("valid_hosts.txt", output_hosts).await?;
|
||||
ValidHosts::default(&stun_server_test_results).save().await?;
|
||||
ValidIpV4s::default(&stun_server_test_results).save().await?;
|
||||
ValidIpV6s::default(&stun_server_test_results).save().await?;
|
||||
|
||||
let output_ip4 = profiles.iter()
|
||||
.filter_map(|res| res.as_ref().ok())
|
||||
.flat_map(|profile| profile.addrs.clone().into_iter())
|
||||
.filter(|addr| addr.is_ipv4())
|
||||
.map(|addr| addr.to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut output_ip4 = output_ip4.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
output_ip4.shuffle(&mut thread_rng());
|
||||
let output_ip4 = output_ip4.into_iter()
|
||||
.reduce(|a, b| format!("{}\n{}", a, b))
|
||||
.unwrap_or(String::from(""));
|
||||
tokio::fs::write("valid_ipv4s.txt", output_ip4).await?;
|
||||
write_stun_server_summary(stun_servers_count, &stun_server_test_results,timestamp.elapsed());
|
||||
|
||||
let output_ip6 = profiles.iter()
|
||||
.filter_map(|res| res.as_ref().ok())
|
||||
.flat_map(|profile| profile.addrs.clone().into_iter())
|
||||
.filter(|addr| addr.is_ipv6())
|
||||
.map(|addr| addr.to_string())
|
||||
.collect::<HashSet<_>>();
|
||||
let mut output_ip6 = output_ip6.into_iter()
|
||||
.collect::<Vec<_>>();
|
||||
output_ip6.shuffle(&mut thread_rng());
|
||||
let output_ip6 = output_ip6.into_iter()
|
||||
.reduce(|a, b| format!("{}\n{}", a, b))
|
||||
.unwrap_or(String::from(""));
|
||||
tokio::fs::write("valid_ipv6s.txt", output_ip6).await?;
|
||||
futures::stream::iter(stun_server_test_results.iter())
|
||||
.filter_map(|test_result| async move { if test_result.is_healthy() { Some(test_result) } else { None } })
|
||||
.map(|test_result| futures::stream::iter(test_result.socket_tests.iter()))
|
||||
.flatten()
|
||||
.map(|test_result| test_result.socket)
|
||||
.for_each(|socket| {
|
||||
let client = client.clone();
|
||||
async move {
|
||||
client.borrow_mut().get_ip_geoip_info(socket.ip()).await.expect("GeoIP IP info must be available");
|
||||
}
|
||||
}).await;
|
||||
|
||||
client.borrow_mut().save().await?;
|
||||
|
||||
println!("Finished in {:?}", timestamp.elapsed());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn print_stun_server_status(test_result: &StunServerTestResult) {
|
||||
if test_result.is_healthy() {
|
||||
info!("{:<25} -> Host is healthy", test_result.server.hostname);
|
||||
} else if !test_result.is_resolvable() {
|
||||
info!("{:<25} -> Host is not resolvable", test_result.server.hostname);
|
||||
} else if test_result.is_partial_timeout() {
|
||||
info!("{:<25} -> Host times out on some sockets", test_result.server.hostname);
|
||||
} else if test_result.is_timeout() {
|
||||
info!("{:<25} -> Host times out on all sockets", test_result.server.hostname);
|
||||
} else {
|
||||
info!("{:<25} -> Host behaves in an unexpected way. Run with RUST_LOG=DEBUG for more info", test_result.server.hostname);
|
||||
for socket_test in &test_result.socket_tests {
|
||||
match &socket_test.result {
|
||||
StunSocketResponse::HealthyResponse { .. } => { debug!("{:<25} -> Socket {:<21} returned a healthy response", test_result.server.hostname, socket_test.socket) }
|
||||
StunSocketResponse::InvalidMappingResponse { expected, actual, rtt } => { debug!("{:<25} -> Socket {:<21} returned an invalid mapping: expected={} actual={}", test_result.server.hostname, socket_test.socket, expected, actual) }
|
||||
StunSocketResponse::Timeout { deadline } => { debug!("{:<25} -> Socket {:<21} timed out after {:?}", test_result.server.hostname, socket_test.socket, deadline) }
|
||||
StunSocketResponse::UnexpectedError { err } => { debug!("{:<25} -> Socket {:<21} returned an unexpected error: {}", test_result.server.hostname, socket_test.socket, err) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_stun_server_summary(candidate_hosts_count: usize, results: &Vec<StunServerTestResult>, time_taken: Duration) {
|
||||
let mut healthy = 0;
|
||||
let mut dns_unresolved = 0;
|
||||
let mut partial_timeout = 0;
|
||||
let mut timeout = 0;
|
||||
let mut unexpected_err = 0;
|
||||
results.iter().for_each(|server_test_result| {
|
||||
if server_test_result.is_healthy() {
|
||||
healthy += 1;
|
||||
} else if !server_test_result.is_resolvable() {
|
||||
dns_unresolved += 1;
|
||||
} else if server_test_result.is_partial_timeout() {
|
||||
partial_timeout += 1;
|
||||
} else if server_test_result.is_timeout() {
|
||||
timeout += 1;
|
||||
} else {
|
||||
unexpected_err += 1;
|
||||
}
|
||||
});
|
||||
info!(
|
||||
"Statistics -> Tested={}, Healthy={}, DNS failure={}, partial Timeout={}, Timeout={}, Unexpected err={}. Finished in {:?}",
|
||||
candidate_hosts_count, healthy, dns_unresolved, partial_timeout, timeout, unexpected_err, time_taken
|
||||
);
|
||||
|
||||
if healthy == 0 {
|
||||
warn!("No healthy hosts found! Are you behind NAT?")
|
||||
}
|
||||
}
|
||||
|
110
src/outputs.rs
Normal file
110
src/outputs.rs
Normal file
@@ -0,0 +1,110 @@
|
||||
use std::io;
|
||||
use rand::prelude::SliceRandom;
|
||||
use rand::thread_rng;
|
||||
use crate::StunServerTestResult;
|
||||
use crate::utils::ReduceToString;
|
||||
|
||||
pub(crate) struct ValidHosts<'a> {
|
||||
server_test_results: &'a Vec<StunServerTestResult>,
|
||||
file_path: String,
|
||||
}
|
||||
|
||||
impl ValidHosts<'_> {
|
||||
pub(crate) fn default(server_test_results: &Vec<StunServerTestResult>) -> ValidHosts {
|
||||
ValidHosts {
|
||||
server_test_results,
|
||||
file_path: String::from("valid_hosts.txt")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_output(&self) -> String {
|
||||
let mut output = self.server_test_results.iter()
|
||||
.filter(|server_test_result| server_test_result.is_healthy())
|
||||
.map(|server_test_result| {
|
||||
let _proto = server_test_result.server.protocol;
|
||||
let host= server_test_result.server.hostname.as_str();
|
||||
let port = server_test_result.server.port;
|
||||
format!("{}:{}\n", host, port)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
output.shuffle(&mut thread_rng());
|
||||
|
||||
let output = output.iter().reduce_to_string();
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) async fn save(&self) -> io::Result<()> {
|
||||
let output = self.get_output();
|
||||
tokio::fs::write(self.file_path.as_str(), output).await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ValidIpV4s<'a> {
|
||||
server_test_results: &'a Vec<StunServerTestResult>,
|
||||
file_path: String,
|
||||
}
|
||||
|
||||
impl ValidIpV4s<'_> {
|
||||
pub(crate) fn default(server_test_results: &Vec<StunServerTestResult>) -> ValidIpV4s {
|
||||
ValidIpV4s {
|
||||
server_test_results,
|
||||
file_path: String::from("valid_ipv4s.txt")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_output(&self) -> String {
|
||||
let mut output = self.server_test_results.iter()
|
||||
.filter(|server_test_result| server_test_result.is_healthy())
|
||||
.flat_map(|server_test_result| {
|
||||
let ipv4s = server_test_result.socket_tests.iter()
|
||||
.filter(|socket_test| socket_test.socket.is_ipv4())
|
||||
.map(|socket_test| format!("{}\n", socket_test.socket));
|
||||
ipv4s
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
output.shuffle(&mut thread_rng());
|
||||
|
||||
let output = output.iter().reduce_to_string();
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) async fn save(&self) -> io::Result<()> {
|
||||
let output = self.get_output();
|
||||
tokio::fs::write(self.file_path.as_str(), output).await
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct ValidIpV6s<'a> {
|
||||
server_test_results: &'a Vec<StunServerTestResult>,
|
||||
file_path: String,
|
||||
}
|
||||
|
||||
impl ValidIpV6s<'_> {
|
||||
pub(crate) fn default(server_test_results: &Vec<StunServerTestResult>) -> ValidIpV6s {
|
||||
ValidIpV6s {
|
||||
server_test_results,
|
||||
file_path: String::from("valid_ipv6s.txt")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_output(&self) -> String {
|
||||
let mut output = self.server_test_results.iter()
|
||||
.filter(|server_test_result| server_test_result.is_healthy())
|
||||
.flat_map(|server_test_result| {
|
||||
let ipv4s = server_test_result.socket_tests.iter()
|
||||
.filter(|socket_test| socket_test.socket.is_ipv6())
|
||||
.map(|socket_test| format!("{}\n", socket_test.socket));
|
||||
ipv4s
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
output.shuffle(&mut thread_rng());
|
||||
|
||||
let output = output.iter().reduce_to_string();
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) async fn save(&self) -> io::Result<()> {
|
||||
let output = self.get_output();
|
||||
tokio::fs::write(self.file_path.as_str(), output).await
|
||||
}
|
||||
}
|
33
src/servers.rs
Normal file
33
src/servers.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use tokio::io;
|
||||
|
||||
const FILE_PATH: &str = "candidates.txt";
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum TransportProtocol {
|
||||
UDP, TCP
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct StunServer {
|
||||
pub protocol: TransportProtocol,
|
||||
pub hostname: String,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
pub async fn get_stun_servers() -> io::Result<Vec<StunServer>> {
|
||||
let stun_servers = tokio::fs::read_to_string(FILE_PATH).await?
|
||||
.split('\n')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.filter(|s| !s.starts_with('#'))
|
||||
.filter(|s| !s.starts_with("//"))
|
||||
.map(|stun_server_str| {
|
||||
let (hostname, port) = stun_server_str.split_once(':').unwrap();
|
||||
StunServer {
|
||||
protocol: TransportProtocol::UDP,
|
||||
hostname: String::from(hostname),
|
||||
port: port.parse().unwrap()
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
return Ok(stun_servers);
|
||||
}
|
243
src/stun.rs
243
src/stun.rs
@@ -1,88 +1,187 @@
|
||||
use std::future::Future;
|
||||
use std::net::{SocketAddr, SocketAddrV4, SocketAddrV6};
|
||||
use std::net::{SocketAddr};
|
||||
use std::time::{Duration, Instant};
|
||||
use stunclient::Error;
|
||||
use tokio::io;
|
||||
use tokio::net::{lookup_host};
|
||||
use crate::candidates::StunCandidate;
|
||||
use crate::utils::join_all_with_semaphore;
|
||||
use crate::StunServer;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum CheckError {
|
||||
DnsResolutionFailed,
|
||||
IncorrectMappingReturned,
|
||||
PartialTimeout,
|
||||
Timeout,
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct StunServerTestResult {
|
||||
pub(crate) server: StunServer,
|
||||
pub(crate) socket_tests: Vec<StunSocketTestResult>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CandidateProfile {
|
||||
pub candidate: StunCandidate,
|
||||
pub addrs: Vec<SocketAddr>,
|
||||
pub rtt_ms: u32,
|
||||
impl StunServerTestResult {
|
||||
pub(crate) fn is_resolvable(&self) -> bool {
|
||||
return self.socket_tests.len() > 0;
|
||||
}
|
||||
|
||||
pub(crate) fn is_healthy(&self) -> bool {
|
||||
self.is_resolvable() && self.socket_tests.iter()
|
||||
.all(StunSocketTestResult::is_ok)
|
||||
}
|
||||
|
||||
pub(crate) fn is_partial_timeout(&self) -> bool {
|
||||
self.is_resolvable()
|
||||
&& self.is_any_healthy()
|
||||
&& self.is_any_timeout()
|
||||
&&! self.is_any_invalid_mapping()
|
||||
&&! self.is_any_unexpected_response()
|
||||
}
|
||||
|
||||
pub(crate) fn is_timeout(&self) -> bool {
|
||||
self.is_resolvable()
|
||||
&&! self.is_any_healthy()
|
||||
&& self.is_any_timeout()
|
||||
&&! self.is_any_invalid_mapping()
|
||||
&&! self.is_any_unexpected_response()
|
||||
}
|
||||
|
||||
fn is_any_healthy(&self) -> bool {
|
||||
self.is_resolvable() && self.socket_tests.iter()
|
||||
.any(|result| match result.result {
|
||||
StunSocketResponse::HealthyResponse {..} => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_any_timeout(&self) -> bool {
|
||||
self.is_resolvable() && self.socket_tests.iter()
|
||||
.any(|result| match result.result {
|
||||
StunSocketResponse::Timeout {..} => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_any_invalid_mapping(&self) -> bool {
|
||||
self.is_resolvable() && self.socket_tests.iter()
|
||||
.any(|result| match result.result {
|
||||
StunSocketResponse::InvalidMappingResponse {..} => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
|
||||
fn is_any_unexpected_response(&self) -> bool {
|
||||
self.is_resolvable() && self.socket_tests.iter()
|
||||
.any(|result| match result.result {
|
||||
StunSocketResponse::UnexpectedError {..} => true,
|
||||
_ => false,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_candidate(candidate: StunCandidate) -> Result<CandidateProfile, CheckError> {
|
||||
let addrs = lookup_host(&candidate).await
|
||||
.map_err(|err| CheckError::DnsResolutionFailed)?.collect::<Vec<_>>();
|
||||
if addrs.len() == 0 { return Err(CheckError::DnsResolutionFailed); }
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct StunSocketTestResult {
|
||||
pub(crate) socket: SocketAddr,
|
||||
pub(crate) result: StunSocketResponse
|
||||
}
|
||||
|
||||
let responses = addrs.iter().map(|address| async move {
|
||||
let u = tokio::net::UdpSocket::bind(match address {
|
||||
impl StunSocketTestResult {
|
||||
pub(crate) fn is_ok(&self) -> bool {
|
||||
self.result.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) enum StunSocketResponse {
|
||||
HealthyResponse { rtt: Duration },
|
||||
InvalidMappingResponse { expected: SocketAddr, actual: SocketAddr, rtt: Duration },
|
||||
Timeout { deadline: Duration },
|
||||
UnexpectedError { err: String }
|
||||
}
|
||||
|
||||
impl StunSocketResponse {
|
||||
fn is_ok(&self) -> bool {
|
||||
match &self {
|
||||
StunSocketResponse::HealthyResponse { .. } => true,
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn test_udp_stun_server(
|
||||
server: StunServer
|
||||
) -> StunServerTestResult {
|
||||
let socket_addrs = tokio::net::lookup_host(format!("{}:{}", server.hostname, server.port)).await;
|
||||
|
||||
if socket_addrs.is_err() {
|
||||
if socket_addrs.as_ref().err().unwrap().to_string() != "failed to lookup address information: Name or service not known" {
|
||||
warn!("{:<21} -> Unexpected DNS failure: {}", server.hostname, socket_addrs.as_ref().err().unwrap().to_string());
|
||||
}
|
||||
return StunServerTestResult {
|
||||
server,
|
||||
socket_tests: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
let results = socket_addrs.unwrap()
|
||||
.map(|addr| addr.ip())
|
||||
.map(|addr| {
|
||||
let port = server.port;
|
||||
let hostname = server.hostname.as_str();
|
||||
async move {
|
||||
let stun_socket = SocketAddr::new(addr, port);
|
||||
let res = test_socket_addr(hostname, stun_socket).await;
|
||||
res
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let results = join_all_with_semaphore(results.into_iter(), 1).await;
|
||||
|
||||
StunServerTestResult {
|
||||
server,
|
||||
socket_tests: results
|
||||
}
|
||||
}
|
||||
|
||||
async fn test_socket_addr(
|
||||
hostname: &str,
|
||||
socket_addr: SocketAddr
|
||||
) -> StunSocketTestResult {
|
||||
let local_socket = tokio::net::UdpSocket::bind(
|
||||
match socket_addr {
|
||||
SocketAddr::V4(..) => { "0.0.0.0:0" }
|
||||
SocketAddr::V6(..) => { "[::]:0" }
|
||||
}.parse::<std::net::SocketAddr>().unwrap()).await.unwrap();
|
||||
let local_address = u.local_addr().expect("Local address to be available");
|
||||
let mut client = stunclient::StunClient::new(*address);
|
||||
client.set_timeout(Duration::from_secs(1));
|
||||
let time = Instant::now();
|
||||
let res = client.query_external_address_async(&u).await;
|
||||
match res {
|
||||
Ok(mapped_address) => if mapped_address.port() == local_address.port() {
|
||||
Ok((mapped_address, time.elapsed()))
|
||||
} else {
|
||||
Err(CheckError::IncorrectMappingReturned)
|
||||
},
|
||||
Err(_) => { Err(CheckError::Timeout) }
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
).await.unwrap();
|
||||
|
||||
let responses = futures::future::join_all(responses).await;
|
||||
let mut client = stunclient::StunClient::new(socket_addr);
|
||||
let deadline = Duration::from_secs(1);
|
||||
client.set_timeout(deadline);
|
||||
|
||||
let ok_count = responses.iter()
|
||||
.filter(|response| { response.is_ok() })
|
||||
.count();
|
||||
let start = Instant::now();
|
||||
|
||||
if ok_count == responses.len() {
|
||||
let rtt_ms = responses.iter()
|
||||
.filter_map(|response| response.as_ref().ok())
|
||||
.map(|response| response.1)
|
||||
.map(|duration| duration.as_millis() as u32)
|
||||
.sum::<u32>() / responses.len() as u32;
|
||||
let result = client.query_external_address_async(&local_socket).await;
|
||||
|
||||
return Ok(CandidateProfile {
|
||||
candidate,
|
||||
addrs,
|
||||
rtt_ms,
|
||||
});
|
||||
let request_duration = Instant::now() - start;
|
||||
|
||||
return match result {
|
||||
Ok(return_addr) => if return_addr.port() == local_socket.local_addr().unwrap().port() {
|
||||
debug!("{:<25} -> Socket {:<21} returned a healthy response", hostname, &socket_addr);
|
||||
StunSocketTestResult {
|
||||
socket: socket_addr,
|
||||
result: StunSocketResponse::HealthyResponse { rtt: request_duration },
|
||||
}
|
||||
} else {
|
||||
debug!("{:<25} -> Socket {:<21} returned an invalid mapping: expected={}, actual={}", hostname, &socket_addr, local_socket.local_addr().unwrap(), return_addr);
|
||||
StunSocketTestResult {
|
||||
socket: socket_addr,
|
||||
result: StunSocketResponse::InvalidMappingResponse { expected: local_socket.local_addr().unwrap(), actual: return_addr, rtt: request_duration },
|
||||
}
|
||||
},
|
||||
Err(err) => {
|
||||
if err.to_string() == "Timed out waiting for STUN server reply" {
|
||||
debug!("{:<25} -> Socket {:<21} timed out after {:?}", hostname, &socket_addr, deadline);
|
||||
StunSocketTestResult {
|
||||
socket: socket_addr,
|
||||
result: StunSocketResponse::Timeout { deadline },
|
||||
}
|
||||
} else {
|
||||
debug!("{:<25} -> Socket {:<21} returned an unexpected error: {:?}", hostname, &socket_addr, err.to_string());
|
||||
StunSocketTestResult {
|
||||
socket: socket_addr,
|
||||
result: StunSocketResponse::UnexpectedError { err: err.to_string() },
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let ip_fails = responses.iter()
|
||||
.filter_map(|response| response.err())
|
||||
.filter(|err| err == &CheckError::IncorrectMappingReturned)
|
||||
.count();
|
||||
|
||||
if ip_fails > 0 {
|
||||
return Err(CheckError::IncorrectMappingReturned);
|
||||
}
|
||||
|
||||
let timeouts = responses.iter()
|
||||
.filter_map(|response| response.err())
|
||||
.filter(|err| err == &CheckError::Timeout)
|
||||
.count();
|
||||
|
||||
if timeouts < responses.len() {
|
||||
return Err(CheckError::PartialTimeout);
|
||||
}
|
||||
|
||||
return Err(CheckError::Timeout);
|
||||
}
|
||||
|
34
src/utils.rs
Normal file
34
src/utils.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use std::fmt::Display;
|
||||
use std::future::Future;
|
||||
use std::rc::Rc;
|
||||
use tokio::sync::Semaphore;
|
||||
|
||||
pub(crate) async fn join_all_with_semaphore<T: Iterator<Item=U>, U: Future<Output = V>, V> (
|
||||
it: T,
|
||||
permits: usize,
|
||||
) -> Vec<V> {
|
||||
let semaphore = Rc::new(Semaphore::new(permits));
|
||||
let pending_futures = it.map(|it| {
|
||||
let semaphore_local = semaphore.clone();
|
||||
async move {
|
||||
let permit = semaphore_local.acquire().await.unwrap();
|
||||
let output = it.await;
|
||||
drop(permit);
|
||||
output
|
||||
}
|
||||
}).collect::<Vec<_>>();
|
||||
let resolved_futures = futures::future::join_all(pending_futures).await;
|
||||
resolved_futures
|
||||
}
|
||||
|
||||
pub(crate) trait ReduceToString<U: Display> : Iterator<Item=U> {
|
||||
fn reduce_to_string(self) -> String where Self: Sized {
|
||||
let mut s = String::from("");
|
||||
for it in self {
|
||||
s.push_str(it.to_string().as_str());
|
||||
}
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
impl <I: Iterator<Item=U>, U: Display> ReduceToString<U> for I {}
|
Reference in New Issue
Block a user