mirror of
https://github.com/EFForg/rayhunter.git
synced 2026-05-18 05:44:47 -07:00
merge main
This commit is contained in:
@@ -4,6 +4,8 @@ rustflags = ["-C", "target-feature=+crt-static"]
|
|||||||
|
|
||||||
[alias]
|
[alias]
|
||||||
test_pc = "test --target=x86_64-unknown-linux-gnu"
|
test_pc = "test --target=x86_64-unknown-linux-gnu"
|
||||||
|
build_pc = "build --target=x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
|
||||||
[target.armv7-unknown-linux-gnueabihf]
|
[target.armv7-unknown-linux-gnueabihf]
|
||||||
linker = "arm-linux-gnueabihf-gcc"
|
linker = "arm-linux-gnueabihf-gcc"
|
||||||
|
|||||||
22
.github/workflows/rust.yml
vendored
Normal file
22
.github/workflows/rust.yml
vendored
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
name: Rust
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "main" ]
|
||||||
|
|
||||||
|
env:
|
||||||
|
CARGO_TERM_COLOR: always
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Build
|
||||||
|
run: cargo build_pc --verbose
|
||||||
|
- name: Run tests
|
||||||
|
run: cargo test_pc --verbose
|
||||||
327
Cargo.lock
generated
327
Cargo.lock
generated
@@ -17,6 +17,18 @@ version = "1.0.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77c3a9648d43b9cd48db467b3f87fdd6e146bcc88ab0180006cef2179fe11d01"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
"zerocopy",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@@ -26,6 +38,12 @@ dependencies = [
|
|||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "allocator-api2"
|
||||||
|
version = "0.2.16"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "android-tzdata"
|
name = "android-tzdata"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
@@ -60,9 +78,9 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum"
|
name = "axum"
|
||||||
version = "0.7.3"
|
version = "0.7.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d09dbe0e490df5da9d69b36dca48a76635288a82f92eca90024883a56202026d"
|
checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum-core",
|
"axum-core",
|
||||||
@@ -94,9 +112,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-core"
|
name = "axum-core"
|
||||||
version = "0.4.2"
|
version = "0.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e87c8503f93e6d144ee5690907ba22db7ba79ab001a932ab99034f0fe836b3df"
|
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -136,9 +154,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.4.1"
|
version = "2.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07"
|
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitvec"
|
name = "bitvec"
|
||||||
@@ -196,16 +214,17 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "chrono"
|
name = "chrono"
|
||||||
version = "0.4.31"
|
version = "0.4.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
|
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
|
"serde",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"windows-targets 0.48.5",
|
"windows-targets 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -301,9 +320,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "env_logger"
|
name = "env_logger"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
|
checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"humantime",
|
"humantime",
|
||||||
"is-terminal",
|
"is-terminal",
|
||||||
@@ -343,6 +362,12 @@ dependencies = [
|
|||||||
"percent-encoding",
|
"percent-encoding",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fuchsia-cprng"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "funty"
|
name = "funty"
|
||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
@@ -364,6 +389,17 @@ version = "0.3.30"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-macro"
|
||||||
|
version = "0.3.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "futures-sink"
|
name = "futures-sink"
|
||||||
version = "0.3.30"
|
version = "0.3.30"
|
||||||
@@ -383,9 +419,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"futures-core",
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
"futures-task",
|
"futures-task",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"pin-utils",
|
"pin-utils",
|
||||||
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -396,9 +434,9 @@ checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h2"
|
name = "h2"
|
||||||
version = "0.4.0"
|
version = "0.4.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a"
|
checksum = "31d030e59af851932b72ceebadf4a2b5986dba4c3b99dd2493f8273a0f151943"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"fnv",
|
"fnv",
|
||||||
@@ -418,12 +456,16 @@ name = "hashbrown"
|
|||||||
version = "0.14.3"
|
version = "0.14.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
"allocator-api2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hermit-abi"
|
name = "hermit-abi"
|
||||||
version = "0.3.3"
|
version = "0.3.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
|
checksum = "5d3d0e0f38255e7fa3cf31335b3a56f05febd18025f4db5ef7a0cfb4f8da651f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "http"
|
name = "http"
|
||||||
@@ -498,12 +540,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper-util"
|
name = "hyper-util"
|
||||||
version = "0.1.2"
|
version = "0.1.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "bdea9aac0dbe5a9240d68cfd9501e2db94222c6dc06843e06640b9e07f0fdc67"
|
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"http",
|
"http",
|
||||||
"http-body",
|
"http-body",
|
||||||
@@ -511,7 +552,6 @@ dependencies = [
|
|||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"socket2",
|
"socket2",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -564,9 +604,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.1.0"
|
version = "2.2.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f"
|
checksum = "824b2ae422412366ba479e8111fd301f7b5faece8149317bb81925979a53f520"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"equivalent",
|
"equivalent",
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
@@ -591,18 +631,18 @@ checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.66"
|
version = "0.3.67"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca"
|
checksum = "9a1d36f1235bc969acba30b7f5990b864423a6068a10f7c90ae8f0112e3a59d1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.151"
|
version = "0.2.153"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4"
|
checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libusb1-sys"
|
name = "libusb1-sys"
|
||||||
@@ -618,9 +658,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "linux-raw-sys"
|
name = "linux-raw-sys"
|
||||||
version = "0.4.12"
|
version = "0.4.13"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c4cd1a83af159aa67994778be9070f0ae1bd732942279cabb14f86f986a21456"
|
checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
@@ -720,21 +760,6 @@ version = "1.19.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "orca"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"chrono",
|
|
||||||
"crc",
|
|
||||||
"deku",
|
|
||||||
"env_logger",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"pcap-file",
|
|
||||||
"thiserror",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "parking_lot"
|
name = "parking_lot"
|
||||||
version = "0.12.1"
|
version = "0.12.1"
|
||||||
@@ -777,18 +802,18 @@ checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project"
|
name = "pin-project"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422"
|
checksum = "0302c4a0442c456bd56f841aee5c3bfd17967563f6fadc9ceb9f9c23cf3807e0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"pin-project-internal",
|
"pin-project-internal",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-internal"
|
name = "pin-project-internal"
|
||||||
version = "1.1.3"
|
version = "1.1.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405"
|
checksum = "266c042b60c9c76b8d53061e52b2e0d1116abc57cefc8c5cd671619a56ac3690"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -809,9 +834,9 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pkg-config"
|
name = "pkg-config"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a"
|
checksum = "2900ede94e305130c13ddd391e0ab7cbaeb783945ae07a279c268cb05109c6cb"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro-crate"
|
name = "proc-macro-crate"
|
||||||
@@ -825,9 +850,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.75"
|
version = "1.0.78"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708"
|
checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
@@ -847,6 +872,79 @@ version = "0.7.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
|
||||||
|
dependencies = [
|
||||||
|
"fuchsia-cprng",
|
||||||
|
"libc",
|
||||||
|
"rand_core 0.3.1",
|
||||||
|
"rdrand",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.3.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.4.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.4.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayhunter"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"chrono",
|
||||||
|
"crc",
|
||||||
|
"deku",
|
||||||
|
"env_logger",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"pcap-file",
|
||||||
|
"thiserror",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rayhunter-daemon"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"chrono",
|
||||||
|
"env_logger",
|
||||||
|
"futures-core",
|
||||||
|
"futures-macro",
|
||||||
|
"include_dir",
|
||||||
|
"log",
|
||||||
|
"mime_guess",
|
||||||
|
"rayhunter",
|
||||||
|
"serde",
|
||||||
|
"tempdir",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"tokio-util",
|
||||||
|
"toml",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rdrand"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core 0.3.1",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@@ -858,9 +956,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.10.2"
|
version = "1.10.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "380b951a9c5e80ddfd6136919eef32310721aa4aacd4889a8d39124b026ab343"
|
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -870,9 +968,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-automata"
|
name = "regex-automata"
|
||||||
version = "0.4.3"
|
version = "0.4.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5f804c7828047e88b2d32e2d7fe5a105da8ee3264f01902f796c8e067dc2483f"
|
checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
@@ -885,6 +983,15 @@ version = "0.8.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "remove_dir_all"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||||
|
dependencies = [
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rootshell"
|
name = "rootshell"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@@ -907,11 +1014,11 @@ checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustix"
|
name = "rustix"
|
||||||
version = "0.38.28"
|
version = "0.38.30"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72e572a5e8ca657d7366229cdde4bd14c4eb5499a9573d4d366fe1b599daa316"
|
checksum = "322394588aaf33c24007e8bb3238ee3e4c5c09c084ab32bc73890b99ff326bca"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.2",
|
||||||
"errno",
|
"errno",
|
||||||
"libc",
|
"libc",
|
||||||
"linux-raw-sys",
|
"linux-raw-sys",
|
||||||
@@ -938,18 +1045,18 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.194"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0b114498256798c94a0689e1a15fec6005dee8ac1f41de56404b67afc2a4b773"
|
checksum = "870026e60fa08c69f064aa766c10f10b1d62db9ccd4d0abb206472bee0ce3b32"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_derive"
|
||||||
version = "1.0.194"
|
version = "1.0.196"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3385e45322e8f9931410f01b3031ec534c3947d0e94c18049af4d9f9907d4e0"
|
checksum = "33c85360c95e7d137454dc81d9a4ed2b8efd8fbe19cee57357b32b9771fccb67"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -958,9 +1065,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.111"
|
version = "1.0.113"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4"
|
checksum = "69801b70b1c3dac963ecb03a364ba0ceda9cf60c71cfe475e99864759c8b8a79"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"ryu",
|
"ryu",
|
||||||
@@ -1025,9 +1132,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.11.2"
|
version = "1.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970"
|
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "socket2"
|
name = "socket2"
|
||||||
@@ -1080,10 +1187,20 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termcolor"
|
name = "tempdir"
|
||||||
version = "1.4.0"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ff1bc3d3f05aff0403e8ac0d92ced918ec05b666a43f83297ccef5bea8a3d449"
|
checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
|
||||||
|
dependencies = [
|
||||||
|
"rand",
|
||||||
|
"remove_dir_all",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"winapi-util",
|
"winapi-util",
|
||||||
]
|
]
|
||||||
@@ -1147,6 +1264,8 @@ dependencies = [
|
|||||||
"bytes",
|
"bytes",
|
||||||
"futures-core",
|
"futures-core",
|
||||||
"futures-sink",
|
"futures-sink",
|
||||||
|
"futures-util",
|
||||||
|
"hashbrown",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tracing",
|
"tracing",
|
||||||
@@ -1154,14 +1273,14 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml"
|
name = "toml"
|
||||||
version = "0.8.8"
|
version = "0.8.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a1a195ec8c9da26928f773888e0742ca3ca1040c6cd859c919c9f59c1954ab35"
|
checksum = "c6a4b9e8023eb94392d3dca65d717c53abc5dad49c07cb65bb8fcd87115fa325"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_spanned",
|
"serde_spanned",
|
||||||
"toml_datetime",
|
"toml_datetime",
|
||||||
"toml_edit 0.21.0",
|
"toml_edit 0.21.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -1186,9 +1305,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "toml_edit"
|
name = "toml_edit"
|
||||||
version = "0.21.0"
|
version = "0.21.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d34d383cd00a163b4a5b85053df514d45bc330f6de7737edfe0a93311d1eaa03"
|
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"serde",
|
"serde",
|
||||||
@@ -1280,9 +1399,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen"
|
name = "wasm-bindgen"
|
||||||
version = "0.2.89"
|
version = "0.2.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e"
|
checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"wasm-bindgen-macro",
|
"wasm-bindgen-macro",
|
||||||
@@ -1290,9 +1409,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-backend"
|
name = "wasm-bindgen-backend"
|
||||||
version = "0.2.89"
|
version = "0.2.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826"
|
checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"log",
|
"log",
|
||||||
@@ -1305,9 +1424,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro"
|
name = "wasm-bindgen-macro"
|
||||||
version = "0.2.89"
|
version = "0.2.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2"
|
checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"quote",
|
"quote",
|
||||||
"wasm-bindgen-macro-support",
|
"wasm-bindgen-macro-support",
|
||||||
@@ -1315,9 +1434,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-macro-support"
|
name = "wasm-bindgen-macro-support"
|
||||||
version = "0.2.89"
|
version = "0.2.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283"
|
checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@@ -1328,27 +1447,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wasm-bindgen-shared"
|
name = "wasm-bindgen-shared"
|
||||||
version = "0.2.89"
|
version = "0.2.90"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f"
|
checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "wavehunter"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"axum",
|
|
||||||
"env_logger",
|
|
||||||
"futures-core",
|
|
||||||
"include_dir",
|
|
||||||
"log",
|
|
||||||
"mime_guess",
|
|
||||||
"orca",
|
|
||||||
"serde",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
"toml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi"
|
name = "winapi"
|
||||||
@@ -1524,9 +1625,9 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winnow"
|
name = "winnow"
|
||||||
version = "0.5.32"
|
version = "0.5.36"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "8434aeec7b290e8da5c3f0d628cb0eac6cabcb31d14bb74f779a08109a5914d6"
|
checksum = "818ce546a11a9986bc24f93d0cdf38a8a1a400f1473ea8c82e59f6e0ffab9249"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
@@ -1539,3 +1640,23 @@ checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"tap",
|
"tap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be"
|
||||||
|
dependencies = [
|
||||||
|
"zerocopy-derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zerocopy-derive"
|
||||||
|
version = "0.7.32"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.48",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
|
|
||||||
members = [
|
members = [
|
||||||
"orca",
|
"lib",
|
||||||
|
"bin",
|
||||||
"serial",
|
"serial",
|
||||||
"rootshell",
|
"rootshell",
|
||||||
"wavehunter",
|
|
||||||
]
|
]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -1,11 +1,12 @@
|
|||||||
# Wave Hunter
|
# Rayhunter
|
||||||
|
|
||||||
```
|
```
|
||||||
@@@ @@@ @@@ @@@@@@ @@@ @@@ @@@@@@@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@@@@@ @@@@@@@@ @@@@@@@
|
@@@@@@@ @@@@@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@ @@@@@@@ @@@@@@@@ @@@@@@@
|
||||||
@@! @@! @@! @@! @@@ @@! @@@ @@! @@! @@@ @@! @@@ @@!@!@@@ @!! @@! @@! @@@
|
@@! @@@ @@! @@@ @@! !@@ @@! @@@ @@! @@@ @@!@!@@@ @@! @@! @@! @@@
|
||||||
@!! !!@ @!@ @!@!@!@! @!@ !@! @!!!:! @!@!@!@! @!@ !@! @!@@!!@! @!! @!!!:! @!@!!@!
|
@!@!!@! @!@!@!@! !@!@! @!@!@!@! @!@ !@! @!@@!!@! @!! @!!!:! @!@!!@!
|
||||||
!: !!: !! !!: !!! !: .:! !!: !!: !!! !!: !!! !!: !!! !!: !!: !!: :!!
|
!!: :!! !!: !!! !!: !!: !!! !!: !!! !!: !!! !!: !!: !!: :!!
|
||||||
::.: ::: : : : :: : :: :: : : : :.:: : :: : : : :: :: : : :
|
: : : : : : .: : : : :.:: : :: : : : :: ::: : : :
|
||||||
|
|
||||||
|
|
||||||
_ _ _ _ _ _ _ _
|
_ _ _ _ _ _ _ _
|
||||||
)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_
|
)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_)`'-.,_
|
||||||
@@ -24,4 +25,35 @@ _ _ _ _ _ _ _ _
|
|||||||
\__;
|
\__;
|
||||||
```
|
```
|
||||||
|
|
||||||
diag helper binary for the Orbic mobile hotspot. Based on code from [QCSuper](https://github.com/P1sec/QCSuper)
|
Rayhunter is an IMSI Catcher Catcher for the Orbic mobile hotspot. Based on code from [QCSuper](https://github.com/P1sec/QCSuper)
|
||||||
|
|
||||||
|
**THIS CODE IS PROOF OF CONCEPT AND SHOULD NOT BE RELIED UPON IN HIGH RISK SITUATIONS**
|
||||||
|
|
||||||
|
Code is built and tested for the Orbic RC400L mobile hotspot, it may work on other orbics and other
|
||||||
|
linux/qualcom devices but this is the only one we have tested on. Buy the orbic [using bezos bucks](https://www.amazon.com/gp/product/B09CLS6Z7X/)
|
||||||
|
|
||||||
|
Root your device on windows using the instructions here: https://xdaforums.com/t/resetting-verizon-orbic-speed-rc400l-firmware-flash-kajeet.4334899/#post-87855183
|
||||||
|
(script to root on linux coming soon)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
on your linux laptop install rust the usual way and then install cross compiling dependences.
|
||||||
|
run `sudo apt install build-essential libc6-armhf-cross libc6-dev-armhf-cross gcc-arm-linux-gnueabihf`
|
||||||
|
|
||||||
|
set up cross compliing for rust:
|
||||||
|
```
|
||||||
|
rustup target add x86_64-unknown-linux-gnu
|
||||||
|
rustup target add armv7-unknown-linux-gnueabihf
|
||||||
|
```
|
||||||
|
|
||||||
|
Build for arm using `cargo build`
|
||||||
|
|
||||||
|
Run tests using `cargo test_pc`
|
||||||
|
|
||||||
|
|
||||||
|
Push the scripts in `scripts/` to /etc/init.d on device and make a directory called /data/rayhunter using `adb shell` (and sshell for your root shell if you followed the steps above)
|
||||||
|
|
||||||
|
you also need to copy `config.toml.example` to /data/rayhunter/config.toml
|
||||||
|
|
||||||
|
Then run ./make.sh this will build the binary and push it over adb. Restart your device or run `/etc/init.d/rayhunter_daemon start` on the device and you are good to go.
|
||||||
|
|||||||
@@ -1,20 +1,25 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "wavehunter"
|
name = "rayhunter-daemon"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
[[bin]]
|
||||||
|
name = "rayhunter"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
orca = { path = "../orca" }
|
rayhunter = { path = "../lib" }
|
||||||
toml = "0.8.8"
|
toml = "0.8.8"
|
||||||
serde = { version = "1.0.193", features = ["derive"] }
|
serde = { version = "1.0.193", features = ["derive"] }
|
||||||
tokio = { version = "1.35.1", features = ["full"] }
|
tokio = { version = "1.35.1", features = ["full"] }
|
||||||
axum = "0.7.2"
|
axum = "0.7.3"
|
||||||
futures-core = "0.3.30"
|
futures-core = "0.3.30"
|
||||||
thiserror = "1.0.52"
|
thiserror = "1.0.52"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
env_logger = "0.10.1"
|
env_logger = "0.10.1"
|
||||||
tokio-util = { version = "0.7.10", features = ["io"] }
|
tokio-util = { version = "0.7.10", features = ["rt"] }
|
||||||
|
futures-macro = "0.3.30"
|
||||||
include_dir = "0.7.3"
|
include_dir = "0.7.3"
|
||||||
mime_guess = "2.0.4"
|
mime_guess = "2.0.4"
|
||||||
|
tempdir = "0.3.7"
|
||||||
|
chrono = { version = "0.4.31", features = ["serde"] }
|
||||||
54
bin/src/config.rs
Normal file
54
bin/src/config.rs
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
use crate::error::RayhunterError;
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ConfigFile {
|
||||||
|
qmdl_store_path: Option<String>,
|
||||||
|
port: Option<u16>,
|
||||||
|
readonly_mode: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Config {
|
||||||
|
pub qmdl_store_path: String,
|
||||||
|
pub port: u16,
|
||||||
|
pub readonly_mode: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Self {
|
||||||
|
Config {
|
||||||
|
qmdl_store_path: "/data/rayhunter/qmdl".to_string(),
|
||||||
|
port: 8080,
|
||||||
|
readonly_mode: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_config<P>(path: P) -> Result<Config, RayhunterError> where P: AsRef<std::path::Path> {
|
||||||
|
let mut config = Config::default();
|
||||||
|
if let Ok(config_file) = std::fs::read_to_string(&path) {
|
||||||
|
let parsed_config: ConfigFile = toml::from_str(&config_file)
|
||||||
|
.map_err(RayhunterError::ConfigFileParsingError)?;
|
||||||
|
if let Some(path) = parsed_config.qmdl_store_path { config.qmdl_store_path = path }
|
||||||
|
if let Some(port) = parsed_config.port { config.port = port }
|
||||||
|
if let Some(readonly_mode) = parsed_config.readonly_mode { config.readonly_mode = readonly_mode }
|
||||||
|
}
|
||||||
|
Ok(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Args {
|
||||||
|
pub config_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_args() -> Args {
|
||||||
|
let args: Vec<String> = std::env::args().collect();
|
||||||
|
if args.len() != 2 {
|
||||||
|
println!("Usage: {} /path/to/config/file", args[0]);
|
||||||
|
std::process::exit(1);
|
||||||
|
}
|
||||||
|
Args {
|
||||||
|
config_path: args[1].clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
110
bin/src/diag.rs
Normal file
110
bin/src/diag.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use axum::extract::State;
|
||||||
|
use axum::http::StatusCode;
|
||||||
|
use rayhunter::diag_device::DiagDevice;
|
||||||
|
use rayhunter::diag_reader::DiagReader;
|
||||||
|
use tokio::sync::RwLock;
|
||||||
|
use tokio::sync::mpsc::{Receiver, self};
|
||||||
|
use rayhunter::qmdl::QmdlWriter;
|
||||||
|
use log::{debug, info};
|
||||||
|
use tokio::sync::mpsc::error::TryRecvError;
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio_util::task::TaskTracker;
|
||||||
|
|
||||||
|
use crate::error::RayhunterError;
|
||||||
|
use crate::qmdl_store::QmdlStore;
|
||||||
|
use crate::server::ServerState;
|
||||||
|
|
||||||
|
pub enum DiagDeviceCtrlMessage {
|
||||||
|
StopRecording,
|
||||||
|
StartRecording(QmdlWriter<std::fs::File>),
|
||||||
|
Exit,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run_diag_read_thread(task_tracker: &TaskTracker, mut dev: DiagDevice, mut qmdl_file_rx: Receiver<DiagDeviceCtrlMessage>, qmdl_store_lock: Arc<RwLock<QmdlStore>>) -> JoinHandle<Result<(), RayhunterError>> {
|
||||||
|
// mpsc channel for updating QmdlStore entry filesizes. First usize is the
|
||||||
|
// index, second is the size in bytes
|
||||||
|
let (tx, mut rx) = mpsc::channel::<(usize, usize)>(1);
|
||||||
|
|
||||||
|
// Spawn a thread to monitor the (usize, usize) channel for updates,
|
||||||
|
// triggering QmdlStore updates
|
||||||
|
let qmdl_store_lock_clone = qmdl_store_lock.clone();
|
||||||
|
task_tracker.spawn(async move {
|
||||||
|
while let Some((entry_idx, new_size)) = rx.recv().await {
|
||||||
|
let mut qmdl_store = qmdl_store_lock_clone.write().await;
|
||||||
|
qmdl_store.update_entry_size(entry_idx, new_size).await
|
||||||
|
.expect("failed to update qmdl file size");
|
||||||
|
}
|
||||||
|
info!("QMDL store size updater thread exiting...");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Spawn a thread to drive the DiagDevice reading loop. Since DiagDevice
|
||||||
|
// works via synchronous I/O, we have to spawn a "blocking" thread to avoid
|
||||||
|
// gumming up tokio's event loop.
|
||||||
|
task_tracker.spawn_blocking(move || {
|
||||||
|
loop {
|
||||||
|
// First check if we've gotten any control meesages
|
||||||
|
match qmdl_file_rx.try_recv() {
|
||||||
|
Ok(DiagDeviceCtrlMessage::StartRecording(qmdl_writer)) => {
|
||||||
|
dev.qmdl_writer = Some(qmdl_writer);
|
||||||
|
},
|
||||||
|
Ok(DiagDeviceCtrlMessage::StopRecording) => dev.qmdl_writer = None,
|
||||||
|
// Disconnected means all the Senders have been dropped, so it's
|
||||||
|
// time to go
|
||||||
|
Ok(DiagDeviceCtrlMessage::Exit) | Err(TryRecvError::Disconnected) => {
|
||||||
|
info!("Diag reader thread exiting...");
|
||||||
|
return Ok(())
|
||||||
|
},
|
||||||
|
// empty just means there's no message for us, so continue as normal
|
||||||
|
Err(TryRecvError::Empty) => {},
|
||||||
|
}
|
||||||
|
|
||||||
|
// remember the QmdlStore current entry index so we can update its size later
|
||||||
|
let qmdl_store_index = qmdl_store_lock.blocking_read().current_entry;
|
||||||
|
|
||||||
|
// TODO: once we're actually doing analysis, we'll wanna use the messages
|
||||||
|
// returned here. Until then, the DiagDevice has already written those messages
|
||||||
|
// to the QMDL file, so we can just ignore them.
|
||||||
|
debug!("reading response from diag device...");
|
||||||
|
let _messages = dev.read_response().map_err(RayhunterError::DiagReadError)?;
|
||||||
|
debug!("got diag response ({} messages)", _messages.len());
|
||||||
|
|
||||||
|
// keep track of how many bytes were written to the QMDL file so we can read
|
||||||
|
// a valid block of data from it in the HTTP server
|
||||||
|
if let Some(qmdl_writer) = dev.qmdl_writer.as_ref() {
|
||||||
|
debug!("total QMDL bytes written: {}, sending update...", qmdl_writer.total_written);
|
||||||
|
let index = qmdl_store_index.expect("DiagDevice had qmdl_writer, but QmdlStore didn't have current entry???");
|
||||||
|
tx.blocking_send((index, qmdl_writer.total_written)).unwrap();
|
||||||
|
debug!("done!");
|
||||||
|
} else {
|
||||||
|
debug!("no qmdl_writer set, continuing...");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn start_recording(State(state): State<Arc<ServerState>>) -> Result<(StatusCode, String), (StatusCode, String)> {
|
||||||
|
if state.readonly_mode {
|
||||||
|
return Err((StatusCode::FORBIDDEN, format!("server is in readonly mode")));
|
||||||
|
}
|
||||||
|
let mut qmdl_store = state.qmdl_store_lock.write().await;
|
||||||
|
let qmdl_file = qmdl_store.new_entry().await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't create new qmdl entry: {}", e)))?;
|
||||||
|
let qmdl_writer = QmdlWriter::new(qmdl_file.into_std().await);
|
||||||
|
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StartRecording(qmdl_writer)).await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
|
||||||
|
Ok((StatusCode::ACCEPTED, format!("ok")))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn stop_recording(State(state): State<Arc<ServerState>>) -> Result<(StatusCode, String), (StatusCode, String)> {
|
||||||
|
if state.readonly_mode {
|
||||||
|
return Err((StatusCode::FORBIDDEN, format!("server is in readonly mode")));
|
||||||
|
}
|
||||||
|
let mut qmdl_store = state.qmdl_store_lock.write().await;
|
||||||
|
qmdl_store.close_current_entry().await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't close current qmdl entry: {}", e)))?;
|
||||||
|
state.diag_device_ctrl_sender.send(DiagDeviceCtrlMessage::StopRecording).await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("couldn't send stop recording message: {}", e)))?;
|
||||||
|
Ok((StatusCode::ACCEPTED, format!("ok")))
|
||||||
|
}
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use orca::diag_device::DiagDeviceError;
|
use rayhunter::diag_device::DiagDeviceError;
|
||||||
|
|
||||||
|
use crate::qmdl_store::QmdlStoreError;
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
#[derive(Error, Debug)]
|
||||||
pub enum WavehunterError {
|
pub enum RayhunterError{
|
||||||
#[error("Missing config file: {0}")]
|
|
||||||
MissingConfigFile(String),
|
|
||||||
#[error("Config file parsing error: {0}")]
|
#[error("Config file parsing error: {0}")]
|
||||||
ConfigFileParsingError(#[from] toml::de::Error),
|
ConfigFileParsingError(#[from] toml::de::Error),
|
||||||
#[error("Diag intialization error: {0}")]
|
#[error("Diag intialization error: {0}")]
|
||||||
@@ -13,4 +13,8 @@ pub enum WavehunterError {
|
|||||||
DiagReadError(DiagDeviceError),
|
DiagReadError(DiagDeviceError),
|
||||||
#[error("Tokio error: {0}")]
|
#[error("Tokio error: {0}")]
|
||||||
TokioError(#[from] tokio::io::Error),
|
TokioError(#[from] tokio::io::Error),
|
||||||
|
#[error("QmdlStore error: {0}")]
|
||||||
|
QmdlStoreError(#[from] QmdlStoreError),
|
||||||
|
#[error("No QMDL store found at path {0}, but can't create a new one due to readonly mode")]
|
||||||
|
NoStoreReadonlyMode(String),
|
||||||
}
|
}
|
||||||
148
bin/src/main.rs
Normal file
148
bin/src/main.rs
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
mod config;
|
||||||
|
mod error;
|
||||||
|
mod pcap;
|
||||||
|
mod server;
|
||||||
|
mod stats;
|
||||||
|
mod qmdl_store;
|
||||||
|
mod diag;
|
||||||
|
|
||||||
|
use crate::config::{parse_config, parse_args};
|
||||||
|
use crate::diag::run_diag_read_thread;
|
||||||
|
use crate::qmdl_store::QmdlStore;
|
||||||
|
use crate::server::{ServerState, get_qmdl, serve_static};
|
||||||
|
use crate::pcap::get_pcap;
|
||||||
|
use crate::stats::get_system_stats;
|
||||||
|
use crate::error::RayhunterError;
|
||||||
|
|
||||||
|
use axum::response::Redirect;
|
||||||
|
use diag::{DiagDeviceCtrlMessage, start_recording, stop_recording};
|
||||||
|
use log::{info, error};
|
||||||
|
use rayhunter::diag_device::DiagDevice;
|
||||||
|
use axum::routing::{get, post};
|
||||||
|
use axum::Router;
|
||||||
|
use rayhunter::qmdl::QmdlWriter;
|
||||||
|
use stats::get_qmdl_manifest;
|
||||||
|
use tokio::sync::mpsc::{self, Sender};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tokio_util::task::TaskTracker;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
use tokio::sync::{RwLock, oneshot};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// Runs the axum server, taking all the elements needed to build up our
|
||||||
|
// ServerState and a oneshot Receiver that'll fire when it's time to shutdown
|
||||||
|
// (i.e. user hit ctrl+c)
|
||||||
|
async fn run_server(
|
||||||
|
task_tracker: &TaskTracker,
|
||||||
|
config: &config::Config,
|
||||||
|
qmdl_store_lock: Arc<RwLock<QmdlStore>>,
|
||||||
|
server_shutdown_rx: oneshot::Receiver<()>,
|
||||||
|
diag_device_sender: Sender<DiagDeviceCtrlMessage>
|
||||||
|
) -> JoinHandle<()> {
|
||||||
|
let state = Arc::new(ServerState {
|
||||||
|
qmdl_store_lock,
|
||||||
|
diag_device_ctrl_sender: diag_device_sender,
|
||||||
|
readonly_mode: config.readonly_mode,
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/api/pcap/*name", get(get_pcap))
|
||||||
|
.route("/api/qmdl/*name", get(get_qmdl))
|
||||||
|
.route("/api/system-stats", get(get_system_stats))
|
||||||
|
.route("/api/qmdl-manifest", get(get_qmdl_manifest))
|
||||||
|
.route("/api/start-recording", post(start_recording))
|
||||||
|
.route("/api/stop-recording", post(stop_recording))
|
||||||
|
.route("/", get(|| async { Redirect::permanent("/index.html") }))
|
||||||
|
.route("/*path", get(serve_static))
|
||||||
|
.with_state(state);
|
||||||
|
let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
|
||||||
|
let listener = TcpListener::bind(&addr).await.unwrap();
|
||||||
|
task_tracker.spawn(async move {
|
||||||
|
info!("The orca is hunting for stingrays...");
|
||||||
|
axum::serve(listener, app)
|
||||||
|
.with_graceful_shutdown(server_shutdown_signal(server_shutdown_rx))
|
||||||
|
.await.unwrap();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn server_shutdown_signal(server_shutdown_rx: oneshot::Receiver<()>) {
|
||||||
|
server_shutdown_rx.await.unwrap();
|
||||||
|
info!("Server received shutdown signal, exiting...");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads a QmdlStore if one exists, and if not, only create one if we're not in
|
||||||
|
// readonly mode.
|
||||||
|
async fn init_qmdl_store(config: &config::Config) -> Result<QmdlStore, RayhunterError> {
|
||||||
|
match (QmdlStore::exists(&config.qmdl_store_path).await?, config.readonly_mode) {
|
||||||
|
(true, _) => Ok(QmdlStore::load(&config.qmdl_store_path).await?),
|
||||||
|
(false, false) => Ok(QmdlStore::create(&config.qmdl_store_path).await?),
|
||||||
|
(false, true) => Err(RayhunterError::NoStoreReadonlyMode(config.qmdl_store_path.clone())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start a thread that'll track when user hits ctrl+c. When that happens,
|
||||||
|
// trigger various cleanup tasks, including sending signals to other threads to
|
||||||
|
// shutdown
|
||||||
|
fn run_ctrl_c_thread(
|
||||||
|
task_tracker: &TaskTracker,
|
||||||
|
diag_device_sender: Sender<DiagDeviceCtrlMessage>,
|
||||||
|
server_shutdown_tx: oneshot::Sender<()>,
|
||||||
|
qmdl_store_lock: Arc<RwLock<QmdlStore>>
|
||||||
|
) -> JoinHandle<Result<(), RayhunterError>> {
|
||||||
|
task_tracker.spawn(async move {
|
||||||
|
match tokio::signal::ctrl_c().await {
|
||||||
|
Ok(()) => {
|
||||||
|
let mut qmdl_store = qmdl_store_lock.write().await;
|
||||||
|
if qmdl_store.current_entry.is_some() {
|
||||||
|
info!("Closing current QMDL entry...");
|
||||||
|
qmdl_store.close_current_entry().await?;
|
||||||
|
info!("Done!");
|
||||||
|
}
|
||||||
|
|
||||||
|
server_shutdown_tx.send(())
|
||||||
|
.expect("couldn't send server shutdown signal");
|
||||||
|
diag_device_sender.send(DiagDeviceCtrlMessage::Exit).await
|
||||||
|
.expect("couldn't send Exit message to diag thread");
|
||||||
|
},
|
||||||
|
Err(err) => {
|
||||||
|
error!("Unable to listen for shutdown signal: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), RayhunterError> {
|
||||||
|
env_logger::init();
|
||||||
|
|
||||||
|
let args = parse_args();
|
||||||
|
let config = parse_config(&args.config_path)?;
|
||||||
|
|
||||||
|
// TaskTrackers give us an interface to spawn tokio threads, and then
|
||||||
|
// eventually await all of them ending
|
||||||
|
let task_tracker = TaskTracker::new();
|
||||||
|
|
||||||
|
let qmdl_store_lock = Arc::new(RwLock::new(init_qmdl_store(&config).await?));
|
||||||
|
let (tx, rx) = mpsc::channel::<DiagDeviceCtrlMessage>(1);
|
||||||
|
if !config.readonly_mode {
|
||||||
|
let qmdl_file = qmdl_store_lock.write().await.new_entry().await?;
|
||||||
|
let qmdl_writer = QmdlWriter::new(qmdl_file.into_std().await);
|
||||||
|
let mut dev = DiagDevice::new(Some(qmdl_writer))
|
||||||
|
.map_err(RayhunterError::DiagInitError)?;
|
||||||
|
dev.config_logs()
|
||||||
|
.map_err(RayhunterError::DiagInitError)?;
|
||||||
|
|
||||||
|
run_diag_read_thread(&task_tracker, dev, rx, qmdl_store_lock.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (server_shutdown_tx, server_shutdown_rx) = oneshot::channel::<()>();
|
||||||
|
run_ctrl_c_thread(&task_tracker, tx.clone(), server_shutdown_tx, qmdl_store_lock.clone());
|
||||||
|
run_server(&task_tracker, &config, qmdl_store_lock.clone(), server_shutdown_rx, tx).await;
|
||||||
|
|
||||||
|
task_tracker.close();
|
||||||
|
task_tracker.wait().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
use crate::ServerState;
|
use crate::ServerState;
|
||||||
|
|
||||||
use orca::gsmtap_parser::GsmtapParser;
|
use rayhunter::gsmtap_parser::GsmtapParser;
|
||||||
use orca::pcap::GsmtapPcapWriter;
|
use rayhunter::pcap::GsmtapPcapWriter;
|
||||||
use orca::qmdl::{QmdlReader, QmdlReaderError};
|
use rayhunter::qmdl::{QmdlReader, QmdlReaderError};
|
||||||
use orca::diag_reader::DiagReader;
|
use rayhunter::diag_reader::DiagReader;
|
||||||
use axum::body::Body;
|
use axum::body::Body;
|
||||||
use axum::http::header::CONTENT_TYPE;
|
use axum::http::header::CONTENT_TYPE;
|
||||||
use axum::extract::State;
|
use axum::extract::{State, Path};
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::{Response, IntoResponse};
|
use axum::response::{Response, IntoResponse};
|
||||||
use std::fs::File;
|
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -22,14 +21,19 @@ use tokio::sync::mpsc;
|
|||||||
// written so far. This is done by spawning a blocking thread (a tokio thread
|
// written so far. This is done by spawning a blocking thread (a tokio thread
|
||||||
// capable of handling blocking operations) which streams chunks of pcap data to
|
// capable of handling blocking operations) which streams chunks of pcap data to
|
||||||
// a channel that's piped to the client.
|
// a channel that's piped to the client.
|
||||||
pub async fn get_pcap(State(state): State<Arc<ServerState>>) -> Result<Response, (StatusCode, String)> {
|
pub async fn get_pcap(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
|
||||||
let qmdl_bytes_written = *state.qmdl_bytes_written.read().await;
|
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||||
if qmdl_bytes_written == 0 {
|
let entry = qmdl_store.entry_for_name(&qmdl_name)
|
||||||
|
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_name)))?;
|
||||||
|
if entry.size_bytes == 0 {
|
||||||
return Err((
|
return Err((
|
||||||
StatusCode::SERVICE_UNAVAILABLE,
|
StatusCode::SERVICE_UNAVAILABLE,
|
||||||
"QMDL file is empty, try again in a bit!".to_string()
|
"QMDL file is empty, try again in a bit!".to_string()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
let qmdl_file = qmdl_store.open_entry(&entry).await
|
||||||
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("{:?}", e)))?
|
||||||
|
.into_std().await;
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel(1);
|
let (tx, rx) = mpsc::channel(1);
|
||||||
let channel_reader = ChannelReader { rx };
|
let channel_reader = ChannelReader { rx };
|
||||||
@@ -37,8 +41,7 @@ pub async fn get_pcap(State(state): State<Arc<ServerState>>) -> Result<Response,
|
|||||||
tokio::task::spawn_blocking(move || {
|
tokio::task::spawn_blocking(move || {
|
||||||
// the QMDL reader should stop at the last successfully written data
|
// the QMDL reader should stop at the last successfully written data
|
||||||
// chunk (qmdl_bytes_written)
|
// chunk (qmdl_bytes_written)
|
||||||
let qmdl_file = File::open(&state.qmdl_path).unwrap();
|
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(entry.size_bytes));
|
||||||
let mut qmdl_reader = QmdlReader::new(qmdl_file, Some(qmdl_bytes_written));
|
|
||||||
|
|
||||||
let mut gsmtap_parser = GsmtapParser::new();
|
let mut gsmtap_parser = GsmtapParser::new();
|
||||||
let mut pcap_writer = GsmtapPcapWriter::new(channel_writer).unwrap();
|
let mut pcap_writer = GsmtapPcapWriter::new(channel_writer).unwrap();
|
||||||
213
bin/src/qmdl_store.rs
Normal file
213
bin/src/qmdl_store.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
use std::path::{PathBuf, Path};
|
||||||
|
use thiserror::Error;
|
||||||
|
use tokio::{fs::{self, File, try_exists}, io::AsyncWriteExt};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum QmdlStoreError {
|
||||||
|
#[error("Can't close an entry when there's no current entry")]
|
||||||
|
NoCurrentEntry,
|
||||||
|
#[error("Couldn't create file: {0}")]
|
||||||
|
CreateFileError(tokio::io::Error),
|
||||||
|
#[error("Couldn't read file: {0}")]
|
||||||
|
ReadFileError(tokio::io::Error),
|
||||||
|
#[error("Couldn't open directory at path: {0}")]
|
||||||
|
OpenDirError(tokio::io::Error),
|
||||||
|
#[error("Couldn't read manifest file: {0}")]
|
||||||
|
ReadManifestError(tokio::io::Error),
|
||||||
|
#[error("Couldn't write manifest file: {0}")]
|
||||||
|
WriteManifestError(tokio::io::Error),
|
||||||
|
#[error("Couldn't parse QMDL store manifest file: {0}")]
|
||||||
|
ParseManifestError(toml::de::Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct QmdlStore {
|
||||||
|
pub path: PathBuf,
|
||||||
|
pub manifest: Manifest,
|
||||||
|
pub current_entry: Option<usize>, // index into manifest
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)]
|
||||||
|
pub struct Manifest {
|
||||||
|
pub entries: Vec<ManifestEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Clone, PartialEq, Debug)]
|
||||||
|
pub struct ManifestEntry {
|
||||||
|
pub name: String,
|
||||||
|
pub start_time: DateTime<Local>,
|
||||||
|
pub end_time: Option<DateTime<Local>>,
|
||||||
|
pub size_bytes: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ManifestEntry {
|
||||||
|
fn new() -> Self {
|
||||||
|
let now = Local::now();
|
||||||
|
ManifestEntry {
|
||||||
|
name: format!("{}", now.timestamp()),
|
||||||
|
start_time: now,
|
||||||
|
end_time: None,
|
||||||
|
size_bytes: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QmdlStore {
|
||||||
|
// Returns whether a directory with a "manifest.toml" exists at the given
|
||||||
|
// path (though doesn't check if that manifest is valid)
|
||||||
|
pub async fn exists<P>(path: P) -> Result<bool, QmdlStoreError> where P: AsRef<Path> {
|
||||||
|
let manifest_path = path.as_ref().join("manifest.toml");
|
||||||
|
let dir_exists = try_exists(path).await.map_err(QmdlStoreError::OpenDirError)?;
|
||||||
|
let manifest_exists = try_exists(manifest_path).await.map_err(QmdlStoreError::ReadManifestError)?;
|
||||||
|
Ok(dir_exists && manifest_exists)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loads an existing QmdlStore at the given path. Errors if no store exists,
|
||||||
|
// or if it's malformed.
|
||||||
|
pub async fn load<P>(path: P) -> Result<Self, QmdlStoreError> where P: AsRef<Path> {
|
||||||
|
let path: PathBuf = path.as_ref().to_path_buf();
|
||||||
|
let manifest = QmdlStore::read_manifest(&path).await?;
|
||||||
|
Ok(QmdlStore {
|
||||||
|
path,
|
||||||
|
manifest,
|
||||||
|
current_entry: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Creates a new QmdlStore at the given path. This involves creating a dir
|
||||||
|
// and writing an empty manifest.
|
||||||
|
pub async fn create<P>(path: P) -> Result<Self, QmdlStoreError> where P: AsRef<Path> {
|
||||||
|
let manifest_path = path.as_ref().join("manifest.toml");
|
||||||
|
fs::create_dir_all(&path).await
|
||||||
|
.map_err(QmdlStoreError::OpenDirError)?;
|
||||||
|
let mut manifest_file = File::create(&manifest_path).await
|
||||||
|
.map_err(QmdlStoreError::WriteManifestError)?;
|
||||||
|
let empty_manifest = Manifest { entries: Vec::new() };
|
||||||
|
let empty_manifest_contents = toml::to_string_pretty(&empty_manifest)
|
||||||
|
.expect("failed to serialize manifest");
|
||||||
|
manifest_file.write_all(empty_manifest_contents.as_bytes()).await
|
||||||
|
.map_err(QmdlStoreError::WriteManifestError)?;
|
||||||
|
QmdlStore::load(path).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_manifest<P>(path: P) -> Result<Manifest, QmdlStoreError> where P: AsRef<Path> {
|
||||||
|
let manifest_path = path.as_ref().join("manifest.toml");
|
||||||
|
let file_contents = fs::read_to_string(&manifest_path).await
|
||||||
|
.map_err(QmdlStoreError::ReadManifestError)?;
|
||||||
|
toml::from_str(&file_contents)
|
||||||
|
.map_err(QmdlStoreError::ParseManifestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Closes the current entry (if needed), creates a new entry based on the
|
||||||
|
// current time, and updates the manifest
|
||||||
|
pub async fn new_entry(&mut self) -> Result<File, QmdlStoreError> {
|
||||||
|
// if we've already got an entry open, close it
|
||||||
|
if self.current_entry.is_some() {
|
||||||
|
self.close_current_entry().await?;
|
||||||
|
}
|
||||||
|
let new_entry = ManifestEntry::new();
|
||||||
|
let mut file_path = self.path.join(&new_entry.name);
|
||||||
|
file_path.set_extension("qmdl");
|
||||||
|
let file = File::options()
|
||||||
|
.create(true)
|
||||||
|
.write(true)
|
||||||
|
.open(&file_path).await
|
||||||
|
.map_err(QmdlStoreError::CreateFileError)?;
|
||||||
|
self.manifest.entries.push(new_entry);
|
||||||
|
self.current_entry = Some(self.manifest.entries.len() - 1);
|
||||||
|
self.write_manifest().await?;
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the corresponding QMDL file for a given entry
|
||||||
|
pub async fn open_entry(&self, entry: &ManifestEntry) -> Result<File, QmdlStoreError> {
|
||||||
|
let mut file_path = self.path.join(&entry.name);
|
||||||
|
file_path.set_extension("qmdl");
|
||||||
|
File::open(file_path).await
|
||||||
|
.map_err(QmdlStoreError::ReadFileError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the current entry's end_time, updates the manifest, and unsets the
|
||||||
|
// current entry
|
||||||
|
pub async fn close_current_entry(&mut self) -> Result<(), QmdlStoreError> {
|
||||||
|
let entry_index = self.current_entry.take()
|
||||||
|
.ok_or(QmdlStoreError::NoCurrentEntry)?;
|
||||||
|
self.manifest.entries[entry_index].end_time = Some(Local::now());
|
||||||
|
self.write_manifest().await
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the given entry's size, updating the manifest
|
||||||
|
pub async fn update_entry_size(&mut self, entry_index: usize, size_bytes: usize) -> Result<(), QmdlStoreError> {
|
||||||
|
self.manifest.entries[entry_index].size_bytes = size_bytes;
|
||||||
|
self.write_manifest().await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn write_manifest(&mut self) -> Result<(), QmdlStoreError> {
|
||||||
|
let mut manifest_file = File::options()
|
||||||
|
.write(true)
|
||||||
|
.open(self.path.join("manifest.toml")).await
|
||||||
|
.map_err(QmdlStoreError::WriteManifestError)?;
|
||||||
|
let manifest_contents = toml::to_string_pretty(&self.manifest)
|
||||||
|
.expect("failed to serialize manifest");
|
||||||
|
manifest_file.write_all(manifest_contents.as_bytes()).await
|
||||||
|
.map_err(QmdlStoreError::WriteManifestError)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finds an entry by filename
|
||||||
|
pub fn entry_for_name(&self, name: &str) -> Option<ManifestEntry> {
|
||||||
|
self.manifest.entries.iter()
|
||||||
|
.find(|entry| entry.name == name)
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use tempdir::TempDir;
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_load_from_empty_dir() {
|
||||||
|
let dir = TempDir::new("qmdl_store_test").unwrap();
|
||||||
|
assert!(!QmdlStore::exists(dir.path()).await.unwrap());
|
||||||
|
let _created_store = QmdlStore::create(dir.path()).await.unwrap();
|
||||||
|
assert!(QmdlStore::exists(dir.path()).await.unwrap());
|
||||||
|
let loaded_store = QmdlStore::load(dir.path()).await.unwrap();
|
||||||
|
assert_eq!(loaded_store.manifest.entries.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_creating_updating_and_closing_entries() {
|
||||||
|
let dir = TempDir::new("qmdl_store_test").unwrap();
|
||||||
|
let mut store = QmdlStore::create(dir.path()).await.unwrap();
|
||||||
|
let _ = store.new_entry().await.unwrap();
|
||||||
|
let entry_index = store.current_entry.unwrap();
|
||||||
|
assert_eq!(QmdlStore::read_manifest(dir.path()).await.unwrap(), store.manifest);
|
||||||
|
|
||||||
|
store.update_entry_size(entry_index, 1000).await.unwrap();
|
||||||
|
assert_eq!(store.manifest.entries[entry_index].size_bytes, 1000);
|
||||||
|
assert_eq!(QmdlStore::read_manifest(dir.path()).await.unwrap(), store.manifest);
|
||||||
|
|
||||||
|
assert!(store.manifest.entries[entry_index].end_time.is_none());
|
||||||
|
store.close_current_entry().await.unwrap();
|
||||||
|
let entry = store.entry_for_name(&store.manifest.entries[entry_index].name).unwrap();
|
||||||
|
assert!(entry.end_time.is_some());
|
||||||
|
assert_eq!(QmdlStore::read_manifest(dir.path()).await.unwrap(), store.manifest);
|
||||||
|
|
||||||
|
assert!(matches!(store.close_current_entry().await, Err(QmdlStoreError::NoCurrentEntry)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_repeated_new_entries() {
|
||||||
|
let dir = TempDir::new("qmdl_store_test").unwrap();
|
||||||
|
let mut store = QmdlStore::create(dir.path()).await.unwrap();
|
||||||
|
let _ = store.new_entry().await.unwrap();
|
||||||
|
let entry_index = store.current_entry.unwrap();
|
||||||
|
let _ = store.new_entry().await.unwrap();
|
||||||
|
let new_entry_index = store.current_entry.unwrap();
|
||||||
|
assert_ne!(entry_index, new_entry_index);
|
||||||
|
assert_eq!(store.manifest.entries.len(), 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,22 +5,28 @@ use axum::http::{StatusCode, HeaderValue};
|
|||||||
use axum::response::{Response, IntoResponse};
|
use axum::response::{Response, IntoResponse};
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use tokio::io::AsyncReadExt;
|
use tokio::io::AsyncReadExt;
|
||||||
|
use tokio::sync::mpsc::Sender;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::RwLock;
|
||||||
use tokio::fs::File as AsyncFile;
|
|
||||||
use tokio_util::io::ReaderStream;
|
use tokio_util::io::ReaderStream;
|
||||||
use include_dir::{include_dir, Dir};
|
use include_dir::{include_dir, Dir};
|
||||||
|
|
||||||
|
use crate::DiagDeviceCtrlMessage;
|
||||||
|
use crate::qmdl_store::QmdlStore;
|
||||||
|
|
||||||
pub struct ServerState {
|
pub struct ServerState {
|
||||||
pub qmdl_bytes_written: Arc<RwLock<usize>>,
|
pub qmdl_store_lock: Arc<RwLock<QmdlStore>>,
|
||||||
pub qmdl_path: String,
|
pub diag_device_ctrl_sender: Sender<DiagDeviceCtrlMessage>,
|
||||||
|
pub readonly_mode: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_qmdl(State(state): State<Arc<ServerState>>) -> Result<Response, (StatusCode, String)> {
|
pub async fn get_qmdl(State(state): State<Arc<ServerState>>, Path(qmdl_name): Path<String>) -> Result<Response, (StatusCode, String)> {
|
||||||
let qmdl_file = AsyncFile::open(&state.qmdl_path).await
|
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||||
|
let entry = qmdl_store.entry_for_name(&qmdl_name)
|
||||||
|
.ok_or((StatusCode::NOT_FOUND, format!("couldn't find qmdl file with name {}", qmdl_name)))?;
|
||||||
|
let qmdl_file = qmdl_store.open_entry(&entry).await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("error opening QMDL file: {}", e)))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, format!("error opening QMDL file: {}", e)))?;
|
||||||
let qmdl_bytes_written = *state.qmdl_bytes_written.read().await;
|
let limited_qmdl_file = qmdl_file.take(entry.size_bytes as u64);
|
||||||
let limited_qmdl_file = qmdl_file.take(qmdl_bytes_written as u64);
|
|
||||||
let qmdl_stream = ReaderStream::new(limited_qmdl_file);
|
let qmdl_stream = ReaderStream::new(limited_qmdl_file);
|
||||||
|
|
||||||
let headers = [(CONTENT_TYPE, "application/octet-stream")];
|
let headers = [(CONTENT_TYPE, "application/octet-stream")];
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::qmdl_store::ManifestEntry;
|
||||||
use crate::server::ServerState;
|
use crate::server::ServerState;
|
||||||
|
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::extract::State;
|
use axum::extract::State;
|
||||||
use axum::http::StatusCode;
|
use axum::http::StatusCode;
|
||||||
use axum::response::IntoResponse;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tokio::process::Command;
|
use tokio::process::Command;
|
||||||
@@ -97,7 +98,8 @@ fn humanize_kb(kb: usize) -> String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_system_stats(State(state): State<Arc<ServerState>>) -> Result<Json<SystemStats>, (StatusCode, String)> {
|
pub async fn get_system_stats(State(state): State<Arc<ServerState>>) -> Result<Json<SystemStats>, (StatusCode, String)> {
|
||||||
match SystemStats::new(&state.qmdl_path).await {
|
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||||
|
match SystemStats::new(qmdl_store.path.to_str().unwrap()).await {
|
||||||
Ok(stats) => Ok(Json(stats)),
|
Ok(stats) => Ok(Json(stats)),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("error getting system stats: {}", err);
|
error!("error getting system stats: {}", err);
|
||||||
@@ -109,13 +111,18 @@ pub async fn get_system_stats(State(state): State<Arc<ServerState>>) -> Result<J
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Serialize)]
|
||||||
pub struct DiagStats {
|
pub struct ManifestStats {
|
||||||
bytes_written: usize,
|
pub entries: Vec<ManifestEntry>,
|
||||||
|
pub current_entry: Option<ManifestEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_diag_stats(State(state): State<Arc<ServerState>>) -> impl IntoResponse {
|
pub async fn get_qmdl_manifest(State(state): State<Arc<ServerState>>) -> Result<Json<ManifestStats>, (StatusCode, String)> {
|
||||||
Json(DiagStats {
|
let qmdl_store = state.qmdl_store_lock.read().await;
|
||||||
bytes_written: *state.qmdl_bytes_written.read().await,
|
let mut entries = qmdl_store.manifest.entries.clone();
|
||||||
})
|
let current_entry = qmdl_store.current_entry.map(|index| entries.remove(index));
|
||||||
|
Ok(Json(ManifestStats {
|
||||||
|
entries,
|
||||||
|
current_entry,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
40
bin/static/css/style.css
Normal file
40
bin/static/css/style.css
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
td,
|
||||||
|
th {
|
||||||
|
border: 1px solid rgb(190, 190, 190);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
th[scope='col'] {
|
||||||
|
background-color: #696969;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
th[scope='row'] {
|
||||||
|
background-color: #d7d9f2;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr.current {
|
||||||
|
background-color: #fe537b;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding: 10px;
|
||||||
|
caption-side: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border: 2px solid rgb(200, 200, 200);
|
||||||
|
letter-spacing: 1px;
|
||||||
|
font-family: sans-serif;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
38
bin/static/index.html
Normal file
38
bin/static/index.html
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>rayhunter</title>
|
||||||
|
<link rel="stylesheet" type="text/css" href="css/style.css">
|
||||||
|
<script src="js/main.js"></script>
|
||||||
|
<script>
|
||||||
|
async function repeatedlyPopulate() {
|
||||||
|
await populateDivs();
|
||||||
|
setTimeout(repeatedlyPopulate, 1000);
|
||||||
|
}
|
||||||
|
window.onload = function() {
|
||||||
|
repeatedlyPopulate();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div>
|
||||||
|
<button onclick="startRecording()">Start Recording</button>
|
||||||
|
<button onclick="stopRecording()">Stop Recording</button>
|
||||||
|
</div>
|
||||||
|
<table id="qmdl-manifest-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col">Name</th>
|
||||||
|
<th scope="col">Date Started</th>
|
||||||
|
<th scope="col">Date Stopped</th>
|
||||||
|
<th scope="col">Size (bytes)</th>
|
||||||
|
<th scope="col">PCAP</th>
|
||||||
|
<th scope="col">QMDL</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
</table>
|
||||||
|
<div>
|
||||||
|
<h3>System stats</h3>
|
||||||
|
<pre id="system-stats">Loading...</pre>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
90
bin/static/js/main.js
Normal file
90
bin/static/js/main.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
async function populateDivs() {
|
||||||
|
const systemStats = await getSystemStats();
|
||||||
|
const systemStatsDiv = document.getElementById('system-stats');
|
||||||
|
systemStatsDiv.innerHTML = JSON.stringify(systemStats, null, 2);
|
||||||
|
|
||||||
|
const qmdlManifest = await getQmdlManifest();
|
||||||
|
updateQmdlManifestTable(qmdlManifest);
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateQmdlManifestTable(manifest) {
|
||||||
|
const table = document.getElementById('qmdl-manifest-table');
|
||||||
|
const numRows = table.rows.length;
|
||||||
|
for (let i=1; i<numRows; i++) {
|
||||||
|
table.deleteRow(1);
|
||||||
|
}
|
||||||
|
if (manifest.current_entry) {
|
||||||
|
const row = createEntryRow(manifest.current_entry);
|
||||||
|
row.classList.add('current');
|
||||||
|
table.appendChild(row)
|
||||||
|
}
|
||||||
|
for (let entry of manifest.entries) {
|
||||||
|
table.appendChild(createEntryRow(entry));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEntryRow(entry) {
|
||||||
|
const row = document.createElement('tr');
|
||||||
|
const name = document.createElement('th');
|
||||||
|
name.scope = 'row';
|
||||||
|
name.innerText = entry.name;
|
||||||
|
row.appendChild(name);
|
||||||
|
for (const key of ['start_time', 'end_time', 'size_bytes']) {
|
||||||
|
const td = document.createElement('td');
|
||||||
|
td.innerText = entry[key];
|
||||||
|
row.appendChild(td);
|
||||||
|
}
|
||||||
|
const pcap_td = document.createElement('td');
|
||||||
|
const pcap_link = document.createElement('a');
|
||||||
|
pcap_link.href = `/api/pcap/${entry.name}`;
|
||||||
|
pcap_link.innerText = 'pcap';
|
||||||
|
pcap_td.appendChild(pcap_link);
|
||||||
|
row.appendChild(pcap_td);
|
||||||
|
const qmdl_td = document.createElement('td');
|
||||||
|
const qmdl_link = document.createElement('a');
|
||||||
|
qmdl_link.href = `/api/qmdl/${entry.name}`;
|
||||||
|
qmdl_link.innerText = 'qmdl';
|
||||||
|
qmdl_td.appendChild(qmdl_link);
|
||||||
|
row.appendChild(qmdl_td);
|
||||||
|
return row;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getSystemStats() {
|
||||||
|
return JSON.parse(await req('GET', '/api/system-stats'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getQmdlManifest() {
|
||||||
|
const manifest = JSON.parse(await req('GET', '/api/qmdl-manifest'));
|
||||||
|
if (manifest.current_entry) {
|
||||||
|
manifest.current_entry.start_time = new Date(manifest.current_entry.start_time);
|
||||||
|
}
|
||||||
|
for (entry of manifest.entries) {
|
||||||
|
entry.start_time = new Date(entry.start_time);
|
||||||
|
entry.end_time = new Date(entry.end_time);
|
||||||
|
}
|
||||||
|
// sort them in reverse chronological order
|
||||||
|
manifest.entries.reverse();
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startRecording() {
|
||||||
|
await req('POST', '/api/start-recording');
|
||||||
|
populateDivs();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function stopRecording() {
|
||||||
|
await req('POST', '/api/stop-recording');
|
||||||
|
populateDivs();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function req(method, url) {
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: method,
|
||||||
|
});
|
||||||
|
const body = await response.text();
|
||||||
|
if (response.status >= 200 && response.status < 300) {
|
||||||
|
return body;
|
||||||
|
} else {
|
||||||
|
throw new Error(body);
|
||||||
|
}
|
||||||
|
}
|
||||||
4
config.toml.example
Normal file
4
config.toml.example
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# cat config.toml
|
||||||
|
qmdl_store_path = "/data/rayhunter/qmdl"
|
||||||
|
port = 8080
|
||||||
|
readonly_mode = false
|
||||||
20
install.sh
Executable file
20
install.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
cargo build --release
|
||||||
|
# Force a switch into the debug mode to enable ADB
|
||||||
|
target/x86_64-unknown-linux-gnu/release/serial AT
|
||||||
|
echo -n "device rooted, waiting for reboot"
|
||||||
|
until adb shell true
|
||||||
|
do
|
||||||
|
echo -n .
|
||||||
|
sleep 1
|
||||||
|
done
|
||||||
|
echo
|
||||||
|
echo "it's alive!"
|
||||||
|
adb push target/armv7-unknown-linux-gnueabihf/release/rootshell /tmp/
|
||||||
|
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=mv /tmp/rootshell /bin/rootshell"
|
||||||
|
sleep 1
|
||||||
|
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chown root /bin/rootshell"
|
||||||
|
sleep 1
|
||||||
|
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chmod 4755 /bin/rootshell"
|
||||||
|
echo "we have root!"
|
||||||
|
adb shell id
|
||||||
|
adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter /data/rayhunter/rayhunter
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "orca"
|
name = "rayhunter"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
description = "Orbic Realtime Cellular Analysis"
|
description = "Realtime cellular data decoding and analysis for IMSI catcher detection"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytes = "1.5.0"
|
bytes = "1.5.0"
|
||||||
@@ -71,7 +71,7 @@ const DIAG_IOCTL_SWITCH_LOGGING: u64 = 7;
|
|||||||
|
|
||||||
pub struct DiagDevice {
|
pub struct DiagDevice {
|
||||||
file: File,
|
file: File,
|
||||||
pub qmdl_writer: QmdlWriter<File>,
|
pub qmdl_writer: Option<QmdlWriter<File>>,
|
||||||
fully_initialized: bool,
|
fully_initialized: bool,
|
||||||
read_buf: Vec<u8>,
|
read_buf: Vec<u8>,
|
||||||
use_mdm: i32,
|
use_mdm: i32,
|
||||||
@@ -92,16 +92,18 @@ impl DiagReader for DiagDevice {
|
|||||||
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
|
warn!("warning: {} leftover bytes when parsing MessagesContainer", leftover_bytes.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
if self.fully_initialized {
|
if let Some(qmdl_writer) = self.qmdl_writer.as_mut() {
|
||||||
self.qmdl_writer.write_container(&container)
|
if self.fully_initialized {
|
||||||
.map_err(DiagDeviceError::QmdlFileWriteError)?;
|
qmdl_writer.write_container(&container)
|
||||||
|
.map_err(DiagDeviceError::QmdlFileWriteError)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(container)
|
Ok(container)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DiagDevice {
|
impl DiagDevice {
|
||||||
pub fn new<P>(qmdl_path: P) -> DiagResult<Self> where P: AsRef<std::path::Path> {
|
pub fn new(qmdl_writer: Option<QmdlWriter<File>>) -> DiagResult<Self> {
|
||||||
let diag_file = std::fs::File::options()
|
let diag_file = std::fs::File::options()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(true)
|
.write(true)
|
||||||
@@ -109,21 +111,6 @@ impl DiagDevice {
|
|||||||
.map_err(DiagDeviceError::OpenDiagDeviceError)?;
|
.map_err(DiagDeviceError::OpenDiagDeviceError)?;
|
||||||
let fd = diag_file.as_raw_fd();
|
let fd = diag_file.as_raw_fd();
|
||||||
|
|
||||||
let qmdl_file = File::options()
|
|
||||||
.create(true)
|
|
||||||
.append(true)
|
|
||||||
.open(&qmdl_path)
|
|
||||||
.map_err(DiagDeviceError::OpenQmdlFileError)?;
|
|
||||||
let qmdl_metadata = qmdl_file.metadata().map_err(DiagDeviceError::OpenQmdlFileError)?;
|
|
||||||
if qmdl_metadata.len() != 0 {
|
|
||||||
info!(
|
|
||||||
"QMDL file {} already contains data ({} bytes), appending to it",
|
|
||||||
qmdl_path.as_ref().display(),
|
|
||||||
qmdl_metadata.len()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let qmdl_writer = QmdlWriter::new_with_existing_size(qmdl_file, qmdl_metadata.len() as usize);
|
|
||||||
|
|
||||||
enable_frame_readwrite(fd, MEMORY_DEVICE_MODE)?;
|
enable_frame_readwrite(fd, MEMORY_DEVICE_MODE)?;
|
||||||
let use_mdm = determine_use_mdm(fd)?;
|
let use_mdm = determine_use_mdm(fd)?;
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
use orca::diag::{
|
use rayhunter::diag::{
|
||||||
Message,
|
Message,
|
||||||
LogBody,
|
LogBody,
|
||||||
LteRrcOtaPacket,
|
LteRrcOtaPacket,
|
||||||
Timestamp,
|
Timestamp,
|
||||||
};
|
};
|
||||||
use orca::gsmtap_parser::GsmtapParser;
|
use rayhunter::gsmtap_parser::GsmtapParser;
|
||||||
use deku::prelude::*;
|
use deku::prelude::*;
|
||||||
|
|
||||||
// Tests here are based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/tests/test_diagltelogparser.py
|
// Tests here are based on https://github.com/fgsect/scat/blob/97442580e628de414c9f7c2a185f4e28d0ee7523/tests/test_diagltelogparser.py
|
||||||
10
make.sh
10
make.sh
@@ -1,10 +1,2 @@
|
|||||||
cargo build --release
|
cargo build --release
|
||||||
# Force a switch into the debug mode to enable ADB
|
adb push target/armv7-unknown-linux-gnueabihf/release/rayhunter /data/rayhunter/rayhunter
|
||||||
target/x86_64-unknown-linux-gnu/release/serial AT
|
|
||||||
adb push target/armv7-unknown-linux-gnueabihf/release/rootshell /tmp/
|
|
||||||
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=mv /tmp/rootshell /bin/rootshell"
|
|
||||||
sleep 1
|
|
||||||
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chown root /bin/rootshell"
|
|
||||||
sleep 1
|
|
||||||
target/x86_64-unknown-linux-gnu/release/serial "AT+SYSCMD=chmod 4755 /bin/rootshell"
|
|
||||||
adb push target/armv7-unknown-linux-gnueabihf/release/wavehunter /data/wavehunter/wavehunter
|
|
||||||
|
|||||||
@@ -41,9 +41,9 @@ case "$1" in
|
|||||||
/etc/init.d/start_atfwd_daemon start
|
/etc/init.d/start_atfwd_daemon start
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f /etc/init.d/wavehunter_daemon ]
|
if [ -f /etc/init.d/rayhunter_daemon ]
|
||||||
then
|
then
|
||||||
/etc/init.d/wavehunter_daemon start
|
/etc/init.d/rayhunter_daemon start
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f /etc/init.d/start_stop_qti_ppp_le ]
|
if [ -f /etc/init.d/start_stop_qti_ppp_le ]
|
||||||
@@ -72,9 +72,9 @@ case "$1" in
|
|||||||
/etc/init.d/start_loc_launcher stop
|
/etc/init.d/start_loc_launcher stop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f /etc/init.d/wavehunter_daemon ]
|
if [ -f /etc/init.d/rayhunter_daemon ]
|
||||||
then
|
then
|
||||||
/etc/init.d/wavehunter_daemon stop
|
/etc/init.d/rayhunter_daemon stop
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ -f /etc/init.d/init_qcom_audio ]
|
if [ -f /etc/init.d/init_qcom_audio ]
|
||||||
|
|||||||
27
scripts/rayhunter_daemon
Normal file
27
scripts/rayhunter_daemon
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
#! /bin/sshell
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start)
|
||||||
|
echo -n "Starting rayhunter: "
|
||||||
|
start-stop-daemon -S -b --make-pidfile --pidfile /tmp/rayhunter.pid \
|
||||||
|
--startas /bin/bash -- -c "exec /data/rayhunter/rayhunter /data/rayhunter/config.toml > /data/rayhunter/rayhunter.log 2>&1"
|
||||||
|
echo "done"
|
||||||
|
;;
|
||||||
|
stop)
|
||||||
|
echo -n "Stopping rayhunter: "
|
||||||
|
start-stop-daemon -K -p /tmp/rayhunter.pid
|
||||||
|
echo "done"
|
||||||
|
;;
|
||||||
|
restart)
|
||||||
|
$0 stop
|
||||||
|
$0 start
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage rayhunter_daemon { start | stop | restart }" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#! /bin/sshell
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
start)
|
|
||||||
echo -n "Starting wavehunter: "
|
|
||||||
start-stop-daemon -S -b --make-pidfile --pidfile /tmp/wavehunter.pid \
|
|
||||||
--startas /bin/bash -- -c "exec /data/wavehunter/wavehunter > /data/wavehunter/wavehunter.log 2>&1"
|
|
||||||
echo "done"
|
|
||||||
;;
|
|
||||||
stop)
|
|
||||||
echo -n "Stopping wavehunter: "
|
|
||||||
start-stop-daemon -K -p /tmp/wavehunter.pid
|
|
||||||
echo "done"
|
|
||||||
;;
|
|
||||||
restart)
|
|
||||||
$0 stop
|
|
||||||
$0 start
|
|
||||||
;;
|
|
||||||
*)
|
|
||||||
echo "Usage atfwd_daemon{ start | stop | restart }" >&2
|
|
||||||
exit 1
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
|
|
||||||
exit 0
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
use crate::error::WavehunterError;
|
|
||||||
|
|
||||||
use serde::Deserialize;
|
|
||||||
|
|
||||||
#[derive(Deserialize)]
|
|
||||||
struct ConfigFile {
|
|
||||||
qmdl_path: Option<String>,
|
|
||||||
port: Option<u16>,
|
|
||||||
debug_mode: Option<bool>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Config {
|
|
||||||
pub qmdl_path: String,
|
|
||||||
pub port: u16,
|
|
||||||
pub debug_mode: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Config {
|
|
||||||
fn default() -> Self {
|
|
||||||
Config {
|
|
||||||
qmdl_path: "./wavehunter.qmdl".to_string(),
|
|
||||||
port: 8080,
|
|
||||||
debug_mode: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_config<P>(path: P) -> Result<Config, WavehunterError> where P: AsRef<std::path::Path> {
|
|
||||||
let config_file = std::fs::read_to_string(&path)
|
|
||||||
.map_err(|_| WavehunterError::MissingConfigFile(format!("{:?}", path.as_ref())))?;
|
|
||||||
let parsed_config: ConfigFile = toml::from_str(&config_file)
|
|
||||||
.map_err(WavehunterError::ConfigFileParsingError)?;
|
|
||||||
let mut config = Config::default();
|
|
||||||
if let Some(path) = parsed_config.qmdl_path { config.qmdl_path = path }
|
|
||||||
if let Some(port) = parsed_config.port { config.port = port }
|
|
||||||
if let Some(debug_mode) = parsed_config.debug_mode { config.debug_mode = debug_mode }
|
|
||||||
Ok(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Args {
|
|
||||||
pub config_path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn parse_args() -> Args {
|
|
||||||
let args: Vec<String> = std::env::args().collect();
|
|
||||||
if args.len() != 2 {
|
|
||||||
println!("Usage: {} /path/to/config/file", args[0]);
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
Args {
|
|
||||||
config_path: args[1].clone(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
mod config;
|
|
||||||
mod error;
|
|
||||||
mod pcap;
|
|
||||||
mod server;
|
|
||||||
mod stats;
|
|
||||||
|
|
||||||
use crate::config::{parse_config, parse_args};
|
|
||||||
use crate::server::{ServerState, get_qmdl, serve_static};
|
|
||||||
use crate::pcap::get_pcap;
|
|
||||||
use crate::stats::{get_system_stats, get_diag_stats};
|
|
||||||
use crate::error::WavehunterError;
|
|
||||||
|
|
||||||
use axum::response::Redirect;
|
|
||||||
use orca::diag_device::DiagDevice;
|
|
||||||
use orca::diag_reader::DiagReader;
|
|
||||||
use axum::routing::get;
|
|
||||||
use axum::Router;
|
|
||||||
use tokio::fs::File;
|
|
||||||
use log::debug;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use tokio::net::TcpListener;
|
|
||||||
use tokio::sync::RwLock;
|
|
||||||
use tokio::task::JoinHandle;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
fn run_diag_read_thread(mut dev: DiagDevice, bytes_read_lock: Arc<RwLock<usize>>) -> JoinHandle<Result<(), WavehunterError>> {
|
|
||||||
tokio::task::spawn_blocking(move || {
|
|
||||||
loop {
|
|
||||||
// TODO: once we're actually doing analysis, we'll wanna use the messages
|
|
||||||
// returned here. Until then, the DiagDevice has already written those messages
|
|
||||||
// to the QMDL file, so we can just ignore them.
|
|
||||||
debug!("reading response from diag device...");
|
|
||||||
let _messages = dev.read_response().map_err(WavehunterError::DiagReadError)?;
|
|
||||||
debug!("got diag response ({} messages)", _messages.len());
|
|
||||||
|
|
||||||
// keep track of how many bytes were written to the QMDL file so we can read
|
|
||||||
// a valid block of data from it in the HTTP server
|
|
||||||
debug!("total QMDL bytes written: {}, updating state...", dev.qmdl_writer.total_written);
|
|
||||||
let mut bytes_read = bytes_read_lock.blocking_write();
|
|
||||||
*bytes_read = dev.qmdl_writer.total_written;
|
|
||||||
debug!("done!");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn run_server(config: &config::Config, qmdl_bytes_written: Arc<RwLock<usize>>) -> Result<(), WavehunterError> {
|
|
||||||
let state = Arc::new(ServerState {
|
|
||||||
qmdl_bytes_written,
|
|
||||||
qmdl_path: config.qmdl_path.clone(),
|
|
||||||
});
|
|
||||||
|
|
||||||
let app = Router::new()
|
|
||||||
.route("/api/pcap/latest.pcap", get(get_pcap))
|
|
||||||
.route("/api/qmdl/latest.qmdl", get(get_qmdl))
|
|
||||||
.route("/api/system-stats", get(get_system_stats))
|
|
||||||
.route("/api/diag-stats", get(get_diag_stats))
|
|
||||||
.route("/", get(|| async { Redirect::permanent("/index.html") }))
|
|
||||||
.route("/*path", get(serve_static))
|
|
||||||
.with_state(state);
|
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], config.port));
|
|
||||||
let listener = TcpListener::bind(&addr).await.unwrap();
|
|
||||||
axum::serve(listener, app).await.unwrap();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), WavehunterError> {
|
|
||||||
env_logger::init();
|
|
||||||
|
|
||||||
let args = parse_args();
|
|
||||||
let config = parse_config(&args.config_path)?;
|
|
||||||
|
|
||||||
let qmdl_bytes_lock: Arc<RwLock<usize>>;
|
|
||||||
if !config.debug_mode {
|
|
||||||
let mut dev = DiagDevice::new(&config.qmdl_path)
|
|
||||||
.map_err(WavehunterError::DiagInitError)?;
|
|
||||||
dev.config_logs()
|
|
||||||
.map_err(WavehunterError::DiagInitError)?;
|
|
||||||
qmdl_bytes_lock = Arc::new(RwLock::new(dev.qmdl_writer.total_written));
|
|
||||||
|
|
||||||
// TODO: handle exiting gracefully
|
|
||||||
let _read_thread_handle = run_diag_read_thread(dev, qmdl_bytes_lock.clone());
|
|
||||||
} else {
|
|
||||||
let qmdl_file = File::open(&config.qmdl_path).await.expect("couldn't open QMDL file");
|
|
||||||
let qmdl_file_size = qmdl_file.metadata().await.expect("couldn't get QMDL file metadata")
|
|
||||||
.len() as usize;
|
|
||||||
qmdl_bytes_lock = Arc::new(RwLock::new(qmdl_file_size));
|
|
||||||
}
|
|
||||||
|
|
||||||
println!("The orca is hunting for stingrays...");
|
|
||||||
run_server(&config, qmdl_bytes_lock).await
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>ORCA</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="css/style.css">
|
|
||||||
<script src="js/main.js"></script>
|
|
||||||
<script>
|
|
||||||
window.onload = function() {
|
|
||||||
setInterval(function() {
|
|
||||||
populateDivs();
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="downloads">
|
|
||||||
<span><a href="/api/pcap/latest.pcap">Latest PCAP</a></span>
|
|
||||||
<span><a href="/api/qmdl/latest.qmdl">Latest QMDL</a></span>
|
|
||||||
</div>
|
|
||||||
<pre id="system-stats">Loading...</pre>
|
|
||||||
<pre id="diag-stats">Loading...</pre>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
async function populateDivs() {
|
|
||||||
const systemStats = await getSystemStats();
|
|
||||||
const diagStats = await getDiagStats();
|
|
||||||
|
|
||||||
const systemStatsDiv = document.getElementById('system-stats');
|
|
||||||
const diagStatsDiv = document.getElementById('diag-stats');
|
|
||||||
|
|
||||||
systemStatsDiv.innerHTML = JSON.stringify(systemStats, null, 2);
|
|
||||||
diagStatsDiv.innerHTML = JSON.stringify(diagStats, null, 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getSystemStats() {
|
|
||||||
return await getJson('/api/system-stats');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getDiagStats() {
|
|
||||||
return await getJson('/api/diag-stats');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getJson(url) {
|
|
||||||
const response = await fetch(url);
|
|
||||||
const data = await response.json();
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user