From 2c80ed4783f8a13755c89ad36f4313ca3174d052 Mon Sep 17 00:00:00 2001 From: SeaLoong <984391132@qq.com> Date: Mon, 21 Sep 2020 16:00:09 +0800 Subject: [PATCH] upload --- .gitattributes | 2 + .gitignore | 8 + Cargo.lock | 1598 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 25 + LICENSE | 165 +++++ README.md | 2 + drcom4scut.iml | 12 + src/constants.rs | 5 + src/device.rs | 171 +++++ src/eap.rs | 575 ++++++++++++++++ src/eap/packet.rs | 184 ++++++ src/main.rs | 233 +++++++ src/settings.rs | 414 ++++++++++++ src/socket.rs | 102 +++ src/udp.rs | 541 +++++++++++++++ src/udp/packet.rs | 270 ++++++++ src/util.rs | 71 ++ 17 files changed, 4378 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 LICENSE create mode 100644 README.md create mode 100644 drcom4scut.iml create mode 100644 src/constants.rs create mode 100644 src/device.rs create mode 100644 src/eap.rs create mode 100644 src/eap/packet.rs create mode 100644 src/main.rs create mode 100644 src/settings.rs create mode 100644 src/socket.rs create mode 100644 src/udp.rs create mode 100644 src/udp/packet.rs create mode 100644 src/util.rs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..dfe0770 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Auto detect text files and perform LF normalization +* text=auto diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7b4980f --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ + +.idea + +#Added by cargo + +/target +/logs +config.yml diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..efe1439 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1598 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + +[[package]] +name = "arrayvec" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "async-trait" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687c230d85c0a52504709705fc8a53e4a692b83a2184f03dae73e38e1e93a783" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "backtrace" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "bitflags" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f67931368edf3a9a51d29886d245f1c3db2f1ef0dcc9e35ff70341b78c10d23" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "chrono" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "942f72db697d8767c22d46a598e01f2d3b475501ea43d0db4f16d90259182d0b" +dependencies = [ + "num-integer", + "num-traits 0.2.12", + "time", +] + +[[package]] +name = "clap" +version = "2.33.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.2.1", + "strsim", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "cloudabi" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4344512281c643ae7638bbabc3af17a11307803ec8f0fcad9fae512a8bf36467" +dependencies = [ + "bitflags 1.2.1", +] + +[[package]] +name = "config" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" +dependencies = [ + "lazy_static", + "nom", + "rust-ini", + "serde 1.0.115", + "serde-hjson", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "crc32fast" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69323bff1fb41c635347b8ead484a5ca6c3f11914d784170b158d8449ab07f8e" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b153fe7cbef478c567df0f972e02e6d736db11affe43dfc9c56a9374d1adfb87" +dependencies = [ + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-deque" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "lazy_static", + "maybe-uninit", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "774ba60a54c213d409d5353bda12d49cd68d14e45036a285234c8d6f91f92570" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "maybe-uninit", +] + +[[package]] +name = "crossbeam-utils" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "drcom4scut" +version = "0.1.0" +dependencies = [ + "bytes", + "chrono", + "clap", + "config", + "crossbeam", + "encoding_rs", + "hex", + "hostname", + "lazy_static", + "log 0.4.11", + "log4rs", + "md-5", + "pnet", + "rand", + "trust-dns-resolver", +] + +[[package]] +name = "dtoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b" + +[[package]] +name = "encoding_rs" +version = "0.8.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2" +dependencies = [ + "cfg-if", +] + +[[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 = "flate2" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "766d0e77a2c1502169d4a93ff3b8c15a71fd946cd0126309752104e5f3c46d94" +dependencies = [ + "cfg-if", + "crc32fast", + "libc", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags 1.2.1", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "futures-io" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789" + +[[package]] +name = "futures-sink" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc" + +[[package]] +name = "futures-task" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" + +[[package]] +name = "glob" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be18de09a56b60ed0edf84bc9df007e30040691af7acd1c41874faac5895bfb" + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3deed196b6e7f9e44a2ae8d94225d80302d81208b1bb673fd21fe634645c85a9" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi 0.3.9", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "idna" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "instant" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b141fdc7836c525d4d594027d318c84161ca17aaf8113ab1f81ab93ae897485" + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "ipconfig" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7" +dependencies = [ + "socket2", + "widestring", + "winapi 0.3.9", + "winreg", +] + +[[package]] +name = "ipnetwork" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8eca9f51da27bc908ef3dd85c21e1bbba794edaf94d7841e37356275b82d31e" +dependencies = [ + "serde 1.0.115", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lexical-core" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616" +dependencies = [ + "arrayvec", + "bitflags 1.2.1", + "cfg-if", + "ryu", + "static_assertions", +] + +[[package]] +name = "libc" +version = "0.2.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" + +[[package]] +name = "linked-hash-map" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd" +dependencies = [ + "serde 0.8.23", + "serde_test", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" + +[[package]] +name = "lock_api" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28247cc5a5be2f05fbcd76dd0cf2c7d3b5400cb978a28042abcd4fa0b3f8261c" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.11", +] + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", + "serde 1.0.115", +] + +[[package]] +name = "log-mdc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a94d21414c1f4a51209ad204c1776a3d0765002c76c6abcb602a6f09f1e881c7" + +[[package]] +name = "log4rs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1e1ad45e4584824d760c35d71868dd7e6e5acd8f5195a9573743b369fc86cd6" +dependencies = [ + "arc-swap", + "chrono", + "flate2", + "fnv", + "humantime", + "libc", + "log 0.4.11", + "log-mdc", + "parking_lot", + "serde 1.0.115", + "serde-value", + "serde_derive", + "serde_json", + "serde_yaml", + "thread-id", + "typemap", + "winapi 0.3.9", +] + +[[package]] +name = "lru-cache" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" +dependencies = [ + "linked-hash-map 0.5.3", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" + +[[package]] +name = "maybe-uninit" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" + +[[package]] +name = "md-5" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15" +dependencies = [ + "block-buffer", + "digest", + "opaque-debug", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "memoffset" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log 0.4.11", + "miow", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "net2" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "lexical-core", + "memchr", + "version_check", +] + +[[package]] +name = "num-integer" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" +dependencies = [ + "autocfg", + "num-traits 0.2.12", +] + +[[package]] +name = "num-traits" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31" +dependencies = [ + "num-traits 0.2.12", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "object" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" + +[[package]] +name = "once_cell" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "ordered-float" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3741934be594d77de1c8461ebcbbe866f585ea616a9753aa78f2bdc69f0e4579" +dependencies = [ + "num-traits 0.2.12", +] + +[[package]] +name = "parking_lot" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4893845fa2ca272e647da5d0e46660a314ead9c2fdd9a883aabc32e481a8733" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c361aa727dd08437f2f1447be8b59a33b0edd15e0fcee698f935613d9efbca9b" +dependencies = [ + "cfg-if", + "cloudabi", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi 0.3.9", +] + +[[package]] +name = "percent-encoding" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" + +[[package]] +name = "pin-project" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca4433fff2ae79342e497d9f8ee990d174071408f28f726d6d83af93e58e48aa" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c0e815c3ee9a031fdf5af21c10aa17c573c9c6a566328d99e3936c34e36461f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pnet" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df42dcd72f6f2a658bcf38509f1027df1440ac85f1af4badbe034418302dc" +dependencies = [ + "ipnetwork", + "pnet_base", + "pnet_datalink", + "pnet_packet", + "pnet_sys", + "pnet_transport", +] + +[[package]] +name = "pnet_base" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cd5f7e15220afa66b0a9a62841ea10089f39dcaa1c29752c0b22dfc03111b5" + +[[package]] +name = "pnet_datalink" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7318ae1d6e0b7fa1e49933233c9473f2b72d3d18b97e70e2716c6415dde5f915" +dependencies = [ + "ipnetwork", + "libc", + "pnet_base", + "pnet_sys", + "winapi 0.2.8", +] + +[[package]] +name = "pnet_macros" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbd5c52c6e04aa720400f9c71cd0e8bcb38cd13421d5caabd9035e9efa47de9" +dependencies = [ + "regex", + "syntex", + "syntex_syntax", +] + +[[package]] +name = "pnet_macros_support" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf9c5c0c36766d0a4da9ab268c0700771b8ec367b9463fd678109fa28463c5b" +dependencies = [ + "pnet_base", +] + +[[package]] +name = "pnet_packet" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89e26a864d71d0ac51a549cf40283c44ed1b8f98168545638a4730ef9f560283" +dependencies = [ + "glob", + "pnet_base", + "pnet_macros", + "pnet_macros_support", + "syntex", +] + +[[package]] +name = "pnet_sys" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f0de0c52609f157b25d79ce24d9016ab1bbf10cde761397200d634a833872c" +dependencies = [ + "libc", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "pnet_transport" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6712ab76534340494d849e3c51c64a6261e4b451337b7c05bd3681e384c48b10" +dependencies = [ + "libc", + "pnet_base", + "pnet_packet", + "pnet_sys", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c36fa947111f5c62a733b652544dd0016a43ce89619538a8ef92724a6f501a20" + +[[package]] +name = "proc-macro2" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175c513d55719db99da20232b06cda8bab6b83ec2d04e3283edf0213c37c1a29" +dependencies = [ + "unicode-xid 0.2.1", +] + +[[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.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "resolv-conf" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a" +dependencies = [ + "hostname", + "quick-error", +] + +[[package]] +name = "rust-ini" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e52c148ef37f8c375d49d5a73aa70713125b7f19095948a923f80afdeb22ec2" + +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + +[[package]] +name = "rustc-serialize" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" + +[[package]] +name = "serde" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54c9a88f2da7238af84b5101443f0c0d0a3bbdc455e34a5c9497b1903ed55d5" + +[[package]] +name = "serde-hjson" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8" +dependencies = [ + "lazy_static", + "linked-hash-map 0.3.0", + "num-traits 0.1.43", + "regex", + "serde 0.8.23", +] + +[[package]] +name = "serde-value" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a65a7291a8a568adcae4c10a677ebcedbc6c9cec91c054dee2ce40b0e3290eb" +dependencies = [ + "ordered-float", + "serde 1.0.115", +] + +[[package]] +name = "serde_derive" +version = "1.0.115" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "609feed1d0a73cc36a0182a840a9b37b4a82f0b1150369f0536a9e3f2a31dc48" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +dependencies = [ + "itoa", + "ryu", + "serde 1.0.115", +] + +[[package]] +name = "serde_test" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5" +dependencies = [ + "serde 0.8.23", +] + +[[package]] +name = "serde_yaml" +version = "0.8.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3e2dd40a7cdc18ca80db804b7f461a39bb721160a85c9a1fa30134bf3c02a5" +dependencies = [ + "dtoa", + "linked-hash-map 0.5.3", + "serde 1.0.115", + "yaml-rust", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "smallvec" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbee7696b84bbf3d89a1c2eccff0850e3047ed46bfcd2e92c29a2d074d57e252" + +[[package]] +name = "socket2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "syn" +version = "1.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid 0.2.1", +] + +[[package]] +name = "syntex" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a30b08a6b383a22e5f6edc127d169670d48f905bb00ca79a00ea3e442ebe317" +dependencies = [ + "syntex_errors", + "syntex_syntax", +] + +[[package]] +name = "syntex_errors" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c48f32867b6114449155b2a82114b86d4b09e1bddb21c47ff104ab9172b646" +dependencies = [ + "libc", + "log 0.3.9", + "rustc-serialize", + "syntex_pos", + "term", + "unicode-xid 0.0.3", +] + +[[package]] +name = "syntex_pos" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fd49988e52451813c61fecbe9abb5cfd4e1b7bb6cdbb980a6fbcbab859171a6" +dependencies = [ + "rustc-serialize", +] + +[[package]] +name = "syntex_syntax" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7628a0506e8f9666fdabb5f265d0059b059edac9a3f810bda077abb5d826bd8d" +dependencies = [ + "bitflags 0.5.0", + "libc", + "log 0.3.9", + "rustc-serialize", + "syntex_errors", + "syntex_pos", + "term", + "unicode-xid 0.0.3", +] + +[[package]] +name = "term" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa63644f74ce96fbeb9b794f66aff2a52d601cbd5e80f4b97123e3899f4570f1" +dependencies = [ + "kernel32-sys", + "winapi 0.2.8", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thiserror" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread-id" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7fbf4c9d56b320106cd64fd024dadfa0be7cb4706725fc44a7d7ce952d820c1" +dependencies = [ + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "time" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" +dependencies = [ + "libc", + "wasi 0.10.0+wasi-snapshot-preview1", + "winapi 0.3.9", +] + +[[package]] +name = "tinyvec" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "238ce071d267c5710f9d31451efec16c5ee22de34df17cc05e56cbc92e967117" + +[[package]] +name = "tokio" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" +dependencies = [ + "bytes", + "iovec", + "lazy_static", + "mio", + "pin-project-lite", + "slab", +] + +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde 1.0.115", +] + +[[package]] +name = "traitobject" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd1f82c56340fdf16f2a953d7bda4f8fdffba13d93b00844c25572110b26079" + +[[package]] +name = "trust-dns-proto" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd7061ba6f4d4d9721afedffbfd403f20f39a4301fee1b70d6fcd09cca69f28" +dependencies = [ + "async-trait", + "backtrace", + "enum-as-inner", + "futures", + "idna", + "lazy_static", + "log 0.4.11", + "rand", + "smallvec", + "thiserror", + "tokio", + "url", +] + +[[package]] +name = "trust-dns-resolver" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f23cdfdc3d8300b3c50c9e84302d3bd6d860fb9529af84ace6cf9665f181b77" +dependencies = [ + "backtrace", + "cfg-if", + "futures", + "ipconfig", + "lazy_static", + "log 0.4.11", + "lru-cache", + "resolv-conf", + "smallvec", + "thiserror", + "tokio", + "trust-dns-proto", +] + +[[package]] +name = "typemap" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "653be63c80a3296da5551e1bfd2cca35227e13cdd08c6668903ae2f4f77aa1f6" +dependencies = [ + "unsafe-any", +] + +[[package]] +name = "typenum" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" + +[[package]] +name = "unicode-bidi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] + +[[package]] +name = "unicode-normalization" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36dff09cafb4ec7c8cf0023eb0b686cb6ce65499116a12201c9e11840ca01beb" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "unsafe-any" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30360d7979f5e9c6e6cea48af192ea8fab4afb3cf72597154b8f08935bc9c7f" +dependencies = [ + "traitobject", +] + +[[package]] +name = "url" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb" +dependencies = [ + "idna", + "matches", + "percent-encoding", +] + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.10.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" + +[[package]] +name = "widestring" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[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 0.3.9", +] + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "yaml-rust" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39f0c922f1a334134dc2f7a8b67dc5d25f0735263feec974345ff706bcf20b0d" +dependencies = [ + "linked-hash-map 0.5.3", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4e65fae --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "drcom4scut" +version = "0.1.0" +authors = ["SeaLoong <984391132@qq.com>"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.7.3" +lazy_static = "1.4.0" +log = "0.4.11" +log4rs = "0.13.0" +chrono = "0.4" +clap = "2" +config = "0.10" +bytes = "0.5" +hex = "0.4" +# tokio = { version = "0.2", features = ["full"] } +pnet = "0.26.0" +md-5 = "0.9.1" +trust-dns-resolver = "0.19.5" +hostname = "0.3.1" +encoding_rs = "0.8.24" +crossbeam = "0.7.3" \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8805d0d --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5310d25 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# drcom4scut + diff --git a/drcom4scut.iml b/drcom4scut.iml new file mode 100644 index 0000000..2fecef3 --- /dev/null +++ b/drcom4scut.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/constants.rs b/src/constants.rs new file mode 100644 index 0000000..72f0b35 --- /dev/null +++ b/src/constants.rs @@ -0,0 +1,5 @@ +pub mod state { + pub const SUCCESS: u8 = 1; + pub const STOP: u8 = 2; + pub const SLEEP: u8 = 3; +} diff --git a/src/device.rs b/src/device.rs new file mode 100644 index 0000000..8e4624f --- /dev/null +++ b/src/device.rs @@ -0,0 +1,171 @@ +use pnet::datalink::{ + channel, interfaces, Channel, Config, DataLinkReceiver, DataLinkSender, MacAddr, + NetworkInterface, +}; +use pnet::ipnetwork::IpNetwork; +use std::cell::RefCell; +use std::io::{Error, ErrorKind, Result}; +use std::net::{IpAddr, Ipv4Addr}; + +pub struct Device { + pub interface: NetworkInterface, + pub mac: MacAddr, + pub ip_net: IpNetwork, + sender: RefCell>, + receiver: RefCell>, +} + +unsafe impl Send for Device {} +unsafe impl Sync for Device {} + +#[inline] +fn filter_interface bool>(f: P) -> Option { + interfaces().into_iter().find(f) +} + +fn get_first_valid_ip(e: &NetworkInterface) -> Option<&IpNetwork> { + for ip_net in &e.ips { + if ip_net.prefix() != 0 { + let ip = ip_net.ip(); + if ip.is_global() { + return Some(ip_net); + } + } + } + None +} + +pub fn get_all_interfaces() -> Vec { + let mut all_interfaces = interfaces(); + all_interfaces.sort_by(|a, b| a.index.cmp(&b.index)); + all_interfaces +} + +pub fn get_device(mac: Option, ip: Option) -> Result { + if let Some(m) = mac { + return Device::from_mac(m); + } + if let Some(ip) = ip { + return Device::from_ip(ip); + } + Device::default() +} + +impl Device { + pub fn from_mac(mac: MacAddr) -> Result { + let interface = filter_interface(|e| match e.mac { + Some(ref m) => *m == mac, + None => false, + }); + match interface { + Some(interface) => Device::new(interface), + None => Err(Error::new( + ErrorKind::NotFound, + format!("Can't get interface which MAC address is {}.", mac), + )), + } + } + + pub fn from_ip(ip: IpAddr) -> Result { + let mut ip_net = IpNetwork::new(IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), 0).unwrap(); + let interface = filter_interface(|e| { + for i in &e.ips { + if i.ip() == ip { + ip_net = *i; + return true; + } + } + false + }); + match interface { + Some(interface) => Device::with_ip_net(interface, ip_net), + None => Err(Error::new( + ErrorKind::NotFound, + format!("Can't get interface which IP address is {}.", ip), + )), + } + } + + pub fn new(interface: NetworkInterface) -> Result { + let ip = if let Some(ip) = get_first_valid_ip(&interface) { + ip.to_owned() + } else if let Some(ip) = interface.ips.get(0) { + ip.to_owned() + } else { + IpNetwork::new(IpAddr::from(Ipv4Addr::new(0, 0, 0, 0)), 0).unwrap() + }; + Device::with_ip_net(interface, ip) + } + + pub fn with_ip_net(interface: NetworkInterface, ip_net: IpNetwork) -> Result { + if interface.mac.is_none() { + return Err(Error::new( + ErrorKind::InvalidInput, + format!( + "The interface {} has not a valid MAC address.", + interface.name + ), + )); + } + let ch = channel(&interface, Config::default()); + match ch { + Ok(Channel::Ethernet(tx, rx)) => Ok(Device { + sender: RefCell::new(tx), + receiver: RefCell::new(rx), + mac: interface.mac.unwrap(), + ip_net, + interface, + }), + Ok(_) => Err(Error::new( + ErrorKind::Other, + format!( + "Created an unknown channel type on Device {}.", + interface.name + ), + )), + Err(err) => Err(err), + } + } + + pub fn default() -> Result { + let all_interfaces = get_all_interfaces(); + for e in &all_interfaces { + if let Some(ip_net) = get_first_valid_ip(e) { + return Device::with_ip_net(e.to_owned(), ip_net.to_owned()); + } + } + if let Some(e) = all_interfaces.get(0) { + Device::new(e.to_owned()) + } else { + Err(Error::new( + ErrorKind::NotFound, + "Can't get the default interface.", + )) + } + } + + pub fn send(&self, data: Vec) -> Result<()> { + let mut sender = self.sender.borrow_mut(); + loop { + if let Some(r) = sender.send_to(&data[..], None) { + return r; + } + } + } + + pub fn receive(&self) -> Result> { + Ok(self.receiver.borrow_mut().next()?.to_vec()) + } +} + +#[test] +fn test_device() { + println!("{:?}", Device::default().unwrap().mac); + assert!(Device::from_mac(MacAddr::new(0, 0, 0, 0, 0, 0)).is_err()); + println!( + "{:?}", + Device::from_ip(IpAddr::from([125, 217, 254, 225])) + .unwrap() + .mac + ); +} diff --git a/src/eap.rs b/src/eap.rs new file mode 100644 index 0000000..d4e19f8 --- /dev/null +++ b/src/eap.rs @@ -0,0 +1,575 @@ +use std::sync::Arc; +use std::time::Duration; + +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use log::{debug, error, info, warn}; +use md5::Digest; +use pnet::datalink::MacAddr; + +use crate::constants; +use crate::device::Device; +use crate::eap::packet::*; +use crate::settings::Settings; +use crate::util::*; +use chrono::Local; +use crossbeam::{Receiver, Sender, TryRecvError}; +use std::str::FromStr; +use std::sync::atomic::{AtomicBool, AtomicI64, Ordering}; +use std::thread; +use std::thread::JoinHandle; + +mod packet; + +const MULTICAST_MAC: MacAddr = MacAddr(0x01, 0x80, 0xc2, 0x00, 0x00, 0x03); + +#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)] +struct ProcessData { + ip: Vec, + md5_extra_data: Vec, + md5: Vec, + response_identity_packet: Option>, +} + +pub struct Process { + eth_header: EthernetHeader, + settings: Arc, + device: Arc, + tx: Sender, + timeout: Arc, + stop: Arc, + data: ProcessData, + receive_channel: Option>>, + send_channel: Option, bool)>>, + send_ts: Arc, + cancel_resend: Arc, + receiver_handle: Option>>, + resender_handle: Option>>, + sender_handle: Option>>, + heartbeat_handle: Option>>, +} + +impl Process { + pub fn new(settings: Arc, device: Arc, tx: Sender) -> Process { + Process { + eth_header: EthernetHeader { + destination: MULTICAST_MAC, + source: device.mac, + ethernet_type: ethernet_types::IEEE8021X, + }, + data: { + let mut d = ProcessData::default(); + d.ip = ip_to_vec(&device.ip_net.ip()); + d + }, + tx, + timeout: Arc::new(AtomicBool::new(false)), + stop: Arc::new(AtomicBool::new(false)), + settings, + device, + receive_channel: None, + send_channel: None, + send_ts: Arc::new(AtomicI64::new(0)), + cancel_resend: Arc::new(AtomicBool::new(true)), + receiver_handle: None, + resender_handle: None, + sender_handle: None, + heartbeat_handle: None, + } + } + + fn start_receive_thread(&mut self) { + if let Some(handle) = &self.receiver_handle { + handle.thread().unpark(); + return; + } + let (tx, rx) = crossbeam::unbounded::>(); + self.receive_channel = Some(rx); + let stop = self.stop.clone(); + let interval = self.settings.retry_interval; + let device = self.device.clone(); + self.receiver_handle = Some(Arc::new( + thread::Builder::new() + .name("EAP-Receiver".to_owned()) + .spawn(move || { + let duration = Duration::from_millis(interval as u64); + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + match device.receive() { + Ok(v) => { + tx.send(v) + .expect("Unexpected! Receive channel is disconnected!"); + } + Err(e) => { + error!("Receive error: {}", e); + thread::sleep(duration); + } + } + } + }) + .expect("Can't create EAP-Receiver thread."), + )); + } + + fn start_send_thread(&mut self) { + if let Some(handle) = &self.resender_handle { + handle.thread().unpark(); + if let Some(handle) = &self.sender_handle { + handle.thread().unpark(); + } + return; + } + let (tx, rx) = crossbeam::unbounded::<(Vec, bool)>(); + self.send_channel = Some(tx.clone()); + let stop = self.stop.clone(); + let interval = self.settings.retry_interval; + let send_ts = self.send_ts.clone(); + let cancel_resend = self.cancel_resend.clone(); + let resender_handle = Arc::new( + thread::Builder::new() + .name("EAP-Resender".to_owned()) + .spawn(move || loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + while cancel_resend.load(Ordering::Acquire) { + thread::park(); + } + let wait_ts = send_ts.load(Ordering::Acquire) - Local::now().timestamp_millis() + + (interval as i64); + if wait_ts > 0 { + thread::sleep(Duration::from_millis(wait_ts as u64)); + } else if wait_ts > -interval as i64 { + debug!("Resending..."); + tx.send((Vec::new(), true)) + .expect("Unexpected! Send channel is disconnected!"); + cancel_resend.store(true, Ordering::Release); + } + }) + .expect("Can't create EAP-ReSender thread."), + ); + self.resender_handle = Some(resender_handle.clone()); + let stop = self.stop.clone(); + let device = self.device.clone(); + let interval = self.settings.retry_interval; + let send_ts = self.send_ts.clone(); + let cancel_resend = self.cancel_resend.clone(); + self.sender_handle = Some(Arc::new( + thread::Builder::new() + .name("EAP-Sender".to_owned()) + .spawn(move || { + let duration = Duration::from_millis(interval as u64); + let mut data = Vec::new(); + let mut resend = false; + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + match rx.recv() { + Ok((v, b)) => { + debug!("Sender received."); + if !v.is_empty() { + data = v; + resend = b; + } + } + Err(_) => panic!("Unexpected! Send channel is disconnected!"), + } + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + debug!("Sender is sending packet: {}", hex::encode(&data[..])); + match device.send(data.clone()) { + Ok(_) => { + send_ts + .store(Local::now().timestamp_millis(), Ordering::Release); + cancel_resend.store(!resend, Ordering::Release); + if resend { + resender_handle.thread().unpark(); + } + break; + } + Err(e) => { + error!("Send error: {}", e); + thread::sleep(duration); + } + } + } + debug!("Sender sent packet."); + } + }) + .expect("Can't create EAP-Sender thread."), + )); + } + + fn receive(&self) -> Option> { + let channel = self.receive_channel.as_ref().unwrap(); + match channel.try_recv() { + Ok(v) => Some(v), + Err(TryRecvError::Disconnected) => { + panic!("Unexpected! Receive channel is disconnected!"); + } + Err(TryRecvError::Empty) => None, + } + } + + fn send(&self, data: Vec, resend: bool) { + let mut data = Vec::from(data); + let l = 96 - data.len(); + if l > 0 { + data.extend_from_slice(&[0u8].repeat(l)); + } + debug!("Will blocking send..."); + let channel = self.send_channel.as_ref().unwrap(); + channel + .send((data, resend)) + .expect("Unexpected! Send channel is disconnected!"); + debug!("Sent."); + } + + fn cancel_resend(&self) { + self.cancel_resend.store(true, Ordering::Release); + } + + pub fn start(&mut self) -> bool { + self.stop.store(false, Ordering::Relaxed); + self.start_receive_thread(); + self.start_send_thread(); + self.login_start(); + let mut ret = false; + while !self.stop.load(Ordering::Relaxed) { + let raw = match self.receive() { + Some(v) => v, + None => { + sleep(); + continue; + } + }; + let bytes = &mut Bytes::copy_from_slice(&raw[..]); + let header = Header::from_bytes(bytes); + // check ethernet header + if header.ethernet_header.is_none() { + continue; + } + let eth_header = header.ethernet_header.unwrap(); + if eth_header.ethernet_type != ethernet_types::IEEE8021X + || !eth_header.is_send_to(&self.eth_header) + { + continue; + } + // check eapol header + if header.eapol_header.is_none() { + continue; + } + let eapol_header = header.eapol_header.unwrap(); + if eapol_header.version != 1 { + continue; + } + match eapol_header.eapol_type { + eapol_types::EAP_PACKET => { + // check eap header + if header.eap_header.is_none() { + continue; + } + debug!("Received: {}", hex::encode(&raw)); + let eap_header = header.eap_header.unwrap(); + match eap_header.code { + eap_codes::REQUEST => { + if eap_header.eap_type.is_none() { + continue; + } + match eap_header.eap_type.unwrap() { + eap_types::IDENTITY => { + self.on_request_identity(ð_header, &eap_header) + } + eap_types::NOTIFICATION => { + ret = self.on_request_notification(&eap_header, bytes); // sleep if true + if ret { + self.tx + .send(ChannelData { + state: constants::state::SLEEP, + data: Vec::new(), + }) + .expect("Can't send SLEEP message to UDP receiver."); + } + self.stop.store(true, Ordering::Relaxed); + } + eap_types::MD5_CHALLENGE => { + self.on_request_md5_challenge(&eap_header, bytes) + } + _ => error!( + "Unexpected packet EAP Type: {}", + eap_header.eap_type.unwrap().0 + ), + } + } + eap_codes::RESPONSE => { + error!("Unexpected packet received: RESPONSE"); + } + eap_codes::SUCCESS => self.on_success(), + eap_codes::FAILURE => { + debug!("Received: FAILURE"); + } + _ => error!("Unexpected packet EAP Code: {}", eap_header.code.0), + } + } + eapol_types::EAPOL_START => { + error!("Unexpected packet received: EAPOL_START"); + } + eapol_types::EAPOL_LOGOFF => { + error!("Unexpected packet received: EAPOL_LOGOFF"); + } + _ => error!( + "Unexpected packet EAPOL Type: {}", + eapol_header.eapol_type.0 + ), + } + } + ret + } + + #[inline] + fn login_start(&mut self) { + self.timeout.store(false, Ordering::Relaxed); + self.data.response_identity_packet = None; + self.send_logoff(); + thread::sleep(Duration::from_secs(3)); + self.send_start(); + } + + fn on_request_identity(&mut self, eth_header: &EthernetHeader, eap_header: &EAPHeader) { + self.cancel_resend(); + self.timeout.store(false, Ordering::Relaxed); + if let Some(ref mut v) = self.data.response_identity_packet { + v[19] = eap_header.identifier; + let v = v.clone(); + info!("Send Heartbeat(Response, Identity) packet."); + self.send(v, false); + } else if eap_header.identifier > 1 { + warn!("Maybe you have been login."); + self.login_start() + } else { + self.eth_header.destination = eth_header.source; + self.send_response_identity(&eap_header) + } + } + + fn on_request_notification(&mut self, eap_header: &EAPHeader, bytes: &mut Bytes) -> bool { + if bytes.len() < (eap_header.length - 5) as usize { + error!("NOTIFICATION: Unexpected payload!"); + } else { + self.cancel_resend(); + match String::from_utf8(bytes.split_to((eap_header.length - 5) as usize).to_vec()) { + Ok(s) => { + error!("{}", s); + if let Some(s) = s.strip_prefix("userid error") { + if let Ok(x) = i32::from_str(s) { + match x { + 1 => error!("Account does not exist."), + 2 | 3 => error!("Username or password invalid."), + 4 => error!("This account might be expended."), + _ => (), + } + } + } else if let Some(s) = s.strip_prefix("Authentication Fail ErrCode=") { + if let Ok(x) = i32::from_str(s) { + match x { + 0 => error!("Username or password invalid."), + 5 => error!("This account is suspended."), + 9 => error!("This account might be expended."), + 11 => error!( + "You are not allowed to perform a radius authentication." + ), + 16 => { + error!("You are not allowed to access the internet now."); + return true; + } + 30 | 63 => error!("No more time available for this account."), + _ => (), + } + } + } else if s.strip_prefix("AdminReset").is_some() { + error!("AdminReset.") + } else if s.strip_prefix("Mac, IP, NASip, PORT").is_some() { + error!("You are not allowed to login using current IP/MAC address.") + } else if s.strip_prefix("flowover").is_some() { + error!("Data usage has reached the limit.") + } else if s.strip_prefix("In use").is_some() { + error!("This account is in use.") + } + } + Err(_) => { + error!("NOTIFICATION: Parse string failed!"); + } + } + } + false + } + + fn on_request_md5_challenge(&mut self, eap_header: &EAPHeader, bytes: &mut Bytes) { + if bytes.len() < (eap_header.length - 5) as usize { + error!("MD5 Challenge: Unexpected payload!"); + } else { + self.cancel_resend(); + self.send_response_md5_challenge(&eap_header, bytes) + } + } + + fn on_success(&mut self) { + self.cancel_resend(); + info!("802.1X Authorization success!"); + self.start_heartbeat_thread(); + } + + fn start_heartbeat_thread(&mut self) { + if let Some(handle) = &self.heartbeat_handle { + handle.thread().unpark(); + return; + } + let stop = self.stop.clone(); + let eap_timeout = self.settings.heartbeat.eap_timeout; + let timeout = self.timeout.clone(); + let tx = self.tx.clone(); + self.heartbeat_handle = Some(Arc::new( + thread::Builder::new() + .name("EAP-Heartbeat".to_owned()) + .spawn(move || { + let duration = Duration::from_secs(eap_timeout as u64); + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + if timeout.load(Ordering::Relaxed) { + error!( + "Heartbeat timeout! No Request, Identity packet received for {}s.", + eap_timeout + ); + stop.store(true, Ordering::Relaxed); + tx.send(ChannelData { + state: constants::state::STOP, + data: Vec::new(), + }) + .expect("Can't send STOP message to UDP receiver."); + break; + } + timeout.store(true, Ordering::Relaxed); + std::thread::sleep(duration); + } + }) + .expect("Can't create EAP-Heartbeat thread!"), + )); + // notify UDP process should start + self.tx + .send(ChannelData { + state: constants::state::SUCCESS, + data: self.data.md5.clone(), + }) + .expect("Can't send SUCCESS message to UDP receiver."); + } + + fn send_logoff(&mut self) { + info!("Send Logoff packet."); + let data = &mut BytesMut::with_capacity(96); + self.eth_header.destination = MULTICAST_MAC; + self.eth_header.append_to(data); + EAPOL_HEADER_LOGOFF.append_to(data); + self.send(data.to_vec(), false) + // Failure + } + + fn send_start(&mut self) { + info!("Send Start packet."); + let data = &mut BytesMut::with_capacity(96); + self.eth_header.destination = MULTICAST_MAC; + self.eth_header.append_to(data); + EAPOL_HEADER_START.append_to(data); + self.send(data.to_vec(), true) + // Request, Identity + } + + fn send_response_identity(&mut self, eap_header: &EAPHeader) { + info!("Send Response, Identity packet."); + let payload = &mut BytesMut::with_capacity(96); + payload.put(self.settings.username.as_bytes()); + payload.put(&self.settings.data.response_identity.unknown[..]); + payload.put(&self.data.ip[..]); + let length = payload.len() as u16 + 5; + let data = &mut BytesMut::with_capacity(96); + self.eth_header.append_to(data); + EAPOLHeader { + version: 1, + eapol_type: eapol_types::EAP_PACKET, + length, + } + .append_to(data); + EAPHeader { + code: eap_codes::RESPONSE, + identifier: eap_header.identifier, + length, + eap_type: Some(eap_types::IDENTITY), + } + .append_to(data); + data.put(payload); + self.data.response_identity_packet = Some(data.to_vec()); + self.send(data.to_vec(), true) + // Request, MD5-Challenge + } + + fn send_response_md5_challenge(&mut self, eap_header: &EAPHeader, bytes: &mut Bytes) { + info!("Send Response, MD5-Challenge packet."); + let md5_size = bytes.get_u8() as usize; + let md5_value = bytes.split_to(md5_size).to_vec(); + self.data.md5_extra_data = bytes + .split_to((eap_header.length as usize) - md5_size - 6) + .to_vec(); + let md5 = &md5::Md5::digest( + &{ + let mut not_encrypt = + BytesMut::with_capacity(1 + self.settings.password.len() + md5_value.len()); + not_encrypt.put_u8(eap_header.identifier); + not_encrypt.put(self.settings.password.as_bytes()); + not_encrypt.put(&md5_value[..]); + not_encrypt + } + .bytes(), + )[..]; + self.data.md5 = md5.to_vec(); + let payload = &mut BytesMut::with_capacity(96); + payload.put_u8(md5.len() as u8); + payload.put(md5); + payload.put(self.settings.username.as_bytes()); + payload.put(&self.settings.data.response_md5_challenge.unknown[..]); + payload.put(&self.data.ip[..]); + let data = &mut BytesMut::with_capacity(96); + self.eth_header.append_to(data); + let length = payload.len() as u16 + 5; + EAPOLHeader { + version: 1, + eapol_type: eapol_types::EAP_PACKET, + length, + } + .append_to(data); + EAPHeader { + code: eap_codes::RESPONSE, + identifier: eap_header.identifier, + length, + eap_type: Some(eap_types::MD5_CHALLENGE), + } + .append_to(data); + data.put(payload); + self.send(data.to_vec(), true) + // Success + } +} + +#[test] +fn test_md5_calc() { + let mut data = BytesMut::new(); + data.put_u8(0); + data.put("qwert12345".as_bytes()); + data.put(&hex::decode("ff62b079ca26d283ca26d28300000000").unwrap()[..]); + let r = hex::encode(md5::Md5::digest(data.bytes())).to_lowercase(); + assert_eq!(&r, "313a3758ad589ce03dc6af0371c31239"); +} diff --git a/src/eap/packet.rs b/src/eap/packet.rs new file mode 100644 index 0000000..2f232bf --- /dev/null +++ b/src/eap/packet.rs @@ -0,0 +1,184 @@ +use crate::eap::packet::eapol_types::{EAPOL_LOGOFF, EAPOL_START}; +use crate::util::{get_mac, put_mac}; +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use pnet::datalink::MacAddr; + +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] +pub struct Header { + pub ethernet_header: Option, + pub eapol_header: Option, + pub eap_header: Option, +} + +impl Header { + #[inline] + pub fn from_bytes(data: &mut Bytes) -> Header { + let mut ethernet_header: Option = None; + let mut eapol_header: Option = None; + let mut eap_header: Option = None; + if let Some(pkt) = EthernetHeader::from_bytes(data) { + ethernet_header = Some(pkt); + if let Some(pkt) = EAPOLHeader::from_bytes(data) { + eapol_header = Some(pkt); + if let Some(pkt) = EAPHeader::from_bytes(data) { + eap_header = Some(pkt); + } + } + } + Header { + ethernet_header, + eapol_header, + eap_header, + } + } +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] +pub struct EthernetType(pub u16); +pub mod ethernet_types { + use crate::eap::packet::EthernetType; + pub const IEEE8021X: EthernetType = EthernetType(0x888e); +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] +pub struct EthernetHeader { + // 0~13 + pub destination: MacAddr, + pub source: MacAddr, + pub ethernet_type: EthernetType, +} + +impl EthernetHeader { + #[inline] + pub fn is_send_to(&self, to: &EthernetHeader) -> bool { + self.destination == to.source + && (to.destination.is_zero() + || to.destination.is_broadcast() + || to.destination.is_multicast() + || to.destination.is_unicast() + || to.destination == self.source) + } + #[inline] + pub fn append_to(&self, data: &mut BytesMut) { + put_mac(data, &self.destination); + put_mac(data, &self.source); + data.put_u16(self.ethernet_type.0); + } + #[inline] + pub fn from_bytes(data: &mut Bytes) -> Option { + if data.len() < 14 { + return None; + } + let data = &mut data.split_to(14); + Some(EthernetHeader { + destination: get_mac(data), + source: get_mac(data), + ethernet_type: EthernetType(data.get_u16()), + }) + } +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] +pub struct EAPOLType(pub u8); +pub mod eapol_types { + use crate::eap::packet::EAPOLType; + pub const EAP_PACKET: EAPOLType = EAPOLType(0); + pub const EAPOL_START: EAPOLType = EAPOLType(1); + pub const EAPOL_LOGOFF: EAPOLType = EAPOLType(2); +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] +pub struct EAPOLHeader { + // 14~17 + pub version: u8, + pub eapol_type: EAPOLType, + pub length: u16, +} + +impl EAPOLHeader { + #[inline] + pub fn append_to(&self, data: &mut BytesMut) { + data.put_u8(self.version); + data.put_u8(self.eapol_type.0); + data.put_u16(self.length); + } + #[inline] + pub fn from_bytes(data: &mut Bytes) -> Option { + if data.len() < 4 { + return None; + } + let mut data = data.split_to(4); + Some(EAPOLHeader { + version: data.get_u8(), + eapol_type: EAPOLType(data.get_u8()), + length: data.get_u16(), + }) + } +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] +pub struct EAPCode(pub u8); +pub mod eap_codes { + use crate::eap::packet::EAPCode; + pub const REQUEST: EAPCode = EAPCode(1); + pub const RESPONSE: EAPCode = EAPCode(2); + pub const SUCCESS: EAPCode = EAPCode(3); + pub const FAILURE: EAPCode = EAPCode(4); +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, PartialOrd, Ord, Clone, Copy)] +pub struct EAPType(pub u8); +pub mod eap_types { + use crate::eap::packet::EAPType; + pub const IDENTITY: EAPType = EAPType(1); + pub const NOTIFICATION: EAPType = EAPType(2); + pub const MD5_CHALLENGE: EAPType = EAPType(4); +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] +pub struct EAPHeader { + // 18~21/22 + pub code: EAPCode, + pub identifier: u8, + pub length: u16, + pub eap_type: Option, +} + +impl EAPHeader { + #[inline] + pub fn append_to(&self, data: &mut BytesMut) { + data.put_u8(self.code.0); + data.put_u8(self.identifier); + data.put_u16(self.length); + if let Some(t) = self.eap_type { + data.put_u8(t.0); + } + } + #[inline] + pub fn from_bytes(data: &mut Bytes) -> Option { + if data.len() < 4 { + return None; + } + Some(EAPHeader { + code: EAPCode(data.get_u8()), + identifier: data.get_u8(), + length: data.get_u16(), + eap_type: if data.is_empty() { + None + } else { + Some(EAPType(data.get_u8())) + }, + }) + } +} + +pub(crate) const EAPOL_HEADER_START: EAPOLHeader = EAPOLHeader { + version: 1, + eapol_type: EAPOL_START, + length: 0, +}; +pub(crate) const EAPOL_HEADER_LOGOFF: EAPOLHeader = EAPOLHeader { + version: 1, + eapol_type: EAPOL_LOGOFF, + length: 0, +}; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b763c03 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,233 @@ +// #![allow(unused_must_use, dead_code, unused_variables, unused_imports)] +#![feature(ip)] +extern crate lazy_static; +use crate::settings::Settings; +use crate::socket::Socket; +use crate::util::{sleep_at, ChannelData}; +use clap::clap_app; +use log::LevelFilter::{Debug, Info}; +use log::{error, info, LevelFilter}; +use std::str::FromStr; +use std::sync::Arc; +use std::thread; +use std::time::Duration; + +mod constants; +mod device; +mod eap; +mod settings; +mod socket; +mod udp; +mod util; + +fn init_logger(settings: &Settings) -> log4rs::Handle { + use log4rs::{ + append::{ + console::ConsoleAppender, + rolling_file::{ + policy::compound::{ + roll::fixed_window::FixedWindowRoller, trigger::size::SizeTrigger, + CompoundPolicy, + }, + RollingFileAppender, + }, + }, + config::{Appender, Config, Root}, + encode::pattern::PatternEncoder, + }; + let directory = &settings.log.directory; + let stdout = ConsoleAppender::builder() + .encoder(Box::new(PatternEncoder::new( + "{h([{d(%Y-%m-%d %H:%M:%S)}][{l}][{T}] {m}{n})}", + ))) + .build(); + + let logfile = RollingFileAppender::builder() + .encoder(Box::new(PatternEncoder::new( + "[{d(%Y-%m-%d %H:%M:%S)}][{l}][{T}][{M}:{L}] {m}{n}", + ))) + .build( + directory.clone() + "/latest.log", + Box::new(CompoundPolicy::new( + Box::new(SizeTrigger::new(1 << 20)), + Box::new( + FixedWindowRoller::builder() + .base(1) + .build(&(directory.clone() + "/log-{}.gz"), 10) + .unwrap(), + ), + )), + ) + .unwrap(); + + let level = if settings.debug { + Debug + } else { + LevelFilter::from_str(&*settings.log.level).unwrap_or(Info) + }; + + let config = Config::builder() + .appender(Appender::builder().build("stdout", Box::new(stdout))) + .appender(Appender::builder().build("logfile", Box::new(logfile))) + .build( + Root::builder() + .appender("stdout") + .appender("logfile") + .build(level), + ) + .unwrap(); + + log4rs::init_config(config).unwrap() +} + +#[test] +fn test_logger() { + init_logger(&Settings::default()); + trace!("trace test"); + debug!("debug test"); + info!("info test"); + warn!("warn test"); + error!("error test"); +} + +fn main() { + let matches = clap_app!(drcom4scut => + (version: "0.1.0") + (author: "SeaLoong") + (about: "A 3rd-party Drcom client for SCUT.") + (@arg debug: --debug "Enable debug mode.") + (@arg config: -c --config +takes_value "(Optional) Path to config file. Some settings only can be set by config file.") + (@arg mac: -m --mac +takes_value "(Optional) Ethernet Device MAC address.") + (@arg ip: -i --ip +takes_value "(Optional) IP address of the selected Ethernet Device.") + (@arg username: -u --username +takes_value "Username to authorize.") + (@arg password: -p --password +takes_value "Password to authorize.") + (@arg dns: -d --dns +takes_value "(Optional) DNS server. If more than one, you can add the remain DNS servers to config file.") + (@arg host: -H --host +takes_value "(Optional) Host to connect UDP server. Default value is 's.scut.edu.cn'.") + (@arg hostname: -N --hostname +takes_value "(Optional) Default value is current computer host name.") + (@arg time: -t --time +takes_value "(Optional) Time to reconnect automatically after you are not allowed to access Internet. Default value is 7:00.") + (@arg noudp: --noudp "Disable UDP Process.") + ) + .get_matches(); + + let (mut settings, cfg) = settings::Settings::new(&matches).expect("Can't read config file."); + init_logger(&settings); + + settings.done(matches, cfg); + + info!("Start to run..."); + let device = + device::get_device(settings.mac, settings.ip).expect("Fail on getting ethernet device!"); + info!("Ethernet Device: {}", &device.interface.name); + info!("MAC address: {}", &device.mac); + info!("IP Address/Prefix: {}", &device.ip_net); + info!("Username: {}", settings.username); + info!("Password: {}", settings.password); + for dns in &settings.dns { + info!("DNS Server: {}", dns); + } + info!("Host: {}", settings.host); + info!("Hostname: {}", settings.hostname); + info!("Time to wake up: {}", settings.time); + info!("Reconnect Seconds: {}s", settings.reconnect); + info!( + "Heartbeat timeout of EAP: {}s", + settings.heartbeat.eap_timeout + ); + info!( + "Heartbeat timeout of UDP: {}s", + settings.heartbeat.udp_timeout + ); + info!("Retry Interval: {}ms", settings.retry_interval); + info!("Log Directory: {}", settings.log.directory); + info!("Log Level: {}", settings.log.level); + + let settings = Arc::new(settings); + let device = Arc::new(device); + + let (tx, rx) = crossbeam::unbounded::(); + + let settings1 = settings.clone(); + let device1 = device.clone(); + let eap_handle = thread::Builder::new() + .name("EAP-Process".to_owned()) + .spawn(move || { + let settings = settings1.clone(); + info!("Create EAP Process."); + let mut eap_process = eap::Process::new(settings.clone(), device1.clone(), tx); + info!("Start EAP Process."); + loop { + if eap_process.start() { + error!("Will try reconnect at the next {}.", settings.time); + if sleep_at(settings.time).is_some() { + continue; + } + error!( + "Can't create a valid DateTime! Will try reconnect in {} second(s).", + settings.reconnect + ); + } else { + error!( + "Failed at 802.1X Authorization! Will try reconnect in {} second(s).", + settings.reconnect + ); + } + thread::sleep(Duration::from_secs(settings.reconnect as u64)); + } + }); + if settings.no_udp { + info!("UDP Process is disabled.") + } else { + let settings2 = settings; + let device2 = device; + let udp_handle = thread::Builder::new() + .name("UDP-Process".to_owned()) + .spawn(move || { + let settings = settings2.clone(); + let (ip, dns) = match socket::resolve_dns(&settings) { + Some(r) => r, + None => { + error!("UDP: Can't resolve '{}'.", &settings.host); + return; + } + }; + let socket = Socket::new(match socket::socket_bind(ip) { + Some(socket) => socket, + None => { + error!("UDP: Can't create socket and connect to '{}'.", ip); + return; + } + }); + info!("Create UDP Process."); + let mut udp_process = udp::Process::new( + settings.clone(), + Arc::new(socket), + rx, + device2.mac, + device2.ip_net.ip(), + dns, + ); + let settings = settings; + info!("Start UDP Process."); + loop { + if udp_process.start() { + error!( + "Will try restart UDP heartbeat at the next {}.", + settings.time + ); + if sleep_at(settings.time).is_some() { + continue; + } + error!("Can't create a valid DateTime! Will continue UDP process."); + } + } + }); + udp_handle + .expect("Can't create UDP Process thread!") + .join() + .expect("Unexpected Error!"); + } + eap_handle + .expect("Can't create EAP Process thread!") + .join() + .expect("Unexpected Error!"); +} diff --git a/src/settings.rs b/src/settings.rs new file mode 100644 index 0000000..864b7f5 --- /dev/null +++ b/src/settings.rs @@ -0,0 +1,414 @@ +use std::collections::HashMap; + +use chrono::NaiveTime; +use config::{Config, FileFormat, Value}; +use log::error; +use pnet::datalink::MacAddr; +use std::cmp::max; +use std::net::{IpAddr, SocketAddr}; +use std::path::Path; +use std::str::FromStr; + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct Settings { + pub debug: bool, + pub no_udp: bool, + pub path: String, + pub mac: Option, + pub ip: Option, + pub username: String, + pub password: String, + pub dns: Vec, + pub host: String, + pub hostname: String, + pub time: NaiveTime, + pub reconnect: i32, + pub heartbeat: Heartbeat, + pub retry_interval: i32, + pub log: Log, + pub data: Data, +} + +impl Default for Settings { + fn default() -> Self { + Settings { + debug: false, + no_udp: false, + path: String::from("config.yml"), + mac: None, + ip: None, + username: String::new(), + password: String::new(), + dns: Vec::new(), + host: String::from("s.scut.edu.cn"), + hostname: String::new(), + time: NaiveTime::from_hms(7, 0, 0), + reconnect: 120, + heartbeat: Heartbeat::default(), + retry_interval: 5000, + log: Log::default(), + data: Data::default(), + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct Reconnect { + pub allowed_time: String, + pub seconds: i32, +} + +impl Default for Reconnect { + fn default() -> Self { + Reconnect { + allowed_time: String::from("7:00"), + seconds: 120, + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct Heartbeat { + pub eap_timeout: i32, + pub udp_timeout: i32, +} + +impl Default for Heartbeat { + fn default() -> Self { + Heartbeat { + eap_timeout: 60, + udp_timeout: 12, + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct Log { + pub directory: String, + pub level: String, +} + +impl Default for Log { + fn default() -> Self { + Log { + directory: String::from("./logs"), + level: String::from("INFO"), + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone, Default)] +pub struct Data { + pub response_identity: ResponseIdentity, + pub response_md5_challenge: ResponseMD5Challenge, + pub misc_info: MiscInfo, +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct ResponseIdentity { + pub unknown: Vec, +} + +impl Default for ResponseIdentity { + fn default() -> Self { + ResponseIdentity { + unknown: hex::decode("0044610000").unwrap(), + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct ResponseMD5Challenge { + pub unknown: Vec, +} + +impl Default for ResponseMD5Challenge { + fn default() -> Self { + ResponseMD5Challenge { + unknown: hex::decode("0044612a00").unwrap(), + } + } +} + +#[derive(Debug, Hash, PartialEq, Eq, Clone)] +pub struct MiscInfo { + pub unknown1: Vec, + pub crc32_param: Vec, + pub unknown2: Vec, + pub os_major: Vec, + pub os_minor: Vec, + pub os_build: Vec, + pub os_unknown: Vec, + pub version: Vec, + pub hash: String, +} + +impl Default for MiscInfo { + fn default() -> Self { + MiscInfo { + unknown1: hex::decode("0222002a").unwrap(), + crc32_param: hex::decode("c72f3101").unwrap(), + unknown2: hex::decode("94000000").unwrap(), + os_major: hex::decode("06000000").unwrap(), + os_minor: hex::decode("02000000").unwrap(), + os_build: hex::decode("f0230000").unwrap(), + os_unknown: hex::decode("02000000").unwrap(), + version: hex::decode("4472434f4d0096022a").unwrap(), + hash: String::from("4eb81fc048a5585b7dfe1783155241a328b103c6"), + } + } +} + +fn get_str_from_map(map: &HashMap, k: &str) -> Option { + map.get(k).and_then(|v| v.to_owned().into_str().ok()) +} + +fn get_int_from_map(map: &HashMap, k: &str) -> Option { + map.get(k).and_then(|v| v.to_owned().into_int().ok()) +} + +fn get_map_from_map(map: &HashMap, k: &str) -> Option> { + map.get(k).and_then(|v| v.to_owned().into_table().ok()) +} + +fn get_str(matches: &clap::ArgMatches, cfg: &config::Config, k: &str) -> Option { + let s = matches + .value_of(k) + .map(|s| s.to_string()) + .or_else(|| cfg.get_str(k).ok())?; + if s.trim().is_empty() { + None + } else { + Some(s) + } +} + +fn get_int(matches: &clap::ArgMatches, cfg: &config::Config, k: &str) -> Option { + matches + .value_of(k) + .and_then(|s| i64::from_str(s).ok()) + .or_else(|| cfg.get_int(k).ok()) +} + +impl Settings { + pub fn new(matches: &clap::ArgMatches) -> Result<(Settings, Config), config::ConfigError> { + let mut settings = Settings::default(); + settings.debug = matches.is_present("debug"); + settings.no_udp = matches.is_present("noudp"); + + let path = Path::new( + matches + .value_of("config") + .unwrap_or_else(|| settings.path.as_str()), + ); + if !path.is_file() && std::fs::write(path, DEFAULT_CONFIG_FILE).is_err() { + error!("Can't create default config file 'config.yml', use default config and command line args."); + } + + let mut cfg = Config::default(); + cfg.merge( + config::File::from(path) + .required(false) + .format(FileFormat::Yaml), + )?; + Ok((settings, cfg)) + } + pub fn done(&mut self, matches: clap::ArgMatches, cfg: Config) { + let cfg = &cfg; + + if let Some(s) = get_str(&matches, cfg, "mac") { + self.mac = Some(MacAddr::from_str(&s).expect("Can't parse MAC address!")); + } + + if let Some(s) = get_str(&matches, cfg, "ip") { + self.ip = Some(IpAddr::from_str(&s).expect("Can't parse IP Address!")); + } + + self.username = get_str(&matches, cfg, "username").expect("Username is REQUIRED!"); + self.password = get_str(&matches, cfg, "password").expect("Password is REQUIRED!"); + + if let Some(mut s) = matches.value_of("dns").map(|s| s.to_string()) { + if (s.contains(']') && !s.contains("]:")) || !s.contains(':') { + s += ":53"; + } + self.dns + .push(SocketAddr::from_str(&s).expect("Can't parse DNS server to socket address!")); + } + if let Ok(vs) = cfg.get_array("dns") { + for v in vs { + let mut s = v.to_owned().into_str().expect("Invalid DNS server!"); + if (s.contains(']') && !s.contains("]:")) || !s.contains(':') { + s += ":53"; + } + let x = + SocketAddr::from_str(&s).expect("Can't parse DNS server to socket address!"); + if !self.dns.contains(&x) { + self.dns.push(x); + } + } + } + + if let Some(s) = get_str(&matches, cfg, "host") { + self.host = s; + } + + if let Some(s) = get_str(&matches, cfg, "hostname") { + self.hostname = s; + } else { + self.hostname = hostname::get() + .expect("Can't get current computer host name.") + .into_string() + .expect("Can't parse host name to String."); + } + + if let Some(s) = get_str(&matches, cfg, "time") { + self.time = NaiveTime::parse_from_str(&s, "%H:%M") + .expect("Can't parse time String to NativeTime."); + } + + if let Some(x) = get_int(&matches, cfg, "reconnect") { + self.reconnect = x as i32; + } + + if let Ok(map) = cfg.get_table("heartbeat") { + if let Some(x) = get_int_from_map(&map, "eap_timeout") { + self.heartbeat.eap_timeout = x as i32; + } + if let Some(x) = get_int_from_map(&map, "udp_timeout") { + self.heartbeat.udp_timeout = x as i32; + } + } + + if let Some(x) = get_int(&matches, cfg, "retry_interval") { + self.retry_interval = max(x as i32, self.retry_interval); + } + + if let Ok(map) = cfg.get_table("log") { + if let Some(x) = get_str_from_map(&map, "directory") { + self.log.directory = x; + } + if let Some(x) = get_str_from_map(&map, "level") { + self.log.level = x; + } + } + + if let Ok(data) = cfg.get_table("data") { + if let Some(map) = get_map_from_map(&data, "response_identity") { + if let Some(s) = get_str_from_map(&map, "unknown") { + if let Ok(v) = hex::decode(s) { + self.data.response_identity.unknown = v; + } else { + error!("Invalid config: data.response_identity.unknown! Default value instead.") + } + } + } + if let Some(map) = get_map_from_map(&data, "response_md5_challenge") { + if let Some(s) = get_str_from_map(&map, "unknown") { + if let Ok(v) = hex::decode(s) { + self.data.response_md5_challenge.unknown = v; + } else { + error!("Invalid config: data.response_md5_challenge.unknown! Default value instead.") + } + } + } + if let Some(map) = get_map_from_map(&data, "misc_info") { + if let Some(s) = get_str_from_map(&map, "unknown1") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.unknown1 = v; + } else { + error!("Invalid config: data.misc_info.unknown1! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "crc32_param") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.crc32_param = v; + } else { + error!("Invalid config: data.misc_info.crc32_param! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "unknown2") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.unknown2 = v; + } else { + error!("Invalid config: data.misc_info.unknown2! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "os_major") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.os_major = v; + } else { + error!("Invalid config: data.misc_info.os_major! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "os_minor") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.os_minor = v; + } else { + error!("Invalid config: data.misc_info.os_minor! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "os_build") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.os_build = v; + } else { + error!("Invalid config: data.misc_info.os_build! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "os_unknown") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.os_unknown = v; + } else { + error!("Invalid config: data.misc_info.os_unknown! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "version") { + if let Ok(v) = hex::decode(s) { + self.data.misc_info.version = v; + } else { + error!("Invalid config: data.misc_info.version! Default value instead.") + } + } + if let Some(s) = get_str_from_map(&map, "hash") { + self.data.misc_info.hash = s; + } + } + } + } +} + +const DEFAULT_CONFIG_FILE: &str = "mac: +ip: +username: +password: +dns: + - 202.38.193.33 + - 222.201.130.30 + - 202.112.17.33 + - 222.201.130.33 +host: s.scut.edu.cn +hostname: +time: 7:00 +reconnect: 120 +heartbeat: + eap_timeout: 60 + udp_timeout: 12 +retry_interval: 5000 +log: + directory: ./logs + level: INFO +data: + response_identity: + unknown: + response_md5_challenge: + unknown: + misc_info: + unknown1: + crc32_param: + unknown2: + os_major: + os_minor: + os_build: + os_unknown: + version: + hash: +"; diff --git a/src/socket.rs b/src/socket.rs new file mode 100644 index 0000000..842b9f9 --- /dev/null +++ b/src/socket.rs @@ -0,0 +1,102 @@ +use crate::settings::Settings; +use log::{error, info}; +use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket}; +use std::sync::Arc; +use std::{io, ptr}; +use trust_dns_resolver::config::{NameServerConfig, Protocol, ResolverConfig, ResolverOpts}; +use trust_dns_resolver::Resolver; + +pub fn resolve_dns(settings: &Arc) -> Option<(IpAddr, SocketAddr)> { + info!("DNS resolving..."); + let mut r: Option<(IpAddr, SocketAddr)> = None; + for address in &settings.dns { + let mut config = ResolverConfig::new(); + config.add_name_server(NameServerConfig { + socket_addr: *address, + protocol: Protocol::Udp, + tls_dns_name: None, + }); + info!("Use DNS: {}:{}", address.ip(), address.port()); + let resolver = match Resolver::new(config, ResolverOpts::default()) { + Ok(r) => r, + Err(_) => { + error!("Failed to connect resolver."); + continue; + } + }; + let lookup = match resolver.lookup_ip(&settings.host[..]) { + Ok(r) => r, + Err(_) => { + error!("Failed to lookup."); + continue; + } + }; + for ip in lookup { + if r.is_some() { + break; + } + r = Some((ip, address.clone())); + } + if r.is_some() { + break; + } + error!("No addresses returned!"); + } + info!("Resolve result:"); + if r.is_some() { + info!("IP: {}", &r.unwrap().0); + } else { + error!("Resolve failed."); + } + r +} + +pub fn socket_bind(ip: IpAddr) -> Option { + let mut port = 36144; + let address = SocketAddr::new(ip, 61440); + loop { + match UdpSocket::bind(SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), port)) { + Ok(r) => { + if r.connect(address).is_ok() { + return Some(r); + } + } + Err(_) => { + if port == u16::MAX { + return None; + } + } + } + port += 1; + } +} + +pub struct Socket { + socket: UdpSocket, +} + +impl Socket { + pub fn new(socket: UdpSocket) -> Socket { + Socket { socket } + } + + pub fn send(&self, data: Vec) -> io::Result<()> { + let l = data.len(); + let mut n = 0; + while n < l { + n += self.socket.send(&data[n..l])?; + } + Ok(()) + } + + pub fn receive(&self) -> io::Result> { + let mut buffer = [0u8; 2048]; + let size = self.socket.recv(&mut buffer)?; + let mut v = Vec::with_capacity(size); + unsafe { + ptr::copy(buffer.as_ptr(), v.as_mut_ptr(), size); + v.set_len(size); + } + Ok(v) + } +} diff --git a/src/udp.rs b/src/udp.rs new file mode 100644 index 0000000..5bea304 --- /dev/null +++ b/src/udp.rs @@ -0,0 +1,541 @@ +use std::net::{IpAddr, Ipv4Addr, SocketAddr}; +use std::sync::atomic::{AtomicBool, AtomicI64, Ordering}; +use std::sync::{Arc, RwLock}; +use std::time::Duration; +use std::{ptr, thread}; +use thread::{JoinHandle, Thread}; + +use bytes::BytesMut; +use chrono::Local; +use crossbeam::channel::TryRecvError; +use crossbeam::{Receiver, Sender}; +use log::{debug, error, info}; +use pnet::datalink::MacAddr; + +use crate::constants; +use crate::settings::Settings; +use crate::socket::Socket; +use crate::udp::packet::{ + decrypt_info, Alive, HeaderType, HeartbeatType, MiscAlive, MiscHeartbeat1, MiscHeartbeat3, + MiscInfo, +}; +use crate::util::{random_vec, sleep, ChannelData}; + +mod packet; + +#[derive(Default, Debug, PartialEq, Eq, Hash, Clone)] +struct ProcessData { + counter: u8, + rnd: Vec, + crc_md5: Vec, + flux: Vec, + decrypted_from_misc_response_info: Vec, +} + +pub struct Process { + mac: MacAddr, + ip: IpAddr, + dns: SocketAddr, + settings: Arc, + socket: Arc, + rx: Receiver, + timeout: Arc, + stop: Arc, + sleep: Arc, + data: Arc>, + receive_channel: Option>>, + send_channel: Option, bool)>>, + send_ts: Arc, + cancel_resend: Arc, + receiver_handle: Option>>, + resender_handle: Option>>, + sender_handle: Option>>, + receiving_eap_handle: Option>>, + heartbeat_handle: Option>>, + thread: Arc, +} + +impl Process { + pub fn new( + settings: Arc, + socket: Arc, + rx: Receiver, + mac: MacAddr, + ip: IpAddr, + dns: SocketAddr, + ) -> Process { + Process { + timeout: Arc::new(AtomicBool::new(false)), + stop: Arc::new(AtomicBool::new(false)), + sleep: Arc::new(AtomicBool::new(false)), + data: Arc::new(RwLock::new(ProcessData::default())), + receive_channel: None, + send_channel: None, + send_ts: Arc::new(AtomicI64::new(0)), + cancel_resend: Arc::new(AtomicBool::new(true)), + settings, + socket, + rx, + mac, + ip, + dns, + resender_handle: None, + sender_handle: None, + receiving_eap_handle: None, + heartbeat_handle: None, + thread: Arc::new(thread::current()), + receiver_handle: None, + } + } + + fn start_receive_thread(&mut self) { + if let Some(handle) = &self.receiver_handle { + handle.thread().unpark(); + return; + } + let (tx, rx) = crossbeam::unbounded::>(); + self.receive_channel = Some(rx); + let stop = self.stop.clone(); + let interval = self.settings.retry_interval; + let socket = self.socket.clone(); + self.receiver_handle = Some(Arc::new( + thread::Builder::new() + .name("UDP-Receiver".to_owned()) + .spawn(move || { + let duration = Duration::from_millis(interval as u64); + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + match socket.receive() { + Ok(v) => { + tx.send(v) + .expect("Unexpected! Receive channel is disconnected!"); + } + Err(e) => { + error!("Receive error: {}", e); + thread::sleep(duration); + } + } + } + }) + .expect("Can't create UDP-Receiver thread."), + )); + } + + fn start_send_thread(&mut self) { + if let Some(handle) = &self.resender_handle { + handle.thread().unpark(); + if let Some(handle) = &self.sender_handle { + handle.thread().unpark(); + } + return; + } + let (tx, rx) = crossbeam::unbounded::<(Vec, bool)>(); + self.send_channel = Some(tx.clone()); + let stop = self.stop.clone(); + let interval = self.settings.retry_interval; + let send_ts = self.send_ts.clone(); + let cancel_resend = self.cancel_resend.clone(); + let resender_handle = Arc::new( + thread::Builder::new() + .name("UDP-ReSender".to_owned()) + .spawn(move || loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + while cancel_resend.load(Ordering::Acquire) { + thread::park(); + } + let wait_ts = send_ts.load(Ordering::Acquire) - Local::now().timestamp_millis() + + (interval as i64); + if wait_ts > 0 { + thread::sleep(Duration::from_millis(wait_ts as u64)); + } else if wait_ts > -interval as i64 { + debug!("Resending..."); + tx.send((Vec::new(), true)) + .expect("Unexpected! Send channel is disconnected!"); + cancel_resend.store(true, Ordering::Release); + } + }) + .expect("Can't create UDP-ReSender thread."), + ); + self.resender_handle = Some(resender_handle.clone()); + let stop = self.stop.clone(); + let socket = self.socket.clone(); + let interval = self.settings.retry_interval; + let send_ts = self.send_ts.clone(); + let cancel_resend = self.cancel_resend.clone(); + self.sender_handle = Some(Arc::new( + thread::Builder::new() + .name("UDP-Sender".to_owned()) + .spawn(move || { + let duration = Duration::from_millis(interval as u64); + let mut data = Vec::new(); + let mut resend = false; + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + match rx.recv() { + Ok((v, b)) => { + debug!("Sender received."); + if !v.is_empty() { + data = v; + resend = b; + } + } + Err(_) => panic!("Unexpected! Send channel is disconnected!"), + } + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + debug!("Sender is sending packet: {}", hex::encode(&data[..])); + match socket.send(data.clone()) { + Ok(_) => { + send_ts + .store(Local::now().timestamp_millis(), Ordering::Release); + cancel_resend.store(!resend, Ordering::Release); + if resend { + resender_handle.thread().unpark(); + } + break; + } + Err(e) => { + error!("Send error: {}", e); + thread::sleep(duration); + } + } + } + debug!("Sender sent packet."); + } + }) + .expect("Can't create UDP-Sender thread."), + )); + } + + fn receive(&self) -> Option> { + let channel = self.receive_channel.as_ref().unwrap(); + match channel.try_recv() { + Ok(v) => Some(v), + Err(TryRecvError::Disconnected) => { + panic!("Unexpected! Receive channel is disconnected!"); + } + Err(TryRecvError::Empty) => None, + } + } + + fn send(&self, data: Vec, resend: bool) { + let channel = self.send_channel.as_ref().unwrap(); + debug!("Will blocking send..."); + channel + .send((data, resend)) + .expect("Unexpected! Send channel is disconnected!"); + debug!("Sent."); + } + + fn start_receive_eap_thread(&mut self) { + if let Some(handle) = &self.receiving_eap_handle { + handle.thread().unpark(); + return; + } + info!("Start to receive message from EAP."); + let stop = self.stop.clone(); + let sleep = self.sleep.clone(); + let rx = self.rx.clone(); + let data = self.data.clone(); + let thread = self.thread.clone(); + self.receiving_eap_handle = Some(Arc::new( + thread::Builder::new() + .name("EAPtoUDP".to_owned()) + .spawn(move || { + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + match rx.recv() { + Ok(x) => match x.state { + constants::state::SUCCESS => { + info!("Receive SUCCESS from EAP."); + loop { + if let Ok(mut r) = data.try_write() { + r.crc_md5 = x.data; + info!("crc_md5(md5): {}", hex::encode(&r.crc_md5)); + break; + } + } + thread.unpark(); + } + constants::state::STOP => { + info!("Receive STOP from EAP."); + stop.store(true, Ordering::Relaxed); + } + constants::state::SLEEP => { + info!("Receive SLEEP from EAP."); + sleep.store(true, Ordering::Relaxed); + stop.store(true, Ordering::Relaxed); + } + _ => (), + }, + Err(_) => { + error!("Unexpected! EAPtoUDP channel is closed."); + break; + } + } + } + info!("Stop receiving message from EAP."); + }) + .expect("Can't create EAPtoUDP thread."), + )); + } + + fn cancel_resend(&self) { + self.cancel_resend.store(true, Ordering::Release); + } + + pub fn start(&mut self) -> bool { + self.thread = Arc::new(thread::current()); + self.stop.store(false, Ordering::Relaxed); + self.start_receive_thread(); + self.start_send_thread(); + self.start_receive_eap_thread(); + self.login_start(); + while !self.stop.load(Ordering::Relaxed) { + if self + .timeout + .compare_and_swap(true, false, Ordering::Acquire) + { + self.send_alive(); + } + let raw = match self.receive() { + Some(v) => v, + None => { + sleep(); + continue; + } + }; + debug!("Receive packet: {}", hex::encode(&raw[..])); + match HeaderType::from_vec(&raw[..]) { + HeaderType::Invalid => continue, + HeaderType::Unknown => error!("Unknown packet: {}", hex::encode(&raw[..])), + HeaderType::UnknownMisc => error!("Unknown Misc packet: {}", hex::encode(&raw[..])), + HeaderType::UnknownMessage => { + error!("Unknown Message packet: {}", hex::encode(&raw[..])) + } + HeaderType::MiscAlive => { + error!("Unexpected packet MiscAlive: {}", hex::encode(&raw[..])) + } + HeaderType::MiscResponseAlive => { + self.cancel_resend(); + info!("Receive MiscResponseAlive."); + loop { + if let Ok(mut r) = self.data.try_write() { + r.flux = Vec::from(&raw[8..12]); + break; + } + sleep(); + } + self.send_misc_info(); + } + HeaderType::MiscInfo => { + error!("Unexpected packet MiscInfo: {}", hex::encode(&raw[..])) + } + HeaderType::MiscResponseInfo => { + self.cancel_resend(); + info!("Receive MiscResponseInfo."); + self.on_response_info(raw); + } + HeaderType::MiscHeartbeat => { + self.cancel_resend(); + match HeartbeatType::from_vec(&raw[..]).0 { + 2 => { + info!("Receive MiscHeartbeat2."); + loop { + if let Ok(mut r) = self.data.try_write() { + r.flux = Vec::from(&raw[16..20]); + break; + } + sleep(); + } + self.send_misc_heartbeat_3(); + } + 4 => { + info!("Receive MiscHeartbeat4."); + self.timeout.store(false, Ordering::Relaxed); + info!("Heartbeat done."); + } + x => error!( + "Unexpected packet MiscHeartbeat Type{}: {}", + x, + hex::encode(&raw[..]) + ), + } + } + HeaderType::MiscResponseHeartbeatAlive => { + self.cancel_resend(); + info!("Receive MiscResponseHeartbeatAlive."); + self.send_misc_heartbeat_1(); + } + HeaderType::MessageServerInformation => { + let (s, _) = encoding_rs::GB18030.decode_without_bom_handling(&raw[4..]); + info!("Server Information: {}", s); + } + } + } + self.sleep.load(Ordering::Relaxed) + } + + #[inline] + fn login_start(&mut self) { + self.timeout.store(false, Ordering::Relaxed); + self.sleep.store(false, Ordering::Relaxed); + self.send_ts.store(0, Ordering::Relaxed); + info!("Waiting SUCCESS message from EAP."); + thread::park(); + self.send_misc_alive() + } + + fn on_response_info(&mut self, raw: Vec) { + let mut v = raw[16..32].to_vec(); + decrypt_info(&mut v); + loop { + if let Ok(mut r) = self.data.try_write() { + r.decrypted_from_misc_response_info = v; + break; + } + sleep(); + } + self.start_heartbeat_thread(); + } + + fn start_heartbeat_thread(&mut self) { + if let Some(handle) = &self.heartbeat_handle { + handle.thread().unpark(); + return; + } + let stop = self.stop.clone(); + let timeout = self.timeout.clone(); + let udp_timeout = self.settings.heartbeat.udp_timeout; + self.heartbeat_handle = Some(Arc::new(thread::Builder::new().name("UDP-Heartbeat".to_owned()).spawn(move || { + let duration = std::time::Duration::from_secs(udp_timeout as u64); + loop { + while stop.load(Ordering::Relaxed) { + thread::park(); + } + thread::sleep(duration); + if timeout.load(Ordering::Relaxed) { + error!("Heartbeat timeout. No Misc Heartbeat packet received for {}s, but ignored.", udp_timeout); + } + timeout.store(true, Ordering::Release); + } + }).expect("Can't create UDP-Heartbeat thread."))); + } + + fn send_misc_alive(&mut self) { + info!("Send MiscAlive."); + let data = &mut BytesMut::with_capacity(8); + MiscAlive::append_to(data); + self.send(data.to_vec(), true) + } + + fn send_misc_info(&mut self) { + info!("Send MiscInfo."); + let data = &mut BytesMut::with_capacity(244); + let settings = &self.settings; + let fixed = &settings.data.misc_info; + loop { + if let Ok(mut dt) = self.data.try_write() { + let crc = (MiscInfo { + mac: self.mac, + ip: self.ip, + unknown1: fixed.unknown1.clone(), + flux: dt.flux.clone(), + crc32_param: fixed.crc32_param.clone(), + username: settings.username.clone(), + hostname: settings.hostname.clone(), + dns1: self.dns.ip(), + dns2: IpAddr::V4(Ipv4Addr::UNSPECIFIED), + unknown2: fixed.unknown2.clone(), + os_major: fixed.os_major.clone(), + os_minor: fixed.os_minor.clone(), + os_build: fixed.os_build.clone(), + os_unknown: fixed.os_unknown.clone(), + version: fixed.version.clone(), + hash: fixed.hash.clone(), + }) + .append_to(data); + info!("calculate crc and apply to md5."); + unsafe { + ptr::copy(crc.to_le_bytes().as_ptr(), dt.crc_md5.as_mut_ptr(), 4); + } + break; + } + sleep(); + } + self.send(data.to_vec(), true) + } + + fn send_misc_heartbeat_1(&mut self) { + info!("Send MiscHeartbeat1."); + let data = &mut BytesMut::with_capacity(40); + loop { + if let Ok(mut dt) = self.data.try_write() { + dt.counter += 1; + dt.rnd = random_vec(2); + MiscHeartbeat1 { + counter: dt.counter, + rnd: dt.rnd.clone(), + flux: dt.flux.clone(), + } + .append_to(data); + break; + } + } + self.send(data.to_vec(), true) + } + + fn send_misc_heartbeat_3(&mut self) { + info!("Send MiscHeartbeat3."); + let data = &mut BytesMut::with_capacity(40); + loop { + if let Ok(mut dt) = self.data.try_write() { + dt.counter += 1; + MiscHeartbeat3 { + counter: dt.counter, + rnd: dt.rnd.clone(), + flux: dt.flux.clone(), + ip: self.ip, + } + .append_to(data); + break; + } + sleep(); + } + self.send(data.to_vec(), true) + } + + fn send_alive(&mut self) { + info!("Send Alive."); + let data = &mut BytesMut::with_capacity(40); + loop { + if let Ok(dt) = self.data.try_read() { + Alive { + crc_md5: dt.crc_md5.clone(), + decrypted_from_misc_response_info: dt.decrypted_from_misc_response_info.clone(), + } + .append_to(data); + break; + } + sleep(); + } + self.send(data.to_vec(), true) + } +} + +#[test] +fn test() { + let mut s = &mut String::new(); + dbg!(encoding_rs::GB18030.decode_with_bom_removal(hex::decode("d7d432303139c4ea39d4c23239c8d5c6f0a3acc8e7d0e8d4dacee5c9bdd0a3c7f8b0ecc0edcdf8c2e7d6d0d0c4cfe0b9d8d2b5cef1a3acc7ebd2c6b2bdd6c1cee5c9bdd0a3c7f831bac5c2a5caa6c9fab7fecef1d6d0d0c4d2bbc2a5b6abb2e0b4f3ccfc31d6c135bac5b4b0bfdaa1a3cfeacfb8d0c5cfa2c7ebbcfb687474703a2f2f7765622e736375742e6564752e636e2f323031392f303932352f633135323835613333353931312f706167652e68746d00").unwrap().as_slice())); + dbg!(encoding_rs::GB18030.decode_with_bom_removal(hex::decode("d6c2d0a3d4b0cdf8d3c3bba7a3accfd6d2d1b7a2b2bcc6bbb9fbb5e7c4d4d0c2b0e6c8cfd6a4bfcdbba7b6cba3acd6a7b3d6a1be6d61634f532031302e313520436174616c696e61a1bfa3acbfc9b5bd20687474703a2f2f3230322e33382e3139332e36352f20cfc2d4d8a1a3cad7b4ceb0b2d7b0c7b0a3acd0e8cfc8b6cfbfaad3d0cfdfc1acbdd3bbf2b0ceb3f6cdf8cfdfa3acc8bbbaf3b5c7c2bdcedecfdfcdf8c2e7a3acd4d9bdf8d0d0b0b2d7b0a1a3c8e7c4fad3d0c6e4cbfcd2c9cecaa3acbbb6d3add6").unwrap().as_slice())); +} diff --git a/src/udp/packet.rs b/src/udp/packet.rs new file mode 100644 index 0000000..fdc5739 --- /dev/null +++ b/src/udp/packet.rs @@ -0,0 +1,270 @@ +use std::net::IpAddr; + +use crate::util::{ip_to_vec, put_mac}; +use bytes::{BufMut, BytesMut}; +use pnet::datalink::MacAddr; +use std::cmp::min; +use std::ptr; + +pub enum HeaderType { + MiscAlive, + MiscResponseAlive, + MiscInfo, + MiscResponseInfo, + MiscHeartbeat, + MiscResponseHeartbeatAlive, + MessageServerInformation, + Invalid, + Unknown, + UnknownMisc, + UnknownMessage, +} + +impl HeaderType { + pub fn from_vec(data: &[u8]) -> HeaderType { + if data.len() < 5 { + return HeaderType::Invalid; + } + match data[0] { + 0x07 => match data[4] { + 0x01 => HeaderType::MiscAlive, + 0x02 => HeaderType::MiscResponseAlive, + 0x03 => HeaderType::MiscInfo, + 0x04 => HeaderType::MiscResponseInfo, + 0x0b => HeaderType::MiscHeartbeat, + 0x06 => HeaderType::MiscResponseHeartbeatAlive, + _ => HeaderType::UnknownMisc, + }, + 0x4d => match data[1] { + 0x38 => HeaderType::MessageServerInformation, + _ => HeaderType::UnknownMessage, + }, + _ => HeaderType::Unknown, + } + } +} + +pub struct HeartbeatType(pub u8); + +impl HeartbeatType { + pub fn from_vec(data: &[u8]) -> HeartbeatType { + HeartbeatType(if data.len() < 6 { 0 } else { data[5] }) + } +} + +pub struct MiscAlive {} + +impl MiscAlive { + #[inline] + pub fn append_to(data: &mut BytesMut) { + data.put(&[0x07, 0, 0x08, 0, 0x01, 0, 0, 0u8][..]); + } +} + +fn append_crc32(v: &mut Vec) -> u32 { + let mut s = 0u32; + let len = (v[2] >> 2) as usize; + v[28] = 126; + unsafe { + let p = v.as_ptr() as *const u32; + for i in 0..len { + s ^= p.add(i).read(); + } + s = s.to_le(); + s = ((s as u64) * 19680126) as u32; + } + unsafe { + ptr::copy(s.to_le_bytes().as_ptr(), v.as_mut_ptr().add(24), 4); + } + v[28] = 0; + s +} + +pub struct MiscInfo { + pub mac: MacAddr, + pub ip: IpAddr, + pub unknown1: Vec, + pub flux: Vec, + pub crc32_param: Vec, + pub username: String, + pub hostname: String, + pub dns1: IpAddr, + pub dns2: IpAddr, + pub unknown2: Vec, + pub os_major: Vec, + pub os_minor: Vec, + pub os_build: Vec, + pub os_unknown: Vec, + pub version: Vec, + pub hash: String, +} + +impl MiscInfo { + pub fn append_to(&self, bytes: &mut BytesMut) -> u32 { + let data = &mut BytesMut::with_capacity(244); + + data.put(&[0x07, 0x01, 0xf4, 0u8, 0x03][..]); // +5 + let username_len = min(self.username.len(), 25); + data.put_u8(username_len as u8); // +1 + + put_mac(data, &self.mac); // +6 + + data.put(&ip_to_vec(&self.ip)[..]); // +4 + + data.put(&self.unknown1[..]); // +4 + + data.put(&self.flux[..]); // +4 + + // crc32 + data.put(&self.crc32_param[..]); // +4 + + data.put(&[0u8].repeat(4)[..]); // +4 + + data.put(&self.username.as_bytes()[..username_len]); + + let hostname_len = min(self.hostname.len(), 44 - username_len); + data.put(&self.hostname.as_bytes()[..hostname_len]); + if username_len + hostname_len < 44 { + data.put(&[0u8].repeat(44 - username_len - hostname_len)[..]); + } + // +44 + + data.put(&ip_to_vec(&self.dns1)[..]); + data.put(&[0u8].repeat(4)[..]); + data.put(&ip_to_vec(&self.dns2)[..]); + data.put(&[0u8].repeat(8)[..]); + // +20 + data.put(&self.unknown2[..]); + + data.put(&self.os_major[..]); + data.put(&self.os_minor[..]); + data.put(&self.os_build[..]); + data.put(&self.os_unknown[..]); + // +16 + + let padding_len = 64 - self.version.len(); + data.put(&self.version[..]); + data.put(&[0u8].repeat(padding_len)[..]); + // +64 + let padding_len = 64 - self.hash.len(); + data.put(self.hash.as_bytes()); + data.put(&[0u8].repeat(padding_len)[..]); + // +64 + + let r = data.len() % 4; + if r > 0 { + data.put(&[0u8].repeat(4 - r)[..]); + } + + let v = &mut data.to_vec(); + let r = append_crc32(v); + bytes.put(&v[..]); + r + } +} + +pub struct MiscHeartbeat1 { + pub counter: u8, + pub rnd: Vec, + pub flux: Vec, +} + +impl MiscHeartbeat1 { + pub fn append_to(&self, data: &mut BytesMut) { + data.put(&[0x07, self.counter, 0x28, 0u8, 0x0b, 0x01, 0xdc, 0x02][..]); // +8 + data.put(&self.rnd[..]); // +2 + data.put(&[0u8].repeat(6)[..]); // +6 + data.put(&self.flux[..]); // +4 + data.put(&[0u8].repeat(20)[..]); // +20 + } +} + +fn append_checksum(v: &mut Vec) -> u32 { + let mut s = 0u16; + unsafe { + let p = v.as_ptr() as *const u16; + for i in 0..20 { + s ^= p.add(i).read(); + } + s = s.to_le(); + let s = (s as u32) * 711; + ptr::copy(s.to_le_bytes().as_ptr(), v.as_mut_ptr().add(24), 4); + s + } +} + +pub struct MiscHeartbeat3 { + pub counter: u8, + pub rnd: Vec, + pub flux: Vec, + pub ip: IpAddr, +} + +impl MiscHeartbeat3 { + pub fn append_to(&self, bytes: &mut BytesMut) -> u32 { + let data = &mut BytesMut::with_capacity(40); + data.put(&[0x07, self.counter, 0x28, 0u8, 0x0b, 0x03, 0xdc, 0x02][..]); // +8 + data.put(&self.rnd[..]); // +2 + data.put(&[0u8].repeat(6)[..]); // +6 + data.put(&self.flux[..]); // +4 + data.put(&[0u8].repeat(4)[..]); // +4 + + // checksum + data.put(&[0u8].repeat(4)[..]); // +4 + + data.put(&ip_to_vec(&self.ip)[..]); // +4 + data.put(&[0u8].repeat(8)[..]); // +8 + + let v = &mut data.to_vec(); + let r = append_checksum(v); + bytes.put(&v[..]); + r + } +} + +pub fn decrypt_info(v: &mut Vec) { + for i in 0..v.len() { + let x = i & 0x07; + v[i] = (((v[i] as u16) << x) + ((v[i] as u16) >> (8 - x))) as u8; + } +} + +pub struct Alive { + pub crc_md5: Vec, + pub decrypted_from_misc_response_info: Vec, +} + +impl Alive { + pub fn append_to(&self, data: &mut BytesMut) { + data.put_u8(0xff); // +1 + data.put(&self.crc_md5[..]); // +16 + data.put(&[0u8].repeat(3)[..]); // +3 + data.put(&self.decrypted_from_misc_response_info[..]); // +16 + data.put_u16_le(chrono::Local::now().timestamp() as u16); // +2 + } +} + +#[test] +fn test_crc32() { + let mut v = hex::decode("0701f400030cb025aa286db97dd9fee10222002a3bab4e04c72f3101000000003230313833363433313135345365614c6f6f6e67000000000000000000000000000000000000000000000000ca26c12100000000ca7011210000000000000000940000000600000002000000f0230000020000004472434f4d0096022a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034656238316663303438613535383562376466653137383331353532343161333238623130336336000000000000000000000000000000000000000000000000").unwrap(); + append_crc32(&mut v); + assert_eq!(hex::encode(v), "0701f400030cb025aa286db97dd9fee10222002a3bab4e044af8a726000000003230313833363433313135345365614c6f6f6e67000000000000000000000000000000000000000000000000ca26c12100000000ca7011210000000000000000940000000600000002000000f0230000020000004472434f4d0096022a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034656238316663303438613535383562376466653137383331353532343161333238623130336336000000000000000000000000000000000000000000000000"); + + let mut v = hex::decode("0701f400030cb025aa286db97dd9fee10222002a53513f04c72f3101000000003230313833363433313135345365614c6f6f6e67000000000000000000000000000000000000000000000000ca26c12100000000ca7011210000000000000000940000000600000002000000f0230000020000004472434f4d0096022a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034656238316663303438613535383562376466653137383331353532343161333238623130336336000000000000000000000000000000000000000000000000").unwrap(); + append_crc32(&mut v); + assert_eq!(hex::encode(v), "0701f400030cb025aa286db97dd9fee10222002a53513f049adf0351000000003230313833363433313135345365614c6f6f6e67000000000000000000000000000000000000000000000000ca26c12100000000ca7011210000000000000000940000000600000002000000f0230000020000004472434f4d0096022a0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034656238316663303438613535383562376466653137383331353532343161333238623130336336000000000000000000000000000000000000000000000000"); + + let mut v = hex::decode( + "075628000b03dc02b91900000000000067513f0400000000000000007dd9fee10000000000000000", + ) + .unwrap(); + append_checksum(&mut v); + assert_eq!( + hex::encode(v), + "075628000b03dc02b91900000000000067513f0400000000b6e062007dd9fee10000000000000000" + ); + + let mut v = hex::decode("4439d8edac314b07dd8c5f3bef0f04d8").unwrap(); + decrypt_info(&mut v); + assert_eq!(hex::encode(v), "4472636fca26d283dd197dd9fee1016c"); +} diff --git a/src/util.rs b/src/util.rs new file mode 100644 index 0000000..52e86bb --- /dev/null +++ b/src/util.rs @@ -0,0 +1,71 @@ +use bytes::{Buf, BufMut, Bytes, BytesMut}; +use chrono::{Local, NaiveTime}; +use pnet::datalink::MacAddr; +use rand::random; +use std::net::IpAddr; +use std::ops::Add; +use std::time::Duration; + +const MS: Duration = Duration::from_millis(1); + +#[inline] +pub fn sleep() { + std::thread::sleep(MS); + std::thread::yield_now(); +} + +#[inline] +pub fn ip_to_vec(ip: &IpAddr) -> Vec { + match ip { + IpAddr::V4(ip) => ip.octets().to_vec(), + IpAddr::V6(ip) => ip.octets().to_vec(), + } +} + +#[inline] +pub fn put_mac(data: &mut BytesMut, mac: &MacAddr) { + data.put_u8(mac.0); + data.put_u8(mac.1); + data.put_u8(mac.2); + data.put_u8(mac.3); + data.put_u8(mac.4); + data.put_u8(mac.5); +} + +#[inline] +pub fn get_mac(data: &mut Bytes) -> MacAddr { + let mut mac = MacAddr::zero(); + mac.0 = data.get_u8(); + mac.1 = data.get_u8(); + mac.2 = data.get_u8(); + mac.3 = data.get_u8(); + mac.4 = data.get_u8(); + mac.5 = data.get_u8(); + mac +} + +#[inline] +pub fn sleep_at(time: NaiveTime) -> Option<()> { + let mut dt = Local::today().and_time(time)?; + if dt < Local::now() { + dt = dt.add(chrono::Duration::from_std(Duration::new(86400, 0)).ok()?); + } + let duration = dt - Local::now(); + std::thread::sleep(duration.to_std().ok()?); + Some(()) +} + +#[inline] +pub fn random_vec(n: usize) -> Vec { + let mut v = Vec::with_capacity(n); + for _ in 0..n { + v.push(random::()); + } + v +} + +#[derive(Debug, Default, PartialEq, Eq, Hash, Clone)] +pub struct ChannelData { + pub state: u8, + pub data: Vec, +}