├── .cargo └── config.toml ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ └── bug_report.md └── workflows │ ├── ci.yml │ └── manuf.yml ├── .gitignore ├── .gitmodules ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── Makefile ├── README.md ├── assets └── manuf ├── completions ├── bash_angryoxide_completions └── zsh_angryoxide_completions ├── death.png ├── install.sh ├── libs ├── libwifi_macros │ ├── Cargo.toml │ └── src │ │ ├── inner │ │ └── mod.rs │ │ └── lib.rs └── pcap-file │ ├── Cargo.toml │ ├── benches │ ├── bench.pcap │ ├── bench.pcapng │ └── benches.rs │ ├── fuzz │ ├── .gitignore │ ├── Cargo.toml │ └── fuzz_targets │ │ ├── pcap_ng_parser.rs │ │ ├── pcap_ng_reader.rs │ │ ├── pcap_parser.rs │ │ └── pcap_reader.rs │ ├── rustfmt.toml │ └── src │ ├── common.rs │ ├── errors.rs │ ├── lib.rs │ ├── pcap │ ├── header.rs │ ├── mod.rs │ ├── packet.rs │ ├── parser.rs │ ├── reader.rs │ └── writer.rs │ ├── pcapng │ ├── blocks │ │ ├── block_common.rs │ │ ├── enhanced_packet.rs │ │ ├── interface_description.rs │ │ ├── interface_statistics.rs │ │ ├── mod.rs │ │ ├── name_resolution.rs │ │ ├── opt_common.rs │ │ ├── packet.rs │ │ ├── section_header.rs │ │ ├── simple_packet.rs │ │ ├── systemd_journal_export.rs │ │ └── unknown.rs │ ├── mod.rs │ ├── parser.rs │ ├── reader.rs │ └── writer.rs │ └── read_buffer.rs ├── screenshots ├── AP1.png ├── AP2.png ├── AP3.png ├── HS1.png ├── STATIONS.png ├── ap_tab.png ├── attacaksummaries.png ├── handshakes_tab.png ├── logo.png ├── station_tab.png ├── status.png └── wifi-hardware.png └── src ├── advancedtable ├── advtable.rs └── mod.rs ├── ascii.rs ├── attack.rs ├── auth.rs ├── database.rs ├── devices.rs ├── eventhandler.rs ├── geofence.rs ├── gps.rs ├── main.rs ├── matrix.rs ├── oui.rs ├── pcapng.rs ├── rawsocks.rs ├── snowstorm.rs ├── status.rs ├── tabbedblock ├── mod.rs ├── tab.rs └── tabbedblock.rs ├── targets.rs ├── tx.rs ├── ui.rs ├── util.rs └── whitelist.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.mips-unknown-linux-musl] 2 | linker = "/home/rage/dev/Checkpoint/toolchains/mips-linux-muslsf-cross/bin/mips-linux-muslsf-gcc" 3 | rustflags = [ 4 | "-C", "target-feature=+crt-static", 5 | "-C", "relocation-model=static", 6 | "-C", "strip=symbols", "-C", "link-args=-lgcc", 7 | "-L/home/rage/dev/Checkpoint/toolchains/mips-linux-muslsf-cross/mips-linux-muslsf/lib", 8 | "-L/home/rage/dev/Checkpoint//toolchains/mips-linux-muslsf-cross/lib/gcc/mips-linux-muslsf/11.2.1", 9 | ] 10 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: Ragnt 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | If you are unsure if this is a bug or expected behavior, feel free to ask in our Discord (https://discord.gg/QsEgaFndsQ) 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: 17 | 1. Go to '...' 18 | 2. Click on '....' 19 | 3. Scroll down to '....' 20 | 4. See error 21 | 22 | **Expected behavior** 23 | A clear and concise description of what you expected to happen. 24 | 25 | **Screenshots** 26 | If applicable, add screenshots to help explain your problem. 27 | 28 | ** Hardware (please complete the following information):** 29 | - Device: [e.g. VMware / Baremetal Dell XXX] 30 | - OS: [e.g. Kali, Ubuntu, Arch] 31 | - Interface: [e.g. Alfa AWUS035ACM] 32 | 33 | **Additional context** 34 | Add any other context about the problem here. 35 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Builds and Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "*" 7 | 8 | env: 9 | CRATE_NAME: angry_oxide 10 | GITHUB_TOKEN: ${{ github.token }} 11 | RUST_BACKTRACE: 1 12 | 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | test: 18 | name: ${{ matrix.platform.os_name }} with rust ${{ matrix.toolchain }} 19 | runs-on: ${{ matrix.platform.os }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | platform: 24 | - os_name: Linux-x86_64 25 | os: ubuntu-20.04 26 | target: x86_64-unknown-linux-gnu 27 | bin: angryoxide 28 | name: angryoxide-linux-x86_64.tar.gz 29 | 30 | - os_name: Linux-x86_64_musl 31 | os: ubuntu-20.04 32 | target: x86_64-unknown-linux-musl 33 | bin: angryoxide 34 | name: angryoxide-linux-x86_64-musl.tar.gz 35 | 36 | - os_name: Linux-aarch64 37 | os: ubuntu-20.04 38 | target: aarch64-unknown-linux-musl 39 | bin: angryoxide 40 | name: angryoxide-linux-aarch64-musl.tar.gz 41 | 42 | - os_name: Linux-arm 43 | os: ubuntu-20.04 44 | target: arm-unknown-linux-musleabi 45 | bin: angryoxide 46 | name: angryoxide-linux-arm-musl.tar.gz 47 | 48 | - os_name: Linux-armv7hf 49 | os: ubuntu-20.04 50 | target: armv7-unknown-linux-musleabihf 51 | bin: angryoxide 52 | name: angryoxide-linux-armv7hf-musl.tar.gz 53 | 54 | - os_name: Linux-aarch64-gnu 55 | os: ubuntu-20.04 56 | target: aarch64-unknown-linux-gnu 57 | bin: angryoxide 58 | name: angryoxide-linux-aarch64-gnu.tar.gz 59 | 60 | toolchain: 61 | - stable 62 | 63 | steps: 64 | - uses: actions/checkout@v3 65 | - name: Cache cargo & target directories 66 | uses: Swatinem/rust-cache@v2 67 | - name: Configure Git 68 | run: | 69 | git config --global user.email "jdoe@example.com" 70 | git config --global user.name "J. Doe" 71 | - name: Install musl-tools on Linux 72 | run: sudo apt-get update --yes && sudo apt-get install --yes musl-tools 73 | if: contains(matrix.platform.name, 'musl') 74 | 75 | - name: Install cross 76 | id: cross-nix 77 | shell: bash 78 | run: | 79 | set -e 80 | export TARGET="$HOME/bin" 81 | mkdir -p "$TARGET" 82 | ./bootstrap/bootstrap-ubi.sh 83 | "$HOME/bin/ubi" --project cross-rs/cross --matching musl --in . 84 | if: matrix.platform.cross && !contains(matrix.platform.target, 'gnu') 85 | 86 | - name: Install cross (gnu) 87 | id: cross-nix-gnu 88 | shell: bash 89 | run: | 90 | set -e 91 | export TARGET="$HOME/bin" 92 | mkdir -p "$TARGET" 93 | ./bootstrap/bootstrap-ubi.sh 94 | "$HOME/bin/ubi" --project cross-rs/cross --matching musl --in . 95 | if: matrix.platform.cross && contains(matrix.platform.target, 'gnu') 96 | 97 | - name: Build binary 98 | uses: houseabsolute/actions-rust-cross@v0 99 | with: 100 | command: "build" 101 | target: ${{ matrix.platform.target }} 102 | toolchain: ${{ matrix.toolchain }} 103 | args: "--locked --release" 104 | strip: true 105 | if: ${{ !contains(matrix.platform.target, 'mips') }} 106 | 107 | - name: Package as archive 108 | shell: bash 109 | run: | 110 | cd target/${{ matrix.platform.target }}/release 111 | tar czvf ../../../${{ matrix.platform.name }} ${{ matrix.platform.bin }} ../../../install.sh ../../../completions 112 | cd - 113 | if: | 114 | startsWith( github.ref, 'refs/tags/v' ) 115 | - name: Publish GitHub release 116 | uses: softprops/action-gh-release@v1 117 | with: 118 | draft: true 119 | files: "angryoxide-*" 120 | if: startsWith( github.ref, 'refs/tags/v' ) 121 | -------------------------------------------------------------------------------- /.github/workflows/manuf.yml: -------------------------------------------------------------------------------- 1 | name: Update manuf file 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' # Runs daily at midnight UTC 6 | workflow_dispatch: 7 | 8 | env: 9 | GITHUB_TOKEN: ${{ github.token }} 10 | 11 | permissions: 12 | contents: write 13 | 14 | jobs: 15 | update-manuf: 16 | runs-on: ubuntu-latest 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v3 21 | 22 | - name: Download manuf file 23 | run: curl -o assets/manuf https://www.wireshark.org/download/automated/data/manuf 24 | 25 | - name: Commit changes 26 | run: | 27 | git config --global user.name "github-actions" 28 | git config --global user.email "actions@github.com" 29 | git add assets/manuf 30 | git commit -m "Update manuf file" 31 | 32 | - name: Push changes 33 | run: git push 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .vscode/* 3 | *.tar.gz 4 | *.pcapng 5 | *.kismet 6 | *.hc22000 7 | 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/libwifi"] 2 | path = libs/libwifi 3 | url = git@github.com:Ragnt/libwifi.git 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["libs/libwifi", "libs/libwifi_macros", "libs/pcap-file"] 3 | 4 | [workspace.package] 5 | version = "0.8.29" 6 | authors = ["Ryan Butler"] 7 | description = "80211 Attack Tool" 8 | license = "GPL" 9 | edition = "2021" 10 | rust-version = "1.70" 11 | 12 | [package] 13 | name = "angry_oxide" 14 | version.workspace = true 15 | authors.workspace = true 16 | description.workspace = true 17 | edition.workspace = true 18 | license.workspace = true 19 | 20 | [[bin]] 21 | name = "angryoxide" 22 | path = "src/main.rs" 23 | 24 | [features] 25 | default = ["bundled"] 26 | bundled = ["rusqlite/bundled"] 27 | 28 | [dependencies] 29 | libwifi = { version = "0.3.1", path = "libs/libwifi" } 30 | pcap-file = { version = "2.0.0", path = "libs/pcap-file" } 31 | #nl80211-ng = { version = ">=0.5.5", path = "../nl/nl80211-ng" } 32 | nl80211-ng = ">=0.5.5" 33 | byteorder = "1.5.0" 34 | libc = "0.2.149" 35 | nix = { version = "0.27.1", features = [ 36 | "socket", 37 | "ioctl", 38 | "net", 39 | "fs", 40 | "user", 41 | ] } 42 | radiotap = "1.3.0" 43 | anyhow = "1.0.75" 44 | neli = "0.6.4" 45 | neli-proc-macros = "0.1.0" 46 | hex = "0.4.3" 47 | rand = "0.8.5" 48 | ctrlc = "3.4.1" 49 | crossterm = "0.27.0" 50 | ratatui = { version = "0.25.0", features = [ 51 | "all-widgets", 52 | "unstable-segment-size", 53 | ] } 54 | chrono = "0.4.31" 55 | crc = "3.0.1" 56 | clap = { version = "4.4.18", features = ["derive"] } 57 | strum = "0.25.0" 58 | strum_macros = "0.25.3" 59 | derive_setters = "0.1.6" 60 | gpsd_proto = "1.0.0" 61 | itertools = "0.12.0" 62 | geographiclib-rs = "0.2.3" 63 | rusqlite = "0.30.0" 64 | uuid = { version = "1.6.1", features = ["v4"] } 65 | crc32fast = "1.3.2" 66 | flate2 = { version = "1.0.28", features = ["zlib"] } 67 | tar = "0.4.40" 68 | procfs = "0.16.0" 69 | uname = "0.1.1" 70 | globset = "0.4.14" 71 | terminal-clipboard = "0.4.1" 72 | unicode-width = "0.1.11" 73 | stability = "0.1.1" 74 | copypasta-ext = { version = "0.4.4", features = ["osc52"] } 75 | geo = "0.28.0" 76 | geo-types = "0.7.13" 77 | geoconvert = "1.0.2" 78 | geomorph = "1.1.0" 79 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | prog := angryoxide 2 | bash_completion_script := completions/bash_angryoxide_completions 3 | zsh_completion_script := completions/zsh_angryoxide_completions 4 | 5 | debug ?= 6 | 7 | ifdef debug 8 | release := 9 | target := debug 10 | extension := debug 11 | else 12 | release := --release 13 | target := release 14 | extension := 15 | endif 16 | 17 | BASH_COMPLETION_DIR := /etc/bash_completion.d 18 | ZSH_COMPLETION_DIR := /home 19 | 20 | build: 21 | cargo build $(release) 22 | 23 | check-root: 24 | @if [ "$$(id -u)" -ne 0 ]; then \ 25 | echo "This operation must be run as root. Please use sudo." >&2; \ 26 | exit 1; \ 27 | fi 28 | 29 | install-binary: check-root 30 | cp target/$(target)/$(prog) /usr/bin/$(prog) 31 | 32 | install-bash: check-root 33 | @if [ -x "$$(command -v bash)" ]; then \ 34 | echo "Installing bash completion for $(prog)..."; \ 35 | mkdir -p $(BASH_COMPLETION_DIR); \ 36 | cp $(bash_completion_script) $(BASH_COMPLETION_DIR)/$(prog); \ 37 | echo "Bash completion installed successfully."; \ 38 | else \ 39 | echo "Bash not found, skipping Bash completion installation."; \ 40 | fi 41 | 42 | install-zsh: check-root 43 | @if [ -x "$$(command -v zsh)" ]; then \ 44 | echo "Installing zsh completion for $(prog) for all users..."; \ 45 | for dir in $(ZSH_COMPLETION_DIR)/*; do \ 46 | if [ -d "$$dir" ]; then \ 47 | user=$$(basename $$dir); \ 48 | zsh_dir="$$dir/.zsh/completion"; \ 49 | echo "Installing for user $$user..."; \ 50 | mkdir -p $$zsh_dir; \ 51 | cp $(zsh_completion_script) $$zsh_dir/_$(prog); \ 52 | chown $$user:$$user $$zsh_dir/_$(prog); \ 53 | fi \ 54 | done; \ 55 | echo "Zsh completion installed successfully for all users."; \ 56 | else \ 57 | echo "Zsh not found, skipping Zsh completion installation."; \ 58 | fi 59 | 60 | install: install-binary install-bash install-zsh 61 | 62 | help: 63 | @echo "usage: make [debug=1]" 64 | 65 | uninstall: 66 | rm -f /usr/bin/$(prog) 67 | @rm -f $(BASH_COMPLETION_DIR)/$(prog) 68 | @for dir in $(ZSH_COMPLETION_DIR)/*; do \ 69 | if [ -d "$$dir" ]; then \ 70 | rm -f "$$dir/.zsh/completion/_$(prog)"; \ 71 | fi \ 72 | done; \ 73 | echo "Cleaned installed binary and completion scripts." 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngryOxide 😡 2 | 3 | ![Logo](death.png) 4 | 5 | ### A 802.11 Attack tool built in Rust 🦀 ! 6 | 7 | [![Builds and Release](https://github.com/Ragnt/AngryOxide/actions/workflows/ci.yml/badge.svg?branch=master)](https://github.com/Ragnt/AngryOxide/actions/workflows/ci.yml) ![GitHub commit activity](https://img.shields.io/github/commit-activity/m/Ragnt/AngryOxide) [![Discord](https://img.shields.io/discord/1194365883099922643)](https://discord.gg/QsEgaFndsQ) 8 | 9 | **This tool is for research purposes only. I am not responsible for anything you do or damage you cause while using AngryOxide. Only use against networks that you have permission.** 10 | 11 | AngryOxide was developed as a way to learn Rust, netlink, kernel sockets, and WiFi exploitation all at once. 12 | 13 | You can get information about how to use AngryOxide in the [User Guide](https://github.com/Ragnt/AngryOxide/wiki/1.-User-Guide). 14 | 15 | NOTE: This project is under HEAVY development and you can expect a very fast release cycle. 16 | 17 | The overall goal of this tool is to provide a single-interface survey capability with advanced automated attacks that result in valid hashlines you can crack with [Hashcat](https://hashcat.net/hashcat/). 18 | 19 | This tool is heavily inspired by [hcxdumptool](https://github.com/ZerBea/hcxdumptool) and development wouldn't have been possible without help from ZerBea. 20 | 21 | If you have questions or any issues, you can reach me on the [AngryOxide Discord](https://discord.gg/QsEgaFndsQ) 22 | 23 | ## I wanna use it! 24 | 25 | You can download pre-compiled binaries of AngryOxide in the [releases](https://github.com/Ragnt/AngryOxide/releases/latest). 26 | 27 | ```bash 28 | tar -xf angryoxide-linux-x86_64.tar.gz # Untar 29 | chmod +x install.sh # Make executable 30 | sudo ./install.sh # Install (as root, including zsh/bash completions) 31 | ``` 32 | 33 | You can get information about how to use AngryOxide in the [User Guide](https://github.com/Ragnt/AngryOxide/wiki/1.-User-Guide). 34 | 35 | #### Uninstalling: 36 | 37 | ```bash 38 | sudo ./install.sh uninstall # Uninstall 39 | ``` 40 | 41 | ## Features 42 | 43 | - Active state-based attack engine used to retrieve relevent EAPOL messages from Access Points and clients. 44 | - Target option that accepts MAC (aabbcc..., aa:bb:cc...) and SSID "Test_SSID" to limit attack scope. 45 | - Whitelist option to protect specific networks from attacks. Useful if not using targets. 46 | - Auto Hunt capability to find all target channels and hop between them. 47 | - A Terminal-UI that presents all relevent data while still living in the terminal for easy usage over SSH. 48 | - A grepable "Headless" operation mode that simply prints status output, ready to be redirected to a log file. 49 | - Limits DEAUTHENTICATION frames that can cause more damage than good to the authentication sequence. 50 | - EAPOL 4-Way-Handshake validation using Nonce Correction, Replay Counter validation, and Temporal validation. 51 | - Automatically elicits PMKID from access points where available. 52 | - Utilizes GPSD with ability to set remote GPSD service address. 53 | - Ability to enable geo-fencing to force AO to only run when inside a geometric area specified by a latitude, longitude, and radius. 54 | - Provides pcapng files with embedded GPS using the [Kismet Format](https://www.kismetwireless.net/docs/dev/pcapng_gps/). 55 | - Provides a kismetdb file with all frames (with GPS) for post-processing. 56 | - Wraps all output files in a gzipped tarball. 57 | - Bash autocompletions for easy interface selection provided. 58 | 59 | ## Attacks 60 | 61 | Will by default attack ALL access points in range, unless atleast one target is supplied, at which point the tool will only transmit against defined targets. (But will still passively collect on other access points). 62 | 63 | - Attempts authentication/association sequence to produce EAPOL Message 1 (PMKID Collection) 64 | - Attempts to retrieve hidden SSID's with direct probe requests. 65 | - Utilizes Anonymous Reassociation to force Access Points to deauthenticate their own clients (MFP Bypass) 66 | - Will attempt to send Channel Switch Announcement to send clients to adjacent channels. 67 | - Attempts to downgrade RSN modes to WPA2-CCMP (Probe Response Injection via RogueM2) 68 | - Attempts to collect EAPOL M2 from stations based solely on Probe Requests (RogueM2) 69 | - Attempts to disassociate clients using WiFi 6e codes that prevent blacklisting 70 | - All attacks can be manually disabled. 71 | 72 | All of these attacks are rate-controlled both to prevent erroneous EAPOL timer resets and to maintain some level of operational security. 73 | 74 | ## Help 75 | 76 | ``` 77 | ❯ angryoxide --help 78 | Does awesome things... with wifi. 79 | 80 | Usage: angryoxide [OPTIONS] --interface 81 | 82 | Options: 83 | -i, --interface Interface to use 84 | -c, --channel Optional - Channel to scan. Will use "-c 1,6,11" if none specified 85 | -b, --band <2 | 5 | 6 | 60> Optional - Entire band to scan - will include all channels interface can support 86 | -o, --output Optional - Output filename 87 | -h, --help Print help 88 | -V, --version Print version 89 | 90 | Targeting: 91 | -t, --target-entry 92 | Optional - Target (MAC or SSID) to attack - will attack everything if none specified 93 | -w, --whitelist-entry 94 | Optional - Whitelist (MAC or SSID) to NOT attack 95 | --targetlist 96 | Optional - File to load target entries from 97 | --whitelist 98 | Optional - File to load whitelist entries from 99 | 100 | Advanced Options: 101 | -r, --rate Optional - Attack rate (1, 2, 3 || 3 is most aggressive) [default: 2] 102 | --combine Optional - Combine all hc22000 files into one large file for bulk processing 103 | --noactive Optional - Disable Active Monitor mode 104 | --rogue Optional - Tx MAC for rogue-based attacks - will randomize if excluded 105 | --gpsd Optional - Alter default HOST:Port for GPSD connection [default: 127.0.0.1:2947] 106 | --autohunt Optional - AO will auto-hunt all channels then lock in on the ones targets are on 107 | --headless Optional - Set the tool to headless mode without a UI. (useful with --autoexit) 108 | --autoexit Optional - AO will auto-exit when all targets have a valid hashline 109 | --notransmit Optional - Do not transmit - passive only 110 | --notar Optional - Do not tar output files 111 | --disablemouse Optional - Disable mouse capture (scroll wheel) 112 | --dwell Optional - Adjust channel hop dwell time [default: 2] 113 | 114 | Geofencing: 115 | --geofence 116 | Optional - Enable geofencing using a specified latlng and distance 117 | --center
118 | Lat,Lng for geofencing (required if geofence is enabled) 119 | --distance 120 | Distance in meters from the center (required if geofence is enabled) 121 | --geofence-timeout 122 | Timeout to disable geofence if GPS is lost. (default 300 seconds) [default: 300] 123 | 124 | Attacks: 125 | --disable-deauth Optional - Do NOT send deauthentication attacks 126 | --disable-pmkid Optional - Do NOT attempt to associate for PMKID 127 | --disable-anon Optional - Do NOT send anonymous reassociation attacks 128 | --disable-csa Optional - Do NOT send Channel Switch Announcment attacks 129 | --disable-disassoc Optional - Do NOT send disassociation attacks 130 | --disable-roguem2 Optional - Do NOT attempt rogue M2 collection 131 | ``` 132 | 133 | ## Building from source 134 | 135 | If you want to build from source instead of using precompiled binaries, these are the basic instructions: 136 | 137 | ``` 138 | # Install Rust 139 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh 140 | 141 | # Clone this repo 142 | git clone --recurse-submodules https://github.com/Ragnt/AngryOxide.git 143 | 144 | # Build/Install 145 | cd AngryOxide 146 | make 147 | sudo make install 148 | ``` 149 | 150 | This will build from source, install into /usr/bin/angryoxide, and install the bash completions for you. 151 | 152 | ### Cross compiling: 153 | 154 | I use [cross](https://github.com/cross-rs/cross) to cross compile to embedded architectures. 155 | 156 | Here is MIPS (mips-unknown-linux-musl) as an example. 157 | 158 | ``` 159 | # make sure you have the nightly installed 160 | rustup install nightly 161 | 162 | # dynamically linked & soft-float 163 | cross build +nightly --target mips-unknown-linux-musl --release -Zbuild-std 164 | ``` 165 | 166 | 167 | ### Completions script: 168 | 169 | These make using AngryOxide with bash and zsh a bit more fluid, automatically finding your wireless interfaces for you and showing you the arguments in a tab-completable way. 170 | 171 | ## Screenshots! 172 | 173 | ![Access Points Page](screenshots/ap_tab.png) 174 | ![Handshakes Page](screenshots/handshakes_tab.png) 175 | -------------------------------------------------------------------------------- /completions/bash_angryoxide_completions: -------------------------------------------------------------------------------- 1 | # angryoxide completion -*- shell-script -*- 2 | 3 | _available_interfaces() { 4 | COMPREPLY=($(iw dev | grep Interface | awk '{print $2}')) 5 | } 6 | 7 | _angryoxide() 8 | { 9 | local cur prev words cword 10 | _init_completion || return 11 | 12 | case $prev in 13 | -i|--interface) 14 | _available_interfaces 15 | return 0 16 | ;; 17 | --targetlist|--whitelist|--output) 18 | _filedir 19 | return 0 20 | ;; 21 | esac 22 | 23 | if [[ $cword -eq 1 ]]; then 24 | if [[ $cur == --* ]]; then 25 | COMPREPLY=( $( compgen -W '--interface --channel --band --output --help --version --target-entry --whitelist-entry --targetlist --rate --combine --noactive --rogue --gpsd --autohunt --headless --autoexit --notransmit --notar --disablemouse --dwell --geofence --center --distance --geofence-timeout --disable-deauth --disable-pmkid --disable-anon --disable-csa --disable-disassoc --disable-roguem2' -- "$cur" ) ) 26 | elif [[ $cur == -* ]]; then 27 | COMPREPLY=( $( compgen -W '-i -c -b -o -h -V -t -w -r' -- "$cur" ) ) 28 | fi 29 | fi 30 | } && 31 | complete -F _angryoxide angryoxide -------------------------------------------------------------------------------- /completions/zsh_angryoxide_completions: -------------------------------------------------------------------------------- 1 | #compdef angryoxide 2 | 3 | _list_wireless_interfaces() { 4 | local -a interfaces 5 | interfaces=($(iw dev | grep Interface | awk '{print $2}')) 6 | _describe 'interface' interfaces 7 | } 8 | 9 | _angryoxide() { 10 | local -a options 11 | local state 12 | 13 | _arguments -C \ 14 | '1: :->command' \ 15 | '(-i --interface)'{-i,--interface}'[Interface to use]:interface:_list_wireless_interfaces' \ 16 | '(-c --channel)'{-c,--channel}'[Optional - Channel to scan (default: 1,6,11)]:channel:' \ 17 | '(-b --band)'{-b,--band}'[Optional - Entire band to scan]:band:(2 5 6 60)' \ 18 | '(-o --output)'{-o,--output}'[Optional - Output filename]:output file:_files' \ 19 | '(-h --help)'{-h,--help}'[Print help]' \ 20 | '(-V --version)'{-V,--version}'[Print version]' \ 21 | '(-t --target-entry)'{-t,--target-entry}'[Optional - Target (MAC or SSID) to attack]:target entry:' \ 22 | '(-w --whitelist-entry)'{-w,--whitelist-entry}'[Optional - Whitelist (MAC or SSID) to NOT attack]:whitelist entry:' \ 23 | '--targetlist[Optional - File to load target entries from]:targets file:_files' \ 24 | '--whitelist[Optional - File to load whitelist entries from]:whitelist file:_files' \ 25 | '(-r --rate)'{-r,--rate}'[Optional - Attack rate (1, 2, 3)]:rate:(1 2 3)' \ 26 | '--combine[Optional - Combine all hc22000 files into one large file for bulk processing]' \ 27 | '--noactive[Optional - Disable Active Monitor mode]' \ 28 | '--rogue[Optional - Tx MAC for rogue-based attacks]:MAC Address:' \ 29 | '--gpsd[Optional - Alter default HOST:Port for GPSD connection]:GPSD Host:Port:' \ 30 | '--autohunt[Optional - AO will auto-hunt all channels then lock in]' \ 31 | '--headless[Optional - Set the tool to headless mode without a UI]' \ 32 | '--autoexit[Optional - AO will auto-exit when all targets have a valid hashline]' \ 33 | '--notransmit[Optional - Do not transmit - passive only]' \ 34 | '--notar[Optional - Do not tar output files]' \ 35 | '--disablemouse[Optional - Disable mouse capture (scroll wheel)]' \ 36 | '--dwell[Optional - Adjust channel hop dwell time (seconds)]:Dwell Time (seconds):' \ 37 | '--geofence[Optional - Enable geofencing using a specified latlng and distance]' \ 38 | '--center[Lat,Lng for geofencing]:CENTER:' \ 39 | '--distance[Distance in meters from the center]:DISTANCE:' \ 40 | '--geofence-timeout[Timeout to disable geofence if GPS is lost]:GEOFENCE_TIMEOUT:' \ 41 | '--disable-deauth[Optional - Do NOT send deauthentication attacks]' \ 42 | '--disable-pmkid[Optional - Do NOT attempt to associate for PMKID]' \ 43 | '--disable-anon[Optional - Do NOT send anonymous reassociation attacks]' \ 44 | '--disable-csa[Optional - Do NOT send Channel Switch Announcement attacks]' \ 45 | '--disable-disassoc[Optional - Do NOT send disassociation attacks]' \ 46 | '--disable-roguem2[Optional - Do NOT attempt rogue M2 collection]' 47 | } 48 | 49 | _angryoxide "$@" -------------------------------------------------------------------------------- /death.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/death.png -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | prog="angryoxide" 4 | bash_completion_script="completions/bash_angryoxide_completions" 5 | zsh_completion_script="completions/zsh_angryoxide_completions" 6 | BASH_COMPLETION_DIR="/etc/bash_completion.d" 7 | ZSH_COMPLETION_DIR="/home" 8 | 9 | check_root() { 10 | if [[ "$(id -u)" -ne 0 ]]; then 11 | echo "This operation must be run as root. Please use sudo." >&2 12 | exit 1 13 | fi 14 | } 15 | 16 | install_binary() { 17 | check_root 18 | echo "Installing $prog binary..." 19 | chmod +x $prog 20 | cp "$prog" "/usr/bin/$prog" 21 | } 22 | 23 | install_bash() { 24 | check_root 25 | if command -v bash &> /dev/null; then 26 | echo "Installing bash completion for $prog..." 27 | mkdir -p "$BASH_COMPLETION_DIR" 28 | cp "$bash_completion_script" "$BASH_COMPLETION_DIR/$prog" 29 | echo "Bash completion installed successfully." 30 | else 31 | echo "Bash not found, skipping Bash completion installation." 32 | fi 33 | } 34 | 35 | install_zsh() { 36 | check_root 37 | if command -v zsh &> /dev/null; then 38 | echo "Installing zsh completion for $prog for all users..." 39 | for dir in $ZSH_COMPLETION_DIR/*; do 40 | if [[ -d "$dir" ]]; then 41 | user=$(basename "$dir") 42 | zsh_dir="$dir/.zsh/completion" 43 | echo "Installing for user $user..." 44 | mkdir -p "$zsh_dir" 45 | cp "$zsh_completion_script" "$zsh_dir/_$prog" 46 | chown "$user:$user" "$zsh_dir/_$prog" 47 | fi 48 | done 49 | echo "Zsh completion installed successfully for all users." 50 | else 51 | echo "Zsh not found, skipping Zsh completion installation." 52 | fi 53 | } 54 | 55 | uninstall() { 56 | check_root 57 | echo "Uninstalling $prog..." 58 | rm -f "/usr/bin/$prog" 59 | rm -f "$BASH_COMPLETION_DIR/$prog" 60 | for dir in $ZSH_COMPLETION_DIR/*; do 61 | if [[ -d "$dir" ]]; then 62 | rm -f "$dir/.zsh/completion/_$prog" 63 | fi 64 | done 65 | echo "Cleaned installed binary and completion scripts." 66 | } 67 | 68 | case "$1" in 69 | install) 70 | install_binary 71 | install_bash 72 | install_zsh 73 | ;; 74 | uninstall) 75 | uninstall 76 | ;; 77 | *) 78 | install_binary 79 | install_bash 80 | install_zsh 81 | ;; 82 | esac 83 | -------------------------------------------------------------------------------- /libs/libwifi_macros/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "libwifi_macros" 3 | version = "0.0.2" 4 | description = "A library providing macros for the libwifi crate." 5 | authors = ["Arne Beer "] 6 | homepage = "https://github.com/nukesor/libwifi" 7 | repository = "https://github.com/nukesor/libwifi" 8 | documentation = "https://docs.rs/libwifi/" 9 | license.workspace = true 10 | edition.workspace = true 11 | rust-version.workspace = true 12 | 13 | [lib] 14 | proc-macro = true 15 | 16 | [dependencies] 17 | syn = "2" 18 | proc-macro2 = "1" 19 | quote = "1" 20 | -------------------------------------------------------------------------------- /libs/libwifi_macros/src/inner/mod.rs: -------------------------------------------------------------------------------- 1 | use proc_macro2::TokenStream; 2 | use quote::quote; 3 | use syn::DeriveInput; 4 | 5 | pub fn address_header_inner(ast: &DeriveInput) -> syn::Result { 6 | let name = &ast.ident; 7 | 8 | Ok(quote! { 9 | impl crate::Addresses for #name { 10 | fn src(&self) -> Option<&MacAddress> { 11 | self.header.src() 12 | } 13 | 14 | fn dest(&self) -> &MacAddress { 15 | self.header.dest() 16 | } 17 | 18 | fn bssid(&self) -> Option<&MacAddress> { 19 | self.header.bssid() 20 | } 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /libs/libwifi_macros/src/lib.rs: -------------------------------------------------------------------------------- 1 | use proc_macro::TokenStream; 2 | use syn::{parse_macro_input, DeriveInput}; 3 | 4 | mod inner; 5 | 6 | /// A little helper derive macro to implement the `libwifi::Addresses` trait 7 | /// for frames with either a DataHeader or a ManagementHeader. 8 | /// 9 | /// This macro is only designed for internal usage in the [libwifi](https://docs.rs/libwifi/latest/libwifi/) crate. 10 | /// 11 | /// How to use: 12 | /// ```rust,ignore 13 | /// #[derive(Clone, Debug, AddressHeader)] 14 | /// pub struct AssociationRequest { 15 | /// pub header: ManagementHeader, 16 | /// pub beacon_interval: u16, 17 | /// pub capability_info: u16, 18 | /// pub station_info: StationInfo, 19 | /// } 20 | /// ``` 21 | /// 22 | /// The new generated code will look like this: 23 | /// ```rust,ignore 24 | /// impl crate::Addresses for AssociationRequest { 25 | /// fn src(&self) -> Option<&MacAddress> { 26 | /// self.header.src() 27 | /// } 28 | /// 29 | /// fn dest(&self) -> &MacAddress { 30 | /// self.header.dest() 31 | /// } 32 | /// 33 | /// fn bssid(&self) -> Option<&MacAddress> { 34 | /// self.header.bssid() 35 | /// } 36 | /// } 37 | /// ``` 38 | #[proc_macro_derive(AddressHeader)] 39 | pub fn address_header(input: TokenStream) -> TokenStream { 40 | let input = parse_macro_input!(input as DeriveInput); 41 | 42 | let toks = inner::address_header_inner(&input).unwrap_or_else(|err| err.to_compile_error()); 43 | 44 | toks.into() 45 | } 46 | -------------------------------------------------------------------------------- /libs/pcap-file/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "pcap-file" 3 | edition = "2021" 4 | version = "2.0.0" 5 | authors = ["Courvoif "] 6 | description = "A crate to parse, read and write Pcap and PcapNg" 7 | 8 | license = "MIT" 9 | documentation = "https://docs.rs/pcap-file/" 10 | repository = "https://github.com/courvoif/pcap-file" 11 | readme = "README.md" 12 | keywords = ["pcap", "pcapng", "parse", "read", "write"] 13 | categories = ["encoding", "parsing"] 14 | 15 | exclude = ["benches/bench.pcap", "benches/bench.pcapng", "fuzz", "tests"] 16 | 17 | 18 | [dependencies] 19 | byteorder_slice = "3.0.0" 20 | derive-into-owned = "0.2.0" 21 | thiserror = "1.0.35" 22 | 23 | [dev-dependencies] 24 | criterion = "0.4.0" 25 | glob = "0.3.0" 26 | hex = "0.4.3" 27 | -------------------------------------------------------------------------------- /libs/pcap-file/benches/bench.pcap: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:051c0ce7b837da71f7b977c2c23fe535c3a78974d93ad1b73885fa1163b39a0b 3 | size 45521026 4 | -------------------------------------------------------------------------------- /libs/pcap-file/benches/bench.pcapng: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3c0a149f304e0ed4b7aaa4b12a85e93a561d207e85c5b0bdd4281279d81d274c 3 | size 47232248 4 | -------------------------------------------------------------------------------- /libs/pcap-file/benches/benches.rs: -------------------------------------------------------------------------------- 1 | use criterion::{criterion_group, criterion_main, Criterion}; 2 | use pcap_file::pcap::{PcapParser, PcapReader}; 3 | use pcap_file::pcapng::{PcapNgParser, PcapNgReader}; 4 | use pcap_file::PcapError; 5 | 6 | 7 | /// Bench and compare Pcap readers and parsers 8 | pub fn pcap(c: &mut Criterion) { 9 | let pcap = std::fs::read("benches/bench.pcap").unwrap(); 10 | 11 | let mut group = c.benchmark_group("Pcap"); 12 | group.throughput(criterion::Throughput::Bytes(pcap.len() as u64)); 13 | 14 | group.bench_function("Parser", |b| { 15 | b.iter(|| { 16 | let (mut src, parser) = PcapParser::new(&pcap).unwrap(); 17 | loop { 18 | match parser.next_packet(src) { 19 | Ok((rem, _)) => src = rem, 20 | Err(PcapError::IncompleteBuffer) => break, 21 | Err(_) => panic!(), 22 | } 23 | } 24 | }) 25 | }); 26 | 27 | group.bench_function("ParserRaw", |b| { 28 | b.iter(|| { 29 | let (mut src, parser) = PcapParser::new(&pcap).unwrap(); 30 | loop { 31 | match parser.next_raw_packet(src) { 32 | Ok((rem, _)) => src = rem, 33 | Err(PcapError::IncompleteBuffer) => break, 34 | Err(_) => panic!(), 35 | } 36 | } 37 | }) 38 | }); 39 | 40 | group.bench_function("Reader", |b| { 41 | b.iter(|| { 42 | let mut src = &pcap[..]; 43 | let mut reader = PcapReader::new(&mut src).unwrap(); 44 | while let Some(pkt) = reader.next_packet() { 45 | pkt.unwrap(); 46 | } 47 | }) 48 | }); 49 | 50 | group.bench_function("ReaderRaw", |b| { 51 | b.iter(|| { 52 | let mut src = &pcap[..]; 53 | let mut reader = PcapReader::new(&mut src).unwrap(); 54 | while let Some(pkt) = reader.next_raw_packet() { 55 | pkt.unwrap(); 56 | } 57 | }) 58 | }); 59 | } 60 | 61 | 62 | /// Bench and compare PcapNg readers and parsers 63 | pub fn pcapng(c: &mut Criterion) { 64 | let pcapng = std::fs::read("benches/bench.pcapng").unwrap(); 65 | 66 | let mut group = c.benchmark_group("PcapNg"); 67 | group.throughput(criterion::Throughput::Bytes(pcapng.len() as u64)); 68 | 69 | group.bench_function("Parser", |b| { 70 | b.iter(|| { 71 | let (mut src, mut parser) = PcapNgParser::new(&pcapng).unwrap(); 72 | loop { 73 | match parser.next_block(src) { 74 | Ok((rem, _)) => src = rem, 75 | Err(PcapError::IncompleteBuffer) => break, 76 | Err(_) => panic!(), 77 | } 78 | } 79 | }) 80 | }); 81 | 82 | group.bench_function("ParserRaw", |b| { 83 | b.iter(|| { 84 | let (mut src, mut parser) = PcapNgParser::new(&pcapng).unwrap(); 85 | loop { 86 | match parser.next_raw_block(src) { 87 | Ok((rem, _)) => src = rem, 88 | Err(PcapError::IncompleteBuffer) => break, 89 | Err(_) => panic!(), 90 | } 91 | } 92 | }) 93 | }); 94 | 95 | group.bench_function("Reader", |b| { 96 | b.iter(|| { 97 | let mut src = &pcapng[..]; 98 | let mut reader = PcapNgReader::new(&mut src).unwrap(); 99 | while let Some(pkt) = reader.next_block() { 100 | pkt.unwrap(); 101 | } 102 | }) 103 | }); 104 | 105 | group.bench_function("ReaderRaw", |b| { 106 | b.iter(|| { 107 | let mut src = &pcapng[..]; 108 | let mut reader = PcapNgReader::new(&mut src).unwrap(); 109 | while let Some(pkt) = reader.next_raw_block() { 110 | pkt.unwrap(); 111 | } 112 | }) 113 | }); 114 | } 115 | 116 | criterion_group!(benches, pcap, pcapng); 117 | criterion_main!(benches); 118 | -------------------------------------------------------------------------------- /libs/pcap-file/fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | coverage -------------------------------------------------------------------------------- /libs/pcap-file/fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | 2 | [package] 3 | name = "pcap-file-fuzz" 4 | version = "0.0.0" 5 | authors = ["Automatically generated"] 6 | publish = false 7 | edition = "2018" 8 | 9 | [package.metadata] 10 | cargo-fuzz = true 11 | 12 | [dependencies] 13 | libfuzzer-sys = "0.3" 14 | 15 | [dependencies.pcap-file] 16 | path = ".." 17 | 18 | # Prevent this from interfering with workspaces 19 | [workspace] 20 | members = ["."] 21 | 22 | [[bin]] 23 | name = "pcap_parser" 24 | path = "fuzz_targets/pcap_parser.rs" 25 | test = false 26 | doc = false 27 | 28 | [[bin]] 29 | name = "pcap_ng_parser" 30 | path = "fuzz_targets/pcap_ng_parser.rs" 31 | test = false 32 | doc = false 33 | 34 | [[bin]] 35 | name = "pcap_reader" 36 | path = "fuzz_targets/pcap_reader.rs" 37 | test = false 38 | doc = false 39 | 40 | [[bin]] 41 | name = "pcap_ng_reader" 42 | path = "fuzz_targets/pcap_ng_reader.rs" 43 | test = false 44 | doc = false 45 | -------------------------------------------------------------------------------- /libs/pcap-file/fuzz/fuzz_targets/pcap_ng_parser.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use pcap_file::pcapng::PcapNgParser; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | if let Ok((rem, mut pcapng_parser)) = PcapNgParser::new(data) { 7 | let mut src = rem; 8 | 9 | while !src.is_empty() { 10 | let _ = pcapng_parser.next_block(src); 11 | src = &src[1..]; 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /libs/pcap-file/fuzz/fuzz_targets/pcap_ng_reader.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use pcap_file::pcapng::PcapNgReader; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | if let Ok(mut pcapng_reader) = PcapNgReader::new(data) { 7 | while let Some(_block) = pcapng_reader.next_block() {} 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /libs/pcap-file/fuzz/fuzz_targets/pcap_parser.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use pcap_file::pcap::PcapParser; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | if let Ok((rem, pcap_parser)) = PcapParser::new(data) { 7 | let mut src = rem; 8 | 9 | while !src.is_empty() { 10 | let _ = pcap_parser.next_packet(src); 11 | src = &src[1..]; 12 | } 13 | } 14 | }); 15 | -------------------------------------------------------------------------------- /libs/pcap-file/fuzz/fuzz_targets/pcap_reader.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | use pcap_file::pcap::PcapReader; 4 | 5 | fuzz_target!(|data: &[u8]| { 6 | if let Ok(mut pcap_reader) = PcapReader::new(data) { 7 | while let Some(_packet) = pcap_reader.next_packet() {} 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /libs/pcap-file/rustfmt.toml: -------------------------------------------------------------------------------- 1 | array_width = 120 2 | attr_fn_like_width = 120 3 | binop_separator = "Front" 4 | blank_lines_lower_bound = 0 5 | blank_lines_upper_bound = 2 6 | brace_style = "SameLineWhere" 7 | chain_width = 100 8 | color = "Always" 9 | combine_control_expr = false 10 | comment_width = 140 11 | condense_wildcard_suffixes = true 12 | control_brace_style = "ClosingNextLine" 13 | empty_item_single_line = true 14 | enum_discrim_align_threshold = 0 15 | fn_args_layout = "Tall" 16 | fn_call_width = 120 17 | fn_single_line = false 18 | force_explicit_abi = true 19 | force_multiline_blocks = false 20 | format_code_in_doc_comments = true 21 | format_generated_files = false 22 | format_macro_matchers = true 23 | format_macro_bodies = true 24 | format_strings = true 25 | hard_tabs = false 26 | hex_literal_case = "Upper" 27 | imports_indent = "Block" 28 | imports_layout = "Mixed" 29 | indent_style = "Block" 30 | inline_attribute_width = 0 31 | match_arm_blocks = true 32 | match_arm_leading_pipes = "Never" 33 | match_block_trailing_comma = true 34 | max_width = 140 35 | merge_derives = true 36 | imports_granularity = "Module" 37 | newline_style = "Unix" 38 | normalize_comments = false 39 | normalize_doc_attributes = true 40 | overflow_delimited_expr = false 41 | remove_nested_parens = true 42 | reorder_impl_items = true 43 | reorder_imports = true 44 | group_imports = "StdExternalCrate" 45 | reorder_modules = true 46 | skip_children = false 47 | single_line_if_else_max_width = 80 48 | space_after_colon = true 49 | space_before_colon = false 50 | spaces_around_ranges = false 51 | struct_field_align_threshold = 0 52 | struct_lit_single_line = true 53 | struct_lit_width = 80 54 | struct_variant_width = 80 55 | tab_spaces = 4 56 | trailing_comma = "Vertical" 57 | trailing_semicolon = true 58 | type_punctuation_density = "Wide" 59 | unstable_features = true 60 | use_field_init_shorthand = true 61 | use_small_heuristics = "Off" 62 | use_try_shorthand = true 63 | version = "Two" 64 | where_single_line = false 65 | wrap_comments = true 66 | -------------------------------------------------------------------------------- /libs/pcap-file/src/errors.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error; 2 | 3 | /// Result type for the pcap/pcapng parsing 4 | pub type PcapResult = Result; 5 | 6 | /// Error type for the pcap/pcapng parsing 7 | #[derive(Error, Debug)] 8 | pub enum PcapError { 9 | /// Buffer too small 10 | #[error("Need more bytes")] 11 | IncompleteBuffer, 12 | 13 | /// Generic IO error 14 | #[error("Error reading bytes")] 15 | IoError(#[source] std::io::Error), 16 | 17 | /// Invalid field 18 | #[error("Invalid field value: {0}")] 19 | InvalidField(&'static str), 20 | 21 | /// UTF8 conversion error 22 | #[error("UTF8 error")] 23 | Utf8Error(#[source] std::str::Utf8Error), 24 | 25 | /// From UTF8 conversion error 26 | #[error("UTF8 error")] 27 | FromUtf8Error(#[source] std::string::FromUtf8Error), 28 | 29 | /// Invalid interface ID (only for Pcap NG) 30 | #[error("No corresponding interface id: {0}")] 31 | InvalidInterfaceId(u32), 32 | } 33 | 34 | impl From for PcapError { 35 | fn from(err: std::str::Utf8Error) -> Self { 36 | PcapError::Utf8Error(err) 37 | } 38 | } 39 | 40 | impl From for PcapError { 41 | fn from(err: std::string::FromUtf8Error) -> Self { 42 | PcapError::FromUtf8Error(err) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libs/pcap-file/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::unreadable_literal)] 2 | 3 | //! Provides parsers, readers and writers for Pcap and PcapNg files. 4 | //! 5 | //! For Pcap files see the [`pcap`] module, especially [`PcapParser`](pcap::PcapParser), 6 | //! [`PcapReader`](pcap::PcapReader) and [`PcapWriter`](pcap::PcapWriter). 7 | //! 8 | //! For PcapNg files see the [`pcapng`] module, especially [`PcapNgParser`](pcapng::PcapNgParser), 9 | //! [`PcapNgReader`](pcapng::PcapNgReader) and [`PcapNgWriter`](pcapng::PcapNgWriter) 10 | 11 | pub use common::*; 12 | pub use errors::*; 13 | 14 | pub(crate) mod common; 15 | pub(crate) mod errors; 16 | pub(crate) mod read_buffer; 17 | 18 | pub mod pcap; 19 | pub mod pcapng; 20 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcap/header.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use byteorder_slice::byteorder::WriteBytesExt; 4 | use byteorder_slice::result::ReadSlice; 5 | use byteorder_slice::{BigEndian, ByteOrder, LittleEndian}; 6 | 7 | use crate::errors::*; 8 | use crate::{DataLink, Endianness, TsResolution}; 9 | 10 | 11 | /// Pcap Global Header 12 | #[derive(Copy, Clone, Debug, Eq, PartialEq)] 13 | pub struct PcapHeader { 14 | /// Major version number 15 | pub version_major: u16, 16 | 17 | /// Minor version number 18 | pub version_minor: u16, 19 | 20 | /// GMT to local timezone correction, should always be 0 21 | pub ts_correction: i32, 22 | 23 | /// Timestamp accuracy, should always be 0 24 | pub ts_accuracy: u32, 25 | 26 | /// Max length of captured packet, typically 65535 27 | pub snaplen: u32, 28 | 29 | /// DataLink type (first layer in the packet) 30 | pub datalink: DataLink, 31 | 32 | /// Timestamp resolution of the pcap (microsecond or nanosecond) 33 | pub ts_resolution: TsResolution, 34 | 35 | /// Endianness of the pcap (excluding the packet data) 36 | pub endianness: Endianness, 37 | } 38 | 39 | impl PcapHeader { 40 | /// Creates a new [`PcapHeader`] from a slice of bytes. 41 | /// 42 | /// Returns an error if the reader doesn't contain a valid pcap 43 | /// or if there is a reading error. 44 | /// 45 | /// [`PcapError::IncompleteBuffer`] indicates that there is not enough data in the buffer. 46 | pub fn from_slice(mut slice: &[u8]) -> PcapResult<(&[u8], PcapHeader)> { 47 | // Check that slice.len() > PcapHeader length 48 | if slice.len() < 24 { 49 | return Err(PcapError::IncompleteBuffer); 50 | } 51 | 52 | let magic_number = slice.read_u32::().unwrap(); 53 | 54 | match magic_number { 55 | 0xA1B2C3D4 => return init_pcap_header::(slice, TsResolution::MicroSecond, Endianness::Big), 56 | 0xA1B23C4D => return init_pcap_header::(slice, TsResolution::NanoSecond, Endianness::Big), 57 | 0xD4C3B2A1 => return init_pcap_header::(slice, TsResolution::MicroSecond, Endianness::Little), 58 | 0x4D3CB2A1 => return init_pcap_header::(slice, TsResolution::NanoSecond, Endianness::Little), 59 | _ => return Err(PcapError::InvalidField("PcapHeader: wrong magic number")), 60 | }; 61 | 62 | // Inner function used for the initialisation of the PcapHeader. 63 | // Must check the srcclength before calling it. 64 | fn init_pcap_header( 65 | mut src: &[u8], 66 | ts_resolution: TsResolution, 67 | endianness: Endianness, 68 | ) -> PcapResult<(&[u8], PcapHeader)> { 69 | let header = PcapHeader { 70 | version_major: src.read_u16::().unwrap(), 71 | version_minor: src.read_u16::().unwrap(), 72 | ts_correction: src.read_i32::().unwrap(), 73 | ts_accuracy: src.read_u32::().unwrap(), 74 | snaplen: src.read_u32::().unwrap(), 75 | datalink: DataLink::from(src.read_u32::().unwrap()), 76 | ts_resolution, 77 | endianness, 78 | }; 79 | 80 | Ok((src, header)) 81 | } 82 | } 83 | 84 | /// Writes a [`PcapHeader`] to a writer. 85 | /// 86 | /// Uses the endianness of the header. 87 | pub fn write_to(&self, writer: &mut W) -> PcapResult { 88 | return match self.endianness { 89 | Endianness::Big => write_header::<_, BigEndian>(self, writer), 90 | Endianness::Little => write_header::<_, LittleEndian>(self, writer), 91 | }; 92 | 93 | fn write_header(header: &PcapHeader, writer: &mut W) -> PcapResult { 94 | let magic_number = match header.ts_resolution { 95 | TsResolution::MicroSecond => 0xA1B2C3D4, 96 | TsResolution::NanoSecond => 0xA1B23C4D, 97 | }; 98 | 99 | writer.write_u32::(magic_number).map_err(PcapError::IoError)?; 100 | writer.write_u16::(header.version_major).map_err(PcapError::IoError)?; 101 | writer.write_u16::(header.version_minor).map_err(PcapError::IoError)?; 102 | writer.write_i32::(header.ts_correction).map_err(PcapError::IoError)?; 103 | writer.write_u32::(header.ts_accuracy).map_err(PcapError::IoError)?; 104 | writer.write_u32::(header.snaplen).map_err(PcapError::IoError)?; 105 | writer.write_u32::(header.datalink.into()).map_err(PcapError::IoError)?; 106 | 107 | Ok(24) 108 | } 109 | } 110 | } 111 | 112 | /// Creates a new [`PcapHeader`] with these parameters: 113 | /// 114 | /// ```rust,ignore 115 | /// PcapHeader { 116 | /// version_major: 2, 117 | /// version_minor: 4, 118 | /// ts_correction: 0, 119 | /// ts_accuracy: 0, 120 | /// snaplen: 65535, 121 | /// datalink: DataLink::ETHERNET, 122 | /// ts_resolution: TsResolution::MicroSecond, 123 | /// endianness: Endianness::Big 124 | /// }; 125 | /// ``` 126 | impl Default for PcapHeader { 127 | fn default() -> Self { 128 | PcapHeader { 129 | version_major: 2, 130 | version_minor: 4, 131 | ts_correction: 0, 132 | ts_accuracy: 0, 133 | snaplen: 65535, 134 | datalink: DataLink::ETHERNET, 135 | ts_resolution: TsResolution::MicroSecond, 136 | endianness: Endianness::Big, 137 | } 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcap/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains the Pcap parser, reader and writer 2 | 3 | mod header; 4 | mod packet; 5 | mod parser; 6 | mod reader; 7 | mod writer; 8 | 9 | pub use header::*; 10 | pub use packet::*; 11 | pub use parser::*; 12 | pub use reader::*; 13 | pub use writer::*; 14 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcap/packet.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::io::Write; 3 | use std::time::Duration; 4 | 5 | use byteorder_slice::byteorder::WriteBytesExt; 6 | use byteorder_slice::result::ReadSlice; 7 | use byteorder_slice::ByteOrder; 8 | use derive_into_owned::IntoOwned; 9 | 10 | use crate::errors::*; 11 | use crate::TsResolution; 12 | 13 | /// Pcap packet. 14 | /// 15 | /// The payload can be owned or borrowed. 16 | #[derive(Clone, Debug, IntoOwned)] 17 | pub struct PcapPacket<'a> { 18 | /// Timestamp EPOCH of the packet with a nanosecond resolution 19 | pub timestamp: Duration, 20 | /// Original length of the packet when captured on the wire 21 | pub orig_len: u32, 22 | /// Payload, owned or borrowed, of the packet 23 | pub data: Cow<'a, [u8]>, 24 | } 25 | 26 | impl<'a> PcapPacket<'a> { 27 | /// Creates a new borrowed [`PcapPacket`] with the given parameters. 28 | pub fn new(timestamp: Duration, orig_len: u32, data: &'a [u8]) -> PcapPacket<'a> { 29 | PcapPacket { timestamp, orig_len, data: Cow::Borrowed(data) } 30 | } 31 | 32 | /// Creates a new owned [`PcapPacket`] with the given parameters. 33 | pub fn new_owned(timestamp: Duration, orig_len: u32, data: Vec) -> PcapPacket<'static> { 34 | PcapPacket { timestamp, orig_len, data: Cow::Owned(data) } 35 | } 36 | 37 | /// Parses a new borrowed [`PcapPacket`] from a slice. 38 | pub fn from_slice(slice: &'a [u8], ts_resolution: TsResolution, snap_len: u32) -> PcapResult<(&'a [u8], PcapPacket<'a>)> { 39 | let (rem, raw_packet) = RawPcapPacket::from_slice::(slice)?; 40 | let s = Self::try_from_raw_packet(raw_packet, ts_resolution, snap_len)?; 41 | 42 | Ok((rem, s)) 43 | } 44 | 45 | /// Writes a [`PcapPacket`] to a writer. 46 | pub fn write_to(&self, writer: &mut W, ts_resolution: TsResolution, snap_len: u32) -> PcapResult { 47 | // Transforms PcapPacket::ts into ts_sec and ts_frac // 48 | let ts_sec = self 49 | .timestamp 50 | .as_secs() 51 | .try_into() 52 | .map_err(|_| PcapError::InvalidField("PcapPacket: timestamp_secs > u32::MAX"))?; 53 | 54 | let mut ts_frac = self.timestamp.subsec_nanos(); 55 | if ts_resolution == TsResolution::MicroSecond { 56 | ts_frac /= 1000; 57 | } 58 | 59 | // Validate the packet length // 60 | let incl_len = self.data.len().try_into().map_err(|_| PcapError::InvalidField("PcapPacket: incl_len > u32::MAX"))?; 61 | let orig_len = self.orig_len; 62 | 63 | if incl_len > snap_len { 64 | return Err(PcapError::InvalidField("PcapPacket: incl_len > snap_len")); 65 | } 66 | 67 | if incl_len > orig_len { 68 | return Err(PcapError::InvalidField("PcapPacket: incl_len > orig_len")); 69 | } 70 | 71 | let raw_packet = RawPcapPacket { ts_sec, ts_frac, incl_len, orig_len, data: Cow::Borrowed(&self.data[..]) }; 72 | 73 | raw_packet.write_to::<_, B>(writer) 74 | } 75 | 76 | /// Tries to create a [`PcapPacket`] from a [`RawPcapPacket`]. 77 | pub fn try_from_raw_packet(raw: RawPcapPacket<'a>, ts_resolution: TsResolution, snap_len: u32) -> PcapResult { 78 | // Validate timestamps // 79 | let ts_sec = raw.ts_sec; 80 | let mut ts_nsec = raw.ts_frac; 81 | if ts_resolution == TsResolution::MicroSecond { 82 | ts_nsec = ts_nsec.checked_mul(1000).ok_or(PcapError::InvalidField("PacketHeader ts_nanosecond is invalid"))?; 83 | } 84 | if ts_nsec >= 1_000_000_000 { 85 | return Err(PcapError::InvalidField("PacketHeader ts_nanosecond >= 1_000_000_000")); 86 | } 87 | 88 | // Validate lengths // 89 | let incl_len = raw.incl_len; 90 | let orig_len = raw.orig_len; 91 | 92 | if incl_len > snap_len { 93 | return Err(PcapError::InvalidField("PacketHeader incl_len > snap_len")); 94 | } 95 | 96 | if orig_len > snap_len { 97 | return Err(PcapError::InvalidField("PacketHeader orig_len > snap_len")); 98 | } 99 | 100 | if incl_len > orig_len { 101 | return Err(PcapError::InvalidField("PacketHeader incl_len > orig_len")); 102 | } 103 | 104 | Ok(PcapPacket { timestamp: Duration::new(ts_sec as u64, ts_nsec), orig_len, data: raw.data }) 105 | } 106 | } 107 | 108 | 109 | /// Raw Pcap packet with its header and data. 110 | /// The fields of the packet are not validated. 111 | /// The payload can be owned or borrowed. 112 | #[derive(Clone, Debug, IntoOwned)] 113 | pub struct RawPcapPacket<'a> { 114 | /// Timestamp in seconds 115 | pub ts_sec: u32, 116 | /// Nanosecond or microsecond part of the timestamp 117 | pub ts_frac: u32, 118 | /// Number of octets of the packet saved in file 119 | pub incl_len: u32, 120 | /// Original length of the packet on the wire 121 | pub orig_len: u32, 122 | /// Payload, owned or borrowed, of the packet 123 | pub data: Cow<'a, [u8]>, 124 | } 125 | 126 | impl<'a> RawPcapPacket<'a> { 127 | /// Parses a new borrowed [`RawPcapPacket`] from a slice. 128 | pub fn from_slice(mut slice: &'a [u8]) -> PcapResult<(&'a [u8], Self)> { 129 | // Check header length 130 | if slice.len() < 16 { 131 | return Err(PcapError::IncompleteBuffer); 132 | } 133 | 134 | // Read packet header // 135 | // Can unwrap because the length check is done before 136 | let ts_sec = slice.read_u32::().unwrap(); 137 | let ts_frac = slice.read_u32::().unwrap(); 138 | let incl_len = slice.read_u32::().unwrap(); 139 | let orig_len = slice.read_u32::().unwrap(); 140 | 141 | let pkt_len = incl_len as usize; 142 | if slice.len() < pkt_len { 143 | return Err(PcapError::IncompleteBuffer); 144 | } 145 | 146 | let packet = RawPcapPacket { ts_sec, ts_frac, incl_len, orig_len, data: Cow::Borrowed(&slice[..pkt_len]) }; 147 | let rem = &slice[pkt_len..]; 148 | 149 | Ok((rem, packet)) 150 | } 151 | 152 | /// Writes a [`RawPcapPacket`] to a writer. 153 | /// The fields of the packet are not validated. 154 | pub fn write_to(&self, writer: &mut W) -> PcapResult { 155 | writer.write_u32::(self.ts_sec).map_err(PcapError::IoError)?; 156 | writer.write_u32::(self.ts_frac).map_err(PcapError::IoError)?; 157 | writer.write_u32::(self.incl_len).map_err(PcapError::IoError)?; 158 | writer.write_u32::(self.orig_len).map_err(PcapError::IoError)?; 159 | writer.write_all(&self.data).map_err(PcapError::IoError)?; 160 | 161 | Ok(16 + self.data.len()) 162 | } 163 | 164 | /// Tries to convert a [`RawPcapPacket`] into a [`PcapPacket`]. 165 | pub fn try_into_pcap_packet(self, ts_resolution: TsResolution, snap_len: u32) -> PcapResult> { 166 | PcapPacket::try_from_raw_packet(self, ts_resolution, snap_len) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcap/parser.rs: -------------------------------------------------------------------------------- 1 | use byteorder_slice::{BigEndian, LittleEndian}; 2 | 3 | use super::RawPcapPacket; 4 | use crate::errors::*; 5 | use crate::pcap::{PcapHeader, PcapPacket}; 6 | use crate::Endianness; 7 | 8 | 9 | /// Parses a Pcap from a slice of bytes. 10 | /// 11 | /// You can match on [`PcapError::IncompleteBuffer`](crate::errors::PcapError) to known if the parser need more data. 12 | /// 13 | /// # Example 14 | /// ```no_run 15 | /// use pcap_file::pcap::PcapParser; 16 | /// use pcap_file::PcapError; 17 | /// 18 | /// let pcap = vec![0_u8; 0]; 19 | /// let mut src = &pcap[..]; 20 | /// 21 | /// // Creates a new parser and parse the pcap header 22 | /// let (rem, pcap_parser) = PcapParser::new(&pcap[..]).unwrap(); 23 | /// src = rem; 24 | /// 25 | /// loop { 26 | /// match pcap_parser.next_packet(src) { 27 | /// Ok((rem, packet)) => { 28 | /// // Do something 29 | /// 30 | /// // Don't forget to update src 31 | /// src = rem; 32 | /// 33 | /// // No more data, if no more incoming either then this is the end of the file 34 | /// if rem.is_empty() { 35 | /// break; 36 | /// } 37 | /// }, 38 | /// Err(PcapError::IncompleteBuffer) => {}, // Load more data into src 39 | /// Err(_) => {}, // Parsing error 40 | /// } 41 | /// } 42 | /// ``` 43 | #[derive(Debug)] 44 | pub struct PcapParser { 45 | header: PcapHeader, 46 | } 47 | 48 | impl PcapParser { 49 | /// Creates a new [`PcapParser`]. 50 | /// 51 | /// Returns the remainder and the parser. 52 | pub fn new(slice: &[u8]) -> PcapResult<(&[u8], PcapParser)> { 53 | let (slice, header) = PcapHeader::from_slice(slice)?; 54 | 55 | let parser = PcapParser { header }; 56 | 57 | Ok((slice, parser)) 58 | } 59 | 60 | /// Returns the remainder and the next [`PcapPacket`]. 61 | pub fn next_packet<'a>(&self, slice: &'a [u8]) -> PcapResult<(&'a [u8], PcapPacket<'a>)> { 62 | match self.header.endianness { 63 | Endianness::Big => PcapPacket::from_slice::(slice, self.header.ts_resolution, self.header.snaplen), 64 | Endianness::Little => PcapPacket::from_slice::(slice, self.header.ts_resolution, self.header.snaplen), 65 | } 66 | } 67 | 68 | /// Returns the remainder and the next [`RawPcapPacket`]. 69 | pub fn next_raw_packet<'a>(&self, slice: &'a [u8]) -> PcapResult<(&'a [u8], RawPcapPacket<'a>)> { 70 | match self.header.endianness { 71 | Endianness::Big => RawPcapPacket::from_slice::(slice), 72 | Endianness::Little => RawPcapPacket::from_slice::(slice), 73 | } 74 | } 75 | 76 | /// Returns the header of the pcap file. 77 | pub fn header(&self) -> PcapHeader { 78 | self.header 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcap/reader.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use super::{PcapParser, RawPcapPacket}; 4 | use crate::errors::*; 5 | use crate::pcap::{PcapHeader, PcapPacket}; 6 | use crate::read_buffer::ReadBuffer; 7 | 8 | 9 | /// Reads a pcap from a reader. 10 | /// 11 | /// # Example 12 | /// 13 | /// ```rust,no_run 14 | /// use std::fs::File; 15 | /// 16 | /// use pcap_file::pcap::PcapReader; 17 | /// 18 | /// let file_in = File::open("test.pcap").expect("Error opening file"); 19 | /// let mut pcap_reader = PcapReader::new(file_in).unwrap(); 20 | /// 21 | /// // Read test.pcap 22 | /// while let Some(pkt) = pcap_reader.next_packet() { 23 | /// //Check if there is no error 24 | /// let pkt = pkt.unwrap(); 25 | /// 26 | /// //Do something 27 | /// } 28 | /// ``` 29 | #[derive(Debug)] 30 | pub struct PcapReader { 31 | parser: PcapParser, 32 | reader: ReadBuffer, 33 | } 34 | 35 | impl PcapReader { 36 | /// Creates a new [`PcapReader`] from an existing reader. 37 | /// 38 | /// This function reads the global pcap header of the file to verify its integrity. 39 | /// 40 | /// The underlying reader must point to a valid pcap file/stream. 41 | /// 42 | /// # Errors 43 | /// The data stream is not in a valid pcap file format. 44 | /// 45 | /// The underlying data are not readable. 46 | pub fn new(reader: R) -> Result, PcapError> { 47 | let mut reader = ReadBuffer::new(reader); 48 | let parser = reader.parse_with(PcapParser::new)?; 49 | 50 | Ok(PcapReader { parser, reader }) 51 | } 52 | 53 | /// Consumes [`Self`], returning the wrapped reader. 54 | pub fn into_reader(self) -> R { 55 | self.reader.into_inner() 56 | } 57 | 58 | /// Returns the next [`PcapPacket`]. 59 | pub fn next_packet(&mut self) -> Option> { 60 | match self.reader.has_data_left() { 61 | Ok(has_data) => { 62 | if has_data { 63 | Some(self.reader.parse_with(|src| self.parser.next_packet(src))) 64 | } 65 | else { 66 | None 67 | } 68 | }, 69 | Err(e) => Some(Err(PcapError::IoError(e))), 70 | } 71 | } 72 | 73 | /// Returns the next [`RawPcapPacket`]. 74 | pub fn next_raw_packet(&mut self) -> Option> { 75 | match self.reader.has_data_left() { 76 | Ok(has_data) => { 77 | if has_data { 78 | Some(self.reader.parse_with(|src| self.parser.next_raw_packet(src))) 79 | } 80 | else { 81 | None 82 | } 83 | }, 84 | Err(e) => Some(Err(PcapError::IoError(e))), 85 | } 86 | } 87 | 88 | /// Returns the global header of the pcap. 89 | pub fn header(&self) -> PcapHeader { 90 | self.parser.header() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcap/writer.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use byteorder_slice::{BigEndian, LittleEndian}; 4 | 5 | use super::RawPcapPacket; 6 | use crate::errors::*; 7 | use crate::pcap::{PcapHeader, PcapPacket}; 8 | use crate::{Endianness, TsResolution}; 9 | 10 | 11 | /// Writes a pcap to a writer. 12 | /// 13 | /// # Example 14 | /// ```rust,no_run 15 | /// use std::fs::File; 16 | /// 17 | /// use pcap_file::pcap::{PcapReader, PcapWriter}; 18 | /// 19 | /// let file_in = File::open("test.pcap").expect("Error opening file"); 20 | /// let mut pcap_reader = PcapReader::new(file_in).unwrap(); 21 | /// 22 | /// let file_out = File::create("out.pcap").expect("Error creating file out"); 23 | /// let mut pcap_writer = PcapWriter::new(file_out).expect("Error writing file"); 24 | /// 25 | /// // Read test.pcap 26 | /// while let Some(pkt) = pcap_reader.next_packet() { 27 | /// //Check if there is no error 28 | /// let pkt = pkt.unwrap(); 29 | /// 30 | /// //Write each packet of test.pcap in out.pcap 31 | /// pcap_writer.write_packet(&pkt).unwrap(); 32 | /// } 33 | /// ``` 34 | #[derive(Debug)] 35 | pub struct PcapWriter { 36 | endianness: Endianness, 37 | snaplen: u32, 38 | ts_resolution: TsResolution, 39 | writer: W, 40 | } 41 | 42 | impl PcapWriter { 43 | /// Creates a new [`PcapWriter`] from an existing writer. 44 | /// 45 | /// Defaults to the native endianness of the CPU. 46 | /// 47 | /// Writes this default global pcap header to the file: 48 | /// ```rust, ignore 49 | /// PcapHeader { 50 | /// version_major: 2, 51 | /// version_minor: 4, 52 | /// ts_correction: 0, 53 | /// ts_accuracy: 0, 54 | /// snaplen: 65535, 55 | /// datalink: DataLink::ETHERNET, 56 | /// ts_resolution: TsResolution::MicroSecond, 57 | /// endianness: Endianness::Native 58 | /// }; 59 | /// ``` 60 | /// 61 | /// # Errors 62 | /// The writer can't be written to. 63 | pub fn new(writer: W) -> PcapResult> { 64 | let header = PcapHeader { endianness: Endianness::native(), ..Default::default() }; 65 | 66 | PcapWriter::with_header(writer, header) 67 | } 68 | 69 | /// Creates a new [`PcapWriter`] from an existing writer with a user defined [`PcapHeader`]. 70 | /// 71 | /// It also writes the pcap header to the file. 72 | /// 73 | /// # Errors 74 | /// The writer can't be written to. 75 | pub fn with_header(mut writer: W, header: PcapHeader) -> PcapResult> { 76 | header.write_to(&mut writer)?; 77 | 78 | Ok(PcapWriter { 79 | endianness: header.endianness, 80 | snaplen: header.snaplen, 81 | ts_resolution: header.ts_resolution, 82 | writer, 83 | }) 84 | } 85 | 86 | /// Consumes [`Self`], returning the wrapped writer. 87 | pub fn into_writer(self) -> W { 88 | self.writer 89 | } 90 | 91 | /// Writes a [`PcapPacket`]. 92 | pub fn write_packet(&mut self, packet: &PcapPacket) -> PcapResult { 93 | match self.endianness { 94 | Endianness::Big => packet.write_to::<_, BigEndian>(&mut self.writer, self.ts_resolution, self.snaplen), 95 | Endianness::Little => packet.write_to::<_, LittleEndian>(&mut self.writer, self.ts_resolution, self.snaplen), 96 | } 97 | } 98 | 99 | /// Writes a [`RawPcapPacket`]. 100 | pub fn write_raw_packet(&mut self, packet: &RawPcapPacket) -> PcapResult { 101 | match self.endianness { 102 | Endianness::Big => packet.write_to::<_, BigEndian>(&mut self.writer), 103 | Endianness::Little => packet.write_to::<_, LittleEndian>(&mut self.writer), 104 | } 105 | } 106 | 107 | /// Flush data 108 | pub fn flush(&mut self) -> PcapResult<()> { 109 | self.writer.flush().map_err(PcapError::IoError) 110 | } 111 | 112 | /// Returns the endianess used by the writer. 113 | pub fn endianness(&self) -> Endianness { 114 | self.endianness 115 | } 116 | 117 | /// Returns the snaplen used by the writer, i.e. an unsigned value indicating the maximum number of octets captured 118 | /// from each packet. 119 | pub fn snaplen(&self) -> u32 { 120 | self.snaplen 121 | } 122 | 123 | /// Returns the timestamp resolution of the writer. 124 | pub fn ts_resolution(&self) -> TsResolution { 125 | self.ts_resolution 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/enhanced_packet.rs: -------------------------------------------------------------------------------- 1 | //! Enhanced Packet Block (EPB). 2 | 3 | use std::borrow::Cow; 4 | use std::io::{Result as IoResult, Write}; 5 | use std::time::Duration; 6 | 7 | use byteorder_slice::byteorder::WriteBytesExt; 8 | use byteorder_slice::result::ReadSlice; 9 | use byteorder_slice::ByteOrder; 10 | use derive_into_owned::IntoOwned; 11 | 12 | use super::block_common::{Block, PcapNgBlock}; 13 | use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; 14 | use crate::errors::PcapError; 15 | 16 | /// An Enhanced Packet Block (EPB) is the standard container for storing the packets coming from the network. 17 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 18 | pub struct EnhancedPacketBlock<'a> { 19 | /// It specifies the interface this packet comes from. 20 | /// 21 | /// The correct interface will be the one whose Interface Description Block 22 | /// (within the current Section of the file) is identified by the same number of this field. 23 | pub interface_id: u32, 24 | 25 | /// Number of units of time that have elapsed since 1970-01-01 00:00:00 UTC. 26 | pub timestamp: Duration, 27 | 28 | /// Actual length of the packet when it was transmitted on the network. 29 | pub original_len: u32, 30 | 31 | /// The data coming from the network, including link-layer headers. 32 | pub data: Cow<'a, [u8]>, 33 | 34 | /// Options 35 | pub options: Vec>, 36 | } 37 | 38 | impl<'a> PcapNgBlock<'a> for EnhancedPacketBlock<'a> { 39 | fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { 40 | if slice.len() < 20 { 41 | return Err(PcapError::InvalidField("EnhancedPacketBlock: block length length < 20")); 42 | } 43 | 44 | let interface_id = slice.read_u32::().unwrap(); 45 | let timestamp_high = slice.read_u32::().unwrap() as u64; 46 | let timestamp_low = slice.read_u32::().unwrap() as u64; 47 | let timestamp = (timestamp_high << 32) + timestamp_low; 48 | let captured_len = slice.read_u32::().unwrap(); 49 | let original_len = slice.read_u32::().unwrap(); 50 | 51 | let pad_len = (4 - (captured_len as usize % 4)) % 4; 52 | let tot_len = captured_len as usize + pad_len; 53 | 54 | if slice.len() < tot_len { 55 | return Err(PcapError::InvalidField("EnhancedPacketBlock: captured_len + padding > block length")); 56 | } 57 | 58 | let data = &slice[..captured_len as usize]; 59 | slice = &slice[tot_len..]; 60 | 61 | let (slice, options) = EnhancedPacketOption::opts_from_slice::(slice)?; 62 | let block = EnhancedPacketBlock { 63 | interface_id, 64 | timestamp: Duration::from_micros(timestamp), 65 | original_len, 66 | data: Cow::Borrowed(data), 67 | options, 68 | }; 69 | 70 | Ok((slice, block)) 71 | } 72 | 73 | fn write_to(&self, writer: &mut W) -> IoResult { 74 | let pad_len = (4 - (&self.data.len() % 4)) % 4; 75 | 76 | writer.write_u32::(self.interface_id)?; 77 | 78 | let timestamp = self.timestamp.as_micros(); 79 | let timestamp_high = (timestamp >> 32) as u32; 80 | writer.write_u32::(timestamp_high)?; 81 | let timestamp_low = (timestamp & 0xFFFFFFFF) as u32; 82 | writer.write_u32::(timestamp_low)?; 83 | 84 | writer.write_u32::(self.data.len() as u32)?; 85 | writer.write_u32::(self.original_len)?; 86 | writer.write_all(&self.data)?; 87 | writer.write_all(&[0_u8; 3][..pad_len])?; 88 | 89 | let opt_len = EnhancedPacketOption::write_opts_to::(&self.options, writer)?; 90 | 91 | Ok(20 + &self.data.len() + pad_len + opt_len) 92 | } 93 | 94 | fn into_block(self) -> Block<'a> { 95 | Block::EnhancedPacket(self) 96 | } 97 | } 98 | 99 | /// The Enhanced Packet Block (EPB) options 100 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 101 | pub enum EnhancedPacketOption<'a> { 102 | /// Comment associated with the current block 103 | Comment(Cow<'a, str>), 104 | 105 | /// 32-bit flags word containing link-layer information. 106 | Flags(u32), 107 | 108 | /// Contains a hash of the packet. 109 | Hash(Cow<'a, [u8]>), 110 | 111 | /// 64-bit integer value specifying the number of packets lost 112 | /// (by the interface and the operating system) between this packet and the preceding one for 113 | /// the same interface or, for the first packet for an interface, between this packet 114 | /// and the start of the capture process. 115 | DropCount(u64), 116 | 117 | /// Custom option containing binary octets in the Custom Data portion 118 | CustomBinary(CustomBinaryOption<'a>), 119 | 120 | /// Custom option containing a UTF-8 string in the Custom Data portion 121 | CustomUtf8(CustomUtf8Option<'a>), 122 | 123 | /// Unknown option 124 | Unknown(UnknownOption<'a>), 125 | } 126 | 127 | impl<'a> PcapNgOption<'a> for EnhancedPacketOption<'a> { 128 | fn from_slice(code: u16, length: u16, mut slice: &'a [u8]) -> Result { 129 | let opt = match code { 130 | 1 => EnhancedPacketOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 131 | 2 => { 132 | if slice.len() != 4 { 133 | return Err(PcapError::InvalidField("EnhancedPacketOption: Flags length != 4")); 134 | } 135 | EnhancedPacketOption::Flags(slice.read_u32::().map_err(|_| PcapError::IncompleteBuffer)?) 136 | }, 137 | 3 => EnhancedPacketOption::Hash(Cow::Borrowed(slice)), 138 | 4 => { 139 | if slice.len() != 8 { 140 | return Err(PcapError::InvalidField("EnhancedPacketOption: DropCount length != 8")); 141 | } 142 | EnhancedPacketOption::DropCount(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?) 143 | }, 144 | 145 | 2988 | 19372 => EnhancedPacketOption::CustomUtf8(CustomUtf8Option::from_slice::(code, slice)?), 146 | 2989 | 19373 => EnhancedPacketOption::CustomBinary(CustomBinaryOption::from_slice::(code, slice)?), 147 | 148 | _ => EnhancedPacketOption::Unknown(UnknownOption::new(code, length, slice)), 149 | }; 150 | 151 | Ok(opt) 152 | } 153 | 154 | fn write_to(&self, writer: &mut W) -> IoResult { 155 | match self { 156 | EnhancedPacketOption::Comment(a) => a.write_opt_to::(1, writer), 157 | EnhancedPacketOption::Flags(a) => a.write_opt_to::(2, writer), 158 | EnhancedPacketOption::Hash(a) => a.write_opt_to::(3, writer), 159 | EnhancedPacketOption::DropCount(a) => a.write_opt_to::(4, writer), 160 | EnhancedPacketOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), 161 | EnhancedPacketOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), 162 | EnhancedPacketOption::Unknown(a) => a.write_opt_to::(a.code, writer), 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/interface_statistics.rs: -------------------------------------------------------------------------------- 1 | //! Interface Statistics Block. 2 | 3 | use std::borrow::Cow; 4 | use std::io::{Result as IoResult, Write}; 5 | 6 | use byteorder_slice::byteorder::WriteBytesExt; 7 | use byteorder_slice::result::ReadSlice; 8 | use byteorder_slice::ByteOrder; 9 | use derive_into_owned::IntoOwned; 10 | 11 | use super::block_common::{Block, PcapNgBlock}; 12 | use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; 13 | use crate::errors::PcapError; 14 | 15 | 16 | /// The Interface Statistics Block contains the capture statistics for a given interface and it is optional. 17 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 18 | pub struct InterfaceStatisticsBlock<'a> { 19 | /// Specifies the interface these statistics refers to. 20 | /// 21 | /// The correct interface will be the one whose Interface Description Block (within the current Section of the file) 22 | /// is identified by same number of this field. 23 | pub interface_id: u32, 24 | 25 | /// Time this statistics refers to. 26 | /// 27 | /// The format of the timestamp is the same already defined in the Enhanced Packet Block. 28 | /// The length of a unit of time is specified by the 'if_tsresol' option of the Interface Description Block referenced by this packet. 29 | pub timestamp: u64, 30 | 31 | /// Options 32 | pub options: Vec>, 33 | } 34 | 35 | impl<'a> PcapNgBlock<'a> for InterfaceStatisticsBlock<'a> { 36 | fn from_slice(mut slice: &'a [u8]) -> Result<(&[u8], Self), PcapError> { 37 | if slice.len() < 12 { 38 | return Err(PcapError::InvalidField("InterfaceStatisticsBlock: block length < 12")); 39 | } 40 | 41 | let interface_id = slice.read_u32::().unwrap(); 42 | let timestamp = slice.read_u64::().unwrap(); 43 | let (slice, options) = InterfaceStatisticsOption::opts_from_slice::(slice)?; 44 | 45 | let block = InterfaceStatisticsBlock { interface_id, timestamp, options }; 46 | 47 | Ok((slice, block)) 48 | } 49 | 50 | fn write_to(&self, writer: &mut W) -> IoResult { 51 | writer.write_u32::(self.interface_id)?; 52 | writer.write_u64::(self.timestamp)?; 53 | 54 | let opt_len = InterfaceStatisticsOption::write_opts_to::(&self.options, writer)?; 55 | Ok(12 + opt_len) 56 | } 57 | 58 | fn into_block(self) -> Block<'a> { 59 | Block::InterfaceStatistics(self) 60 | } 61 | } 62 | 63 | 64 | /// The Interface Statistics Block options 65 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 66 | pub enum InterfaceStatisticsOption<'a> { 67 | /// The opt_comment option is a UTF-8 string containing human-readable comment text 68 | /// that is associated to the current block. 69 | Comment(Cow<'a, str>), 70 | 71 | /// The isb_starttime option specifies the time the capture started. 72 | IsbStartTime(u64), 73 | 74 | /// The isb_endtime option specifies the time the capture ended. 75 | IsbEndTime(u64), 76 | 77 | /// The isb_ifrecv option specifies the 64-bit unsigned integer number of packets received from the physical interface 78 | /// starting from the beginning of the capture. 79 | IsbIfRecv(u64), 80 | 81 | /// The isb_ifdrop option specifies the 64-bit unsigned integer number of packets dropped by the interface 82 | /// due to lack of resources starting from the beginning of the capture. 83 | IsbIfDrop(u64), 84 | 85 | /// The isb_filteraccept option specifies the 64-bit unsigned integer number of packets accepted 86 | /// by filter starting from the beginning of the capture. 87 | IsbFilterAccept(u64), 88 | 89 | /// The isb_osdrop option specifies the 64-bit unsigned integer number of packets dropped 90 | /// by the operating system starting from the beginning of the capture. 91 | IsbOsDrop(u64), 92 | 93 | /// The isb_usrdeliv option specifies the 64-bit unsigned integer number of packets delivered 94 | /// to the user starting from the beginning of the capture. 95 | IsbUsrDeliv(u64), 96 | 97 | /// Custom option containing binary octets in the Custom Data portion 98 | CustomBinary(CustomBinaryOption<'a>), 99 | 100 | /// Custom option containing a UTF-8 string in the Custom Data portion 101 | CustomUtf8(CustomUtf8Option<'a>), 102 | 103 | /// Unknown option 104 | Unknown(UnknownOption<'a>), 105 | } 106 | 107 | impl<'a> PcapNgOption<'a> for InterfaceStatisticsOption<'a> { 108 | fn from_slice(code: u16, length: u16, mut slice: &'a [u8]) -> Result { 109 | let opt = match code { 110 | 1 => InterfaceStatisticsOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 111 | 2 => InterfaceStatisticsOption::IsbStartTime(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 112 | 3 => InterfaceStatisticsOption::IsbEndTime(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 113 | 4 => InterfaceStatisticsOption::IsbIfRecv(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 114 | 5 => InterfaceStatisticsOption::IsbIfDrop(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 115 | 6 => InterfaceStatisticsOption::IsbFilterAccept(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 116 | 7 => InterfaceStatisticsOption::IsbOsDrop(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 117 | 8 => InterfaceStatisticsOption::IsbUsrDeliv(slice.read_u64::().map_err(|_| PcapError::IncompleteBuffer)?), 118 | 119 | 2988 | 19372 => InterfaceStatisticsOption::CustomUtf8(CustomUtf8Option::from_slice::(code, slice)?), 120 | 2989 | 19373 => InterfaceStatisticsOption::CustomBinary(CustomBinaryOption::from_slice::(code, slice)?), 121 | 122 | _ => InterfaceStatisticsOption::Unknown(UnknownOption::new(code, length, slice)), 123 | }; 124 | 125 | Ok(opt) 126 | } 127 | 128 | fn write_to(&self, writer: &mut W) -> IoResult { 129 | match self { 130 | InterfaceStatisticsOption::Comment(a) => a.write_opt_to::(1, writer), 131 | InterfaceStatisticsOption::IsbStartTime(a) => a.write_opt_to::(2, writer), 132 | InterfaceStatisticsOption::IsbEndTime(a) => a.write_opt_to::(3, writer), 133 | InterfaceStatisticsOption::IsbIfRecv(a) => a.write_opt_to::(4, writer), 134 | InterfaceStatisticsOption::IsbIfDrop(a) => a.write_opt_to::(5, writer), 135 | InterfaceStatisticsOption::IsbFilterAccept(a) => a.write_opt_to::(6, writer), 136 | InterfaceStatisticsOption::IsbOsDrop(a) => a.write_opt_to::(7, writer), 137 | InterfaceStatisticsOption::IsbUsrDeliv(a) => a.write_opt_to::(8, writer), 138 | InterfaceStatisticsOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), 139 | InterfaceStatisticsOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), 140 | InterfaceStatisticsOption::Unknown(a) => a.write_opt_to::(a.code, writer), 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains the PcapNg blocks. 2 | 3 | pub(crate) mod block_common; 4 | pub mod enhanced_packet; 5 | pub mod interface_description; 6 | pub mod interface_statistics; 7 | pub mod name_resolution; 8 | pub mod opt_common; 9 | pub mod packet; 10 | pub mod section_header; 11 | pub mod simple_packet; 12 | pub mod systemd_journal_export; 13 | pub mod unknown; 14 | 15 | pub use block_common::*; 16 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/opt_common.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::io::{Result as IoResult, Write}; 3 | 4 | use byteorder_slice::byteorder::WriteBytesExt; 5 | use byteorder_slice::result::ReadSlice; 6 | use byteorder_slice::ByteOrder; 7 | use derive_into_owned::IntoOwned; 8 | 9 | use crate::errors::PcapError; 10 | 11 | 12 | /// Common fonctions of the PcapNg options 13 | pub(crate) trait PcapNgOption<'a> { 14 | /// Parse an option from a slice 15 | fn from_slice(code: u16, length: u16, slice: &'a [u8]) -> Result 16 | where 17 | Self: std::marker::Sized; 18 | 19 | /// Parse all options in a block 20 | fn opts_from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Vec), PcapError> 21 | where 22 | Self: std::marker::Sized, 23 | { 24 | let mut options = vec![]; 25 | 26 | // If there is nothing left in the slice, it means that there is no option 27 | if slice.is_empty() { 28 | return Ok((slice, options)); 29 | } 30 | 31 | while !slice.is_empty() { 32 | if slice.len() < 4 { 33 | return Err(PcapError::InvalidField("Option: slice.len() < 4")); 34 | } 35 | 36 | let code = slice.read_u16::().unwrap(); 37 | let length = slice.read_u16::().unwrap() as usize; 38 | let pad_len = (4 - (length % 4)) % 4; 39 | 40 | if code == 0 { 41 | return Ok((slice, options)); 42 | } 43 | 44 | if slice.len() < length + pad_len { 45 | return Err(PcapError::InvalidField("Option: length + pad.len() > slice.len()")); 46 | } 47 | 48 | let tmp_slice = &slice[..length]; 49 | let opt = Self::from_slice::(code, length as u16, tmp_slice)?; 50 | 51 | // Jump over the padding 52 | slice = &slice[length + pad_len..]; 53 | 54 | options.push(opt); 55 | } 56 | 57 | Err(PcapError::InvalidField("Invalid option")) 58 | } 59 | 60 | /// Write the option to a writer 61 | fn write_to(&self, writer: &mut W) -> IoResult; 62 | 63 | /// Write all options in a block 64 | fn write_opts_to(opts: &[Self], writer: &mut W) -> IoResult 65 | where 66 | Self: std::marker::Sized, 67 | { 68 | let mut have_opt = false; 69 | let mut written = 0; 70 | for opt in opts { 71 | written += opt.write_to::(writer)?; 72 | have_opt = true; 73 | } 74 | 75 | if have_opt { 76 | writer.write_u16::(0)?; 77 | writer.write_u16::(0)?; 78 | written += 4; 79 | } 80 | 81 | Ok(written) 82 | } 83 | } 84 | 85 | /// Unknown options 86 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 87 | pub struct UnknownOption<'a> { 88 | /// Option code 89 | pub code: u16, 90 | /// Option length 91 | pub length: u16, 92 | /// Option value 93 | pub value: Cow<'a, [u8]>, 94 | } 95 | 96 | impl<'a> UnknownOption<'a> { 97 | /// Creates a new [`UnknownOption`] 98 | pub fn new(code: u16, length: u16, value: &'a [u8]) -> Self { 99 | UnknownOption { code, length, value: Cow::Borrowed(value) } 100 | } 101 | } 102 | 103 | /// Custom binary option 104 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 105 | pub struct CustomBinaryOption<'a> { 106 | /// Option code 107 | pub code: u16, 108 | /// Option PEN identifier 109 | pub pen: u32, 110 | /// Option value 111 | pub value: Cow<'a, [u8]>, 112 | } 113 | 114 | impl<'a> CustomBinaryOption<'a> { 115 | /// Parse an [`CustomBinaryOption`] from a slice 116 | pub fn from_slice(code: u16, mut src: &'a [u8]) -> Result { 117 | let pen = src.read_u32::().map_err(|_| PcapError::IncompleteBuffer)?; 118 | let opt = CustomBinaryOption { code, pen, value: Cow::Borrowed(src) }; 119 | Ok(opt) 120 | } 121 | } 122 | 123 | /// Custom string (UTF-8) option 124 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 125 | pub struct CustomUtf8Option<'a> { 126 | /// Option code 127 | pub code: u16, 128 | /// Option PEN identifier 129 | pub pen: u32, 130 | /// Option value 131 | pub value: Cow<'a, str>, 132 | } 133 | 134 | impl<'a> CustomUtf8Option<'a> { 135 | /// Parse a [`CustomUtf8Option`] from a slice 136 | pub fn from_slice(code: u16, mut src: &'a [u8]) -> Result { 137 | let pen = src.read_u32::().map_err(|_| PcapError::IncompleteBuffer)?; 138 | let opt = CustomUtf8Option { code, pen, value: Cow::Borrowed(std::str::from_utf8(src)?) }; 139 | Ok(opt) 140 | } 141 | } 142 | 143 | pub(crate) trait WriteOptTo { 144 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult; 145 | } 146 | 147 | impl<'a> WriteOptTo for Cow<'a, [u8]> { 148 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 149 | let len = self.len(); 150 | let pad_len = (4 - len % 4) % 4; 151 | 152 | writer.write_u16::(code)?; 153 | writer.write_u16::(len as u16)?; 154 | writer.write_all(self)?; 155 | writer.write_all(&[0_u8; 3][..pad_len])?; 156 | 157 | Ok(len + pad_len + 4) 158 | } 159 | } 160 | 161 | impl<'a> WriteOptTo for Cow<'a, str> { 162 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 163 | let len = self.as_bytes().len(); 164 | let pad_len = (4 - len % 4) % 4; 165 | 166 | writer.write_u16::(code)?; 167 | writer.write_u16::(len as u16)?; 168 | writer.write_all(self.as_bytes())?; 169 | writer.write_all(&[0_u8; 3][..pad_len])?; 170 | 171 | Ok(len + pad_len + 4) 172 | } 173 | } 174 | 175 | impl WriteOptTo for u8 { 176 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 177 | writer.write_u16::(code)?; 178 | writer.write_u16::(1)?; 179 | writer.write_u8(*self)?; 180 | writer.write_all(&[0_u8; 3])?; 181 | 182 | Ok(8) 183 | } 184 | } 185 | 186 | impl WriteOptTo for u16 { 187 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 188 | writer.write_u16::(code)?; 189 | writer.write_u16::(2)?; 190 | writer.write_u16::(*self)?; 191 | writer.write_all(&[0_u8; 2])?; 192 | 193 | Ok(8) 194 | } 195 | } 196 | 197 | impl WriteOptTo for u32 { 198 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 199 | writer.write_u16::(code)?; 200 | writer.write_u16::(4)?; 201 | writer.write_u32::(*self)?; 202 | 203 | Ok(8) 204 | } 205 | } 206 | 207 | impl WriteOptTo for u64 { 208 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 209 | writer.write_u16::(code)?; 210 | writer.write_u16::(8)?; 211 | writer.write_u64::(*self)?; 212 | 213 | Ok(12) 214 | } 215 | } 216 | 217 | impl<'a> WriteOptTo for CustomBinaryOption<'a> { 218 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 219 | let len = &self.value.len() + 4; 220 | let pad_len = (4 - len % 4) % 4; 221 | 222 | writer.write_u16::(code)?; 223 | writer.write_u16::(len as u16)?; 224 | writer.write_u32::(self.pen)?; 225 | writer.write_all(&self.value)?; 226 | writer.write_all(&[0_u8; 3][..pad_len])?; 227 | 228 | Ok(len + pad_len + 4) 229 | } 230 | } 231 | 232 | impl<'a> WriteOptTo for CustomUtf8Option<'a> { 233 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 234 | let len = &self.value.len() + 4; 235 | let pad_len = (4 - len % 4) % 4; 236 | 237 | writer.write_u16::(code)?; 238 | writer.write_u16::(len as u16)?; 239 | writer.write_u32::(self.pen)?; 240 | writer.write_all(self.value.as_bytes())?; 241 | writer.write_all(&[0_u8; 3][..pad_len])?; 242 | 243 | Ok(len + pad_len + 4) 244 | } 245 | } 246 | 247 | impl<'a> WriteOptTo for UnknownOption<'a> { 248 | fn write_opt_to(&self, code: u16, writer: &mut W) -> IoResult { 249 | let len = self.value.len(); 250 | let pad_len = (4 - len % 4) % 4; 251 | 252 | writer.write_u16::(code)?; 253 | writer.write_u16::(len as u16)?; 254 | writer.write_all(&self.value)?; 255 | writer.write_all(&[0_u8; 3][..pad_len])?; 256 | 257 | Ok(len + pad_len + 4) 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/packet.rs: -------------------------------------------------------------------------------- 1 | //! Packet Block. 2 | 3 | use std::borrow::Cow; 4 | use std::io::{Result as IoResult, Write}; 5 | 6 | use byteorder_slice::byteorder::WriteBytesExt; 7 | use byteorder_slice::result::ReadSlice; 8 | use byteorder_slice::ByteOrder; 9 | use derive_into_owned::IntoOwned; 10 | 11 | use super::block_common::{Block, PcapNgBlock}; 12 | use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; 13 | use crate::errors::PcapError; 14 | 15 | /// The Packet Block is obsolete, and MUST NOT be used in new files. 16 | /// Use the Enhanced Packet Block or Simple Packet Block instead. 17 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 18 | pub struct PacketBlock<'a> { 19 | /// It specifies the interface this packet comes from. 20 | pub interface_id: u16, 21 | 22 | /// Local drop counter. 23 | /// 24 | /// It specifies the number of packets lost (by the interface and the operating system) 25 | /// between this packet and the preceding one. 26 | pub drop_count: u16, 27 | 28 | /// The timestamp is a single 64-bit unsigned integer that represents the number of units of time 29 | /// that have elapsed since 1970-01-01 00:00:00 UTC. 30 | pub timestamp: u64, 31 | 32 | /// Number of octets captured from the packet (i.e. the length of the Packet Data field). 33 | pub captured_len: u32, 34 | 35 | /// Actual length of the packet when it was transmitted on the network. 36 | pub original_len: u32, 37 | 38 | /// The data coming from the network, including link-layer headers. 39 | pub data: Cow<'a, [u8]>, 40 | 41 | /// Options 42 | pub options: Vec>, 43 | } 44 | 45 | impl<'a> PcapNgBlock<'a> for PacketBlock<'a> { 46 | fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { 47 | if slice.len() < 20 { 48 | return Err(PcapError::InvalidField("EnhancedPacketBlock: block length length < 20")); 49 | } 50 | 51 | let interface_id = slice.read_u16::().unwrap(); 52 | let drop_count = slice.read_u16::().unwrap(); 53 | let timestamp = slice.read_u64::().unwrap(); 54 | let captured_len = slice.read_u32::().unwrap(); 55 | let original_len = slice.read_u32::().unwrap(); 56 | 57 | let pad_len = (4 - (captured_len as usize % 4)) % 4; 58 | let tot_len = captured_len as usize + pad_len; 59 | 60 | if slice.len() < tot_len { 61 | return Err(PcapError::InvalidField("EnhancedPacketBlock: captured_len + padding > block length")); 62 | } 63 | 64 | let data = &slice[..captured_len as usize]; 65 | slice = &slice[tot_len..]; 66 | 67 | let (slice, options) = PacketOption::opts_from_slice::(slice)?; 68 | let block = PacketBlock { 69 | interface_id, 70 | drop_count, 71 | timestamp, 72 | captured_len, 73 | original_len, 74 | data: Cow::Borrowed(data), 75 | options, 76 | }; 77 | 78 | Ok((slice, block)) 79 | } 80 | 81 | fn write_to(&self, writer: &mut W) -> IoResult { 82 | writer.write_u16::(self.interface_id)?; 83 | writer.write_u16::(self.drop_count)?; 84 | writer.write_u64::(self.timestamp)?; 85 | writer.write_u32::(self.captured_len)?; 86 | writer.write_u32::(self.original_len)?; 87 | writer.write_all(&self.data)?; 88 | 89 | let pad_len = (4 - (self.captured_len as usize % 4)) % 4; 90 | writer.write_all(&[0_u8; 3][..pad_len])?; 91 | 92 | let opt_len = PacketOption::write_opts_to::(&self.options, writer)?; 93 | 94 | Ok(20 + self.data.len() + pad_len + opt_len) 95 | } 96 | 97 | fn into_block(self) -> Block<'a> { 98 | Block::Packet(self) 99 | } 100 | } 101 | 102 | /// Packet Block option 103 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 104 | pub enum PacketOption<'a> { 105 | /// Comment associated with the current block 106 | Comment(Cow<'a, str>), 107 | 108 | /// 32-bit flags word containing link-layer information. 109 | Flags(u32), 110 | 111 | /// Contains a hash of the packet. 112 | Hash(Cow<'a, [u8]>), 113 | 114 | /// Custom option containing binary octets in the Custom Data portion 115 | CustomBinary(CustomBinaryOption<'a>), 116 | 117 | /// Custom option containing a UTF-8 string in the Custom Data portion 118 | CustomUtf8(CustomUtf8Option<'a>), 119 | 120 | /// Unknown option 121 | Unknown(UnknownOption<'a>), 122 | } 123 | 124 | impl<'a> PcapNgOption<'a> for PacketOption<'a> { 125 | fn from_slice(code: u16, length: u16, mut slice: &'a [u8]) -> Result { 126 | let opt = match code { 127 | 1 => PacketOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 128 | 2 => { 129 | if slice.len() != 4 { 130 | return Err(PcapError::InvalidField("PacketOption: Flags length != 4")); 131 | } 132 | PacketOption::Flags(slice.read_u32::().map_err(|_| PcapError::IncompleteBuffer)?) 133 | }, 134 | 3 => PacketOption::Hash(Cow::Borrowed(slice)), 135 | 136 | 2988 | 19372 => PacketOption::CustomUtf8(CustomUtf8Option::from_slice::(code, slice)?), 137 | 2989 | 19373 => PacketOption::CustomBinary(CustomBinaryOption::from_slice::(code, slice)?), 138 | 139 | _ => PacketOption::Unknown(UnknownOption::new(code, length, slice)), 140 | }; 141 | 142 | Ok(opt) 143 | } 144 | 145 | fn write_to(&self, writer: &mut W) -> IoResult { 146 | match self { 147 | PacketOption::Comment(a) => a.write_opt_to::(1, writer), 148 | PacketOption::Flags(a) => a.write_opt_to::(2, writer), 149 | PacketOption::Hash(a) => a.write_opt_to::(3, writer), 150 | PacketOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), 151 | PacketOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), 152 | PacketOption::Unknown(a) => a.write_opt_to::(a.code, writer), 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/section_header.rs: -------------------------------------------------------------------------------- 1 | //! Section Header Block. 2 | 3 | use std::borrow::Cow; 4 | use std::io::{Result as IoResult, Write}; 5 | 6 | use byteorder_slice::byteorder::WriteBytesExt; 7 | use byteorder_slice::result::ReadSlice; 8 | use byteorder_slice::{BigEndian, ByteOrder, LittleEndian}; 9 | use derive_into_owned::IntoOwned; 10 | 11 | use super::block_common::{Block, PcapNgBlock}; 12 | use super::opt_common::{CustomBinaryOption, CustomUtf8Option, PcapNgOption, UnknownOption, WriteOptTo}; 13 | use crate::errors::PcapError; 14 | use crate::Endianness; 15 | 16 | 17 | /// Section Header Block: it defines the most important characteristics of the capture file. 18 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 19 | pub struct SectionHeaderBlock<'a> { 20 | /// Endianness of the section. 21 | pub endianness: Endianness, 22 | 23 | /// Major version of the format. 24 | /// Current value is 1. 25 | pub major_version: u16, 26 | 27 | /// Minor version of the format. 28 | /// Current value is 0. 29 | pub minor_version: u16, 30 | 31 | /// Length in bytes of the following section excluding this block. 32 | /// 33 | /// This block can be used to skip the section for faster navigation in 34 | /// large files. Length of -1i64 means that the length is unspecified. 35 | pub section_length: i64, 36 | 37 | /// Options 38 | pub options: Vec>, 39 | } 40 | 41 | impl<'a> PcapNgBlock<'a> for SectionHeaderBlock<'a> { 42 | fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { 43 | if slice.len() < 16 { 44 | return Err(PcapError::InvalidField("SectionHeaderBlock: block length < 16")); 45 | } 46 | 47 | let magic = slice.read_u32::().unwrap(); 48 | let endianness = match magic { 49 | 0x1A2B3C4D => Endianness::Big, 50 | 0x4D3C2B1A => Endianness::Little, 51 | _ => return Err(PcapError::InvalidField("SectionHeaderBlock: invalid magic number")), 52 | }; 53 | 54 | let (rem, major_version, minor_version, section_length, options) = match endianness { 55 | Endianness::Big => parse_inner::(slice)?, 56 | Endianness::Little => parse_inner::(slice)?, 57 | }; 58 | 59 | let block = SectionHeaderBlock { endianness, major_version, minor_version, section_length, options }; 60 | 61 | return Ok((rem, block)); 62 | 63 | #[allow(clippy::type_complexity)] 64 | fn parse_inner(mut slice: &[u8]) -> Result<(&[u8], u16, u16, i64, Vec), PcapError> { 65 | let maj_ver = slice.read_u16::().unwrap(); 66 | let min_ver = slice.read_u16::().unwrap(); 67 | let sec_len = slice.read_i64::().unwrap(); 68 | let (rem, opts) = SectionHeaderOption::opts_from_slice::(slice)?; 69 | 70 | Ok((rem, maj_ver, min_ver, sec_len, opts)) 71 | } 72 | } 73 | 74 | fn write_to(&self, writer: &mut W) -> IoResult { 75 | match self.endianness { 76 | Endianness::Big => writer.write_u32::(0x1A2B3C4D)?, 77 | Endianness::Little => writer.write_u32::(0x1A2B3C4D)?, 78 | }; 79 | 80 | writer.write_u16::(self.major_version)?; 81 | writer.write_u16::(self.minor_version)?; 82 | writer.write_i64::(self.section_length)?; 83 | 84 | let opt_len = SectionHeaderOption::write_opts_to::(&self.options, writer)?; 85 | 86 | Ok(16 + opt_len) 87 | } 88 | 89 | fn into_block(self) -> Block<'a> { 90 | Block::SectionHeader(self) 91 | } 92 | } 93 | 94 | impl Default for SectionHeaderBlock<'static> { 95 | fn default() -> Self { 96 | Self { 97 | endianness: Endianness::Big, 98 | major_version: 1, 99 | minor_version: 0, 100 | section_length: -1, 101 | options: vec![], 102 | } 103 | } 104 | } 105 | 106 | 107 | /// Section Header Block options 108 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 109 | pub enum SectionHeaderOption<'a> { 110 | /// Comment associated with the current block 111 | Comment(Cow<'a, str>), 112 | 113 | /// Description of the hardware used to create this section 114 | Hardware(Cow<'a, str>), 115 | 116 | /// Name of the operating system used to create this section 117 | OS(Cow<'a, str>), 118 | 119 | /// Name of the application used to create this section 120 | UserApplication(Cow<'a, str>), 121 | 122 | /// Custom option containing binary octets in the Custom Data portion 123 | CustomBinary(CustomBinaryOption<'a>), 124 | 125 | /// Custom option containing a UTF-8 string in the Custom Data portion 126 | CustomUtf8(CustomUtf8Option<'a>), 127 | 128 | /// Unknown option 129 | Unknown(UnknownOption<'a>), 130 | } 131 | 132 | impl<'a> PcapNgOption<'a> for SectionHeaderOption<'a> { 133 | fn from_slice(code: u16, length: u16, slice: &'a [u8]) -> Result { 134 | let opt = match code { 135 | 1 => SectionHeaderOption::Comment(Cow::Borrowed(std::str::from_utf8(slice)?)), 136 | 2 => SectionHeaderOption::Hardware(Cow::Borrowed(std::str::from_utf8(slice)?)), 137 | 3 => SectionHeaderOption::OS(Cow::Borrowed(std::str::from_utf8(slice)?)), 138 | 4 => SectionHeaderOption::UserApplication(Cow::Borrowed(std::str::from_utf8(slice)?)), 139 | 140 | 2988 | 19372 => SectionHeaderOption::CustomUtf8(CustomUtf8Option::from_slice::(code, slice)?), 141 | 2989 | 19373 => SectionHeaderOption::CustomBinary(CustomBinaryOption::from_slice::(code, slice)?), 142 | 143 | _ => SectionHeaderOption::Unknown(UnknownOption::new(code, length, slice)), 144 | }; 145 | 146 | Ok(opt) 147 | } 148 | 149 | fn write_to(&self, writer: &mut W) -> IoResult { 150 | match self { 151 | SectionHeaderOption::Comment(a) => a.write_opt_to::(1, writer), 152 | SectionHeaderOption::Hardware(a) => a.write_opt_to::(2, writer), 153 | SectionHeaderOption::OS(a) => a.write_opt_to::(3, writer), 154 | SectionHeaderOption::UserApplication(a) => a.write_opt_to::(4, writer), 155 | SectionHeaderOption::CustomBinary(a) => a.write_opt_to::(a.code, writer), 156 | SectionHeaderOption::CustomUtf8(a) => a.write_opt_to::(a.code, writer), 157 | SectionHeaderOption::Unknown(a) => a.write_opt_to::(a.code, writer), 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/simple_packet.rs: -------------------------------------------------------------------------------- 1 | //! Simple Packet Block (SPB). 2 | 3 | use std::borrow::Cow; 4 | use std::io::{Result as IoResult, Write}; 5 | 6 | use byteorder_slice::byteorder::WriteBytesExt; 7 | use byteorder_slice::result::ReadSlice; 8 | use byteorder_slice::ByteOrder; 9 | use derive_into_owned::IntoOwned; 10 | 11 | use super::block_common::{Block, PcapNgBlock}; 12 | use crate::errors::PcapError; 13 | 14 | 15 | /// The Simple Packet Block (SPB) is a lightweight container for storing the packets coming from the network. 16 | /// 17 | /// Its presence is optional. 18 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 19 | pub struct SimplePacketBlock<'a> { 20 | /// Actual length of the packet when it was transmitted on the network. 21 | pub original_len: u32, 22 | 23 | /// The data coming from the network, including link-layer headers. 24 | pub data: Cow<'a, [u8]>, 25 | } 26 | 27 | impl<'a> PcapNgBlock<'a> for SimplePacketBlock<'a> { 28 | fn from_slice(mut slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { 29 | if slice.len() < 4 { 30 | return Err(PcapError::InvalidField("SimplePacketBlock: block length < 4")); 31 | } 32 | let original_len = slice.read_u32::().unwrap(); 33 | 34 | let packet = SimplePacketBlock { original_len, data: Cow::Borrowed(slice) }; 35 | 36 | Ok((&[], packet)) 37 | } 38 | 39 | fn write_to(&self, writer: &mut W) -> IoResult { 40 | writer.write_u32::(self.original_len)?; 41 | writer.write_all(&self.data)?; 42 | 43 | let pad_len = (4 - (self.data.len() % 4)) % 4; 44 | writer.write_all(&[0_u8; 3][..pad_len])?; 45 | 46 | Ok(4 + self.data.len() + pad_len) 47 | } 48 | 49 | fn into_block(self) -> Block<'a> { 50 | Block::SimplePacket(self) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/systemd_journal_export.rs: -------------------------------------------------------------------------------- 1 | //! Systemd Journal Export Block. 2 | 3 | use std::borrow::Cow; 4 | use std::io::{Result as IoResult, Write}; 5 | 6 | use byteorder_slice::ByteOrder; 7 | use derive_into_owned::IntoOwned; 8 | 9 | use super::block_common::{Block, PcapNgBlock}; 10 | use crate::errors::PcapError; 11 | 12 | 13 | /// The Systemd Journal Export Block is a lightweight containter for systemd Journal Export Format entry data. 14 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 15 | pub struct SystemdJournalExportBlock<'a> { 16 | /// A journal entry as described in the Journal Export Format documentation. 17 | pub journal_entry: Cow<'a, [u8]>, 18 | } 19 | 20 | impl<'a> PcapNgBlock<'a> for SystemdJournalExportBlock<'a> { 21 | fn from_slice(slice: &'a [u8]) -> Result<(&'a [u8], Self), PcapError> { 22 | let packet = SystemdJournalExportBlock { journal_entry: Cow::Borrowed(slice) }; 23 | Ok((&[], packet)) 24 | } 25 | 26 | fn write_to(&self, writer: &mut W) -> IoResult { 27 | writer.write_all(&self.journal_entry)?; 28 | 29 | let pad_len = (4 - (self.journal_entry.len() % 4)) % 4; 30 | writer.write_all(&[0_u8; 3][..pad_len])?; 31 | 32 | Ok(self.journal_entry.len() + pad_len) 33 | } 34 | 35 | fn into_block(self) -> Block<'a> { 36 | Block::SystemdJournalExport(self) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/blocks/unknown.rs: -------------------------------------------------------------------------------- 1 | //! Unknown Block. 2 | 3 | use std::borrow::Cow; 4 | use std::io::{Result as IoResult, Write}; 5 | 6 | use byteorder_slice::ByteOrder; 7 | use derive_into_owned::IntoOwned; 8 | 9 | use super::block_common::{Block, PcapNgBlock}; 10 | use crate::PcapError; 11 | 12 | 13 | /// Unknown block 14 | #[derive(Clone, Debug, IntoOwned, Eq, PartialEq)] 15 | pub struct UnknownBlock<'a> { 16 | /// Block type 17 | pub type_: u32, 18 | /// Block length 19 | pub length: u32, 20 | /// Block value 21 | pub value: Cow<'a, [u8]>, 22 | } 23 | 24 | impl<'a> UnknownBlock<'a> { 25 | /// Creates a new [`UnknownBlock`] 26 | pub fn new(type_: u32, length: u32, value: &'a [u8]) -> Self { 27 | UnknownBlock { type_, length, value: Cow::Borrowed(value) } 28 | } 29 | } 30 | 31 | impl<'a> PcapNgBlock<'a> for UnknownBlock<'a> { 32 | fn from_slice(_slice: &'a [u8]) -> Result<(&[u8], Self), PcapError> 33 | where 34 | Self: Sized, 35 | { 36 | unimplemented!("UnkknownBlock::::From_slice shouldn't be called") 37 | } 38 | 39 | fn write_to(&self, writer: &mut W) -> IoResult { 40 | writer.write_all(&self.value)?; 41 | Ok(self.value.len()) 42 | } 43 | 44 | fn into_block(self) -> Block<'a> { 45 | Block::Unknown(self) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains the PcapNg parser, reader and writer 2 | 3 | pub mod blocks; 4 | pub use blocks::{Block, PcapNgBlock, RawBlock}; 5 | 6 | pub(crate) mod parser; 7 | pub use parser::*; 8 | 9 | pub(crate) mod reader; 10 | pub use reader::*; 11 | 12 | pub(crate) mod writer; 13 | pub use writer::*; 14 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/parser.rs: -------------------------------------------------------------------------------- 1 | use byteorder_slice::{BigEndian, ByteOrder, LittleEndian}; 2 | 3 | use super::blocks::block_common::{Block, RawBlock}; 4 | use super::blocks::enhanced_packet::EnhancedPacketBlock; 5 | use super::blocks::interface_description::InterfaceDescriptionBlock; 6 | use super::blocks::section_header::SectionHeaderBlock; 7 | use super::blocks::{INTERFACE_DESCRIPTION_BLOCK, SECTION_HEADER_BLOCK}; 8 | use crate::errors::PcapError; 9 | use crate::Endianness; 10 | 11 | 12 | /// Parses a PcapNg from a slice of bytes. 13 | /// 14 | /// You can match on [`PcapError::IncompleteBuffer`] to know if the parser need more data. 15 | /// 16 | /// # Example 17 | /// ```rust,no_run 18 | /// use std::fs::File; 19 | /// 20 | /// use pcap_file::pcapng::PcapNgParser; 21 | /// use pcap_file::PcapError; 22 | /// 23 | /// let pcap = std::fs::read("test.pcapng").expect("Error reading file"); 24 | /// let mut src = &pcap[..]; 25 | /// 26 | /// let (rem, mut pcapng_parser) = PcapNgParser::new(src).unwrap(); 27 | /// src = rem; 28 | /// 29 | /// loop { 30 | /// match pcapng_parser.next_block(src) { 31 | /// Ok((rem, block)) => { 32 | /// // Do something 33 | /// 34 | /// // Don't forget to update src 35 | /// src = rem; 36 | /// }, 37 | /// Err(PcapError::IncompleteBuffer) => { 38 | /// // Load more data into src 39 | /// }, 40 | /// Err(_) => { 41 | /// // Handle parsing error 42 | /// }, 43 | /// } 44 | /// } 45 | /// ``` 46 | pub struct PcapNgParser { 47 | section: SectionHeaderBlock<'static>, 48 | interfaces: Vec>, 49 | } 50 | 51 | impl PcapNgParser { 52 | /// Creates a new [`PcapNgParser`]. 53 | /// 54 | /// Parses the first block which must be a valid SectionHeaderBlock. 55 | pub fn new(src: &[u8]) -> Result<(&[u8], Self), PcapError> { 56 | // Always use BigEndian here because we can't know the SectionHeaderBlock endianness 57 | let (rem, section) = Block::from_slice::(src)?; 58 | let section = match section { 59 | Block::SectionHeader(section) => section.into_owned(), 60 | _ => return Err(PcapError::InvalidField("PcapNg: SectionHeader invalid or missing")), 61 | }; 62 | 63 | let parser = PcapNgParser { section, interfaces: vec![] }; 64 | 65 | Ok((rem, parser)) 66 | } 67 | 68 | /// Returns the remainder and the next [`Block`]. 69 | pub fn next_block<'a>(&mut self, src: &'a [u8]) -> Result<(&'a [u8], Block<'a>), PcapError> { 70 | // Read next Block 71 | match self.section.endianness { 72 | Endianness::Big => { 73 | let (rem, raw_block) = self.next_raw_block_inner::(src)?; 74 | let block = raw_block.try_into_block::()?; 75 | Ok((rem, block)) 76 | }, 77 | Endianness::Little => { 78 | let (rem, raw_block) = self.next_raw_block_inner::(src)?; 79 | let block = raw_block.try_into_block::()?; 80 | Ok((rem, block)) 81 | }, 82 | } 83 | } 84 | 85 | /// Returns the remainder and the next [`RawBlock`]. 86 | pub fn next_raw_block<'a>(&mut self, src: &'a [u8]) -> Result<(&'a [u8], RawBlock<'a>), PcapError> { 87 | // Read next Block 88 | match self.section.endianness { 89 | Endianness::Big => self.next_raw_block_inner::(src), 90 | Endianness::Little => self.next_raw_block_inner::(src), 91 | } 92 | } 93 | 94 | /// Inner function to parse the next raw block. 95 | fn next_raw_block_inner<'a, B: ByteOrder>(&mut self, src: &'a [u8]) -> Result<(&'a [u8], RawBlock<'a>), PcapError> { 96 | let (rem, raw_block) = RawBlock::from_slice::(src)?; 97 | 98 | match raw_block.type_ { 99 | SECTION_HEADER_BLOCK => { 100 | self.section = raw_block.clone().try_into_block::()?.into_owned().into_section_header().unwrap(); 101 | self.interfaces.clear(); 102 | }, 103 | INTERFACE_DESCRIPTION_BLOCK => { 104 | let interface = raw_block.clone().try_into_block::()?.into_owned().into_interface_description().unwrap(); 105 | self.interfaces.push(interface); 106 | }, 107 | _ => {}, 108 | } 109 | 110 | Ok((rem, raw_block)) 111 | } 112 | 113 | /// Returns the current [`SectionHeaderBlock`]. 114 | pub fn section(&self) -> &SectionHeaderBlock<'static> { 115 | &self.section 116 | } 117 | 118 | /// Returns all the current [`InterfaceDescriptionBlock`]. 119 | pub fn interfaces(&self) -> &[InterfaceDescriptionBlock<'static>] { 120 | &self.interfaces[..] 121 | } 122 | 123 | /// Returns the [`InterfaceDescriptionBlock`] corresponding to the given packet. 124 | pub fn packet_interface(&self, packet: &EnhancedPacketBlock) -> Option<&InterfaceDescriptionBlock> { 125 | self.interfaces.get(packet.interface_id as usize) 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/reader.rs: -------------------------------------------------------------------------------- 1 | use std::io::Read; 2 | 3 | use super::blocks::block_common::{Block, RawBlock}; 4 | use super::blocks::enhanced_packet::EnhancedPacketBlock; 5 | use super::blocks::interface_description::InterfaceDescriptionBlock; 6 | use super::blocks::section_header::SectionHeaderBlock; 7 | use super::PcapNgParser; 8 | use crate::errors::PcapError; 9 | use crate::read_buffer::ReadBuffer; 10 | 11 | 12 | /// Reads a PcapNg from a reader. 13 | /// 14 | /// # Example 15 | /// ```rust,no_run 16 | /// use std::fs::File; 17 | /// 18 | /// use pcap_file::pcapng::PcapNgReader; 19 | /// 20 | /// let file_in = File::open("test.pcapng").expect("Error opening file"); 21 | /// let mut pcapng_reader = PcapNgReader::new(file_in).unwrap(); 22 | /// 23 | /// // Read test.pcapng 24 | /// while let Some(block) = pcapng_reader.next_block() { 25 | /// //Check if there is no error 26 | /// let block = block.unwrap(); 27 | /// 28 | /// //Do something 29 | /// } 30 | /// ``` 31 | pub struct PcapNgReader { 32 | parser: PcapNgParser, 33 | reader: ReadBuffer, 34 | } 35 | 36 | impl PcapNgReader { 37 | /// Creates a new [`PcapNgReader`] from a reader. 38 | /// 39 | /// Parses the first block which must be a valid SectionHeaderBlock. 40 | pub fn new(reader: R) -> Result, PcapError> { 41 | let mut reader = ReadBuffer::new(reader); 42 | let parser = reader.parse_with(PcapNgParser::new)?; 43 | Ok(Self { parser, reader }) 44 | } 45 | 46 | /// Returns the next [`Block`]. 47 | pub fn next_block(&mut self) -> Option> { 48 | match self.reader.has_data_left() { 49 | Ok(has_data) => { 50 | if has_data { 51 | Some(self.reader.parse_with(|src| self.parser.next_block(src))) 52 | } 53 | else { 54 | None 55 | } 56 | }, 57 | Err(e) => Some(Err(PcapError::IoError(e))), 58 | } 59 | } 60 | 61 | /// Returns the next [`RawBlock`]. 62 | pub fn next_raw_block(&mut self) -> Option> { 63 | match self.reader.has_data_left() { 64 | Ok(has_data) => { 65 | if has_data { 66 | Some(self.reader.parse_with(|src| self.parser.next_raw_block(src))) 67 | } 68 | else { 69 | None 70 | } 71 | }, 72 | Err(e) => Some(Err(PcapError::IoError(e))), 73 | } 74 | } 75 | 76 | /// Returns the current [`SectionHeaderBlock`]. 77 | pub fn section(&self) -> &SectionHeaderBlock<'static> { 78 | self.parser.section() 79 | } 80 | 81 | /// Returns all the current [`InterfaceDescriptionBlock`]. 82 | pub fn interfaces(&self) -> &[InterfaceDescriptionBlock<'static>] { 83 | self.parser.interfaces() 84 | } 85 | 86 | /// Returns the [`InterfaceDescriptionBlock`] corresponding to the given packet 87 | pub fn packet_interface(&self, packet: &EnhancedPacketBlock) -> Option<&InterfaceDescriptionBlock> { 88 | self.interfaces().get(packet.interface_id as usize) 89 | } 90 | 91 | /// Consumes the [`Self`], returning the wrapped reader. 92 | pub fn into_inner(self) -> R { 93 | self.reader.into_inner() 94 | } 95 | 96 | /// Gets a reference to the wrapped reader. 97 | pub fn get_ref(&self) -> &R { 98 | self.reader.get_ref() 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /libs/pcap-file/src/pcapng/writer.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use byteorder_slice::{BigEndian, ByteOrder, LittleEndian}; 4 | 5 | use super::blocks::block_common::{Block, PcapNgBlock}; 6 | use super::blocks::interface_description::InterfaceDescriptionBlock; 7 | use super::blocks::section_header::SectionHeaderBlock; 8 | use super::blocks::SECTION_HEADER_BLOCK; 9 | use super::RawBlock; 10 | use crate::{Endianness, PcapError, PcapResult}; 11 | 12 | 13 | /// Writes a PcapNg to a writer. 14 | /// 15 | /// # Examples 16 | /// ```rust,no_run 17 | /// use std::fs::File; 18 | /// 19 | /// use pcap_file::pcapng::{PcapNgReader, PcapNgWriter}; 20 | /// 21 | /// let file_in = File::open("test.pcapng").expect("Error opening file"); 22 | /// let mut pcapng_reader = PcapNgReader::new(file_in).unwrap(); 23 | /// 24 | /// let mut out = Vec::new(); 25 | /// let mut pcapng_writer = PcapNgWriter::new(out).unwrap(); 26 | /// 27 | /// // Read test.pcapng 28 | /// while let Some(block) = pcapng_reader.next_block() { 29 | /// // Check if there is no error 30 | /// let block = block.unwrap(); 31 | /// 32 | /// // Write back parsed Block 33 | /// pcapng_writer.write_block(&block).unwrap(); 34 | /// } 35 | /// ``` 36 | pub struct PcapNgWriter { 37 | section: SectionHeaderBlock<'static>, 38 | interfaces: Vec>, 39 | writer: W, 40 | } 41 | 42 | impl PcapNgWriter { 43 | /// Creates a new [`PcapNgWriter`] from an existing writer. 44 | /// 45 | /// Defaults to the native endianness of the CPU. 46 | /// 47 | /// Writes this global pcapng header to the file: 48 | /// ```rust, ignore 49 | /// Self { 50 | /// endianness: Endianness::Native, 51 | /// major_version: 1, 52 | /// minor_version: 0, 53 | /// section_length: -1, 54 | /// options: vec![] 55 | /// } 56 | /// ``` 57 | /// 58 | /// 59 | /// # Errors 60 | /// The writer can't be written to. 61 | pub fn new(writer: W) -> PcapResult { 62 | Self::with_endianness(writer, Endianness::native()) 63 | } 64 | 65 | /// Creates a new [`PcapNgWriter`] from an existing writer with the given endianness. 66 | pub fn with_endianness(writer: W, endianness: Endianness) -> PcapResult { 67 | let section = SectionHeaderBlock { endianness, ..Default::default() }; 68 | 69 | Self::with_section_header(writer, section) 70 | } 71 | 72 | /// Creates a new [`PcapNgWriter`] from an existing writer with the given section header. 73 | pub fn with_section_header(mut writer: W, section: SectionHeaderBlock<'static>) -> PcapResult { 74 | match section.endianness { 75 | Endianness::Big => section.clone().into_block().write_to::(&mut writer).map_err(PcapError::IoError)?, 76 | Endianness::Little => section.clone().into_block().write_to::(&mut writer).map_err(PcapError::IoError)?, 77 | }; 78 | 79 | Ok(Self { section, interfaces: vec![], writer }) 80 | } 81 | 82 | /// Writes a [`Block`]. 83 | /// 84 | /// # Example 85 | /// ```rust,no_run 86 | /// use std::borrow::Cow; 87 | /// use std::fs::File; 88 | /// use std::time::Duration; 89 | /// 90 | /// use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock; 91 | /// use pcap_file::pcapng::blocks::interface_description::InterfaceDescriptionBlock; 92 | /// use pcap_file::pcapng::{PcapNgBlock, PcapNgWriter}; 93 | /// use pcap_file::DataLink; 94 | /// 95 | /// let data = [0u8; 10]; 96 | /// 97 | /// let interface = InterfaceDescriptionBlock { 98 | /// linktype: DataLink::ETHERNET, 99 | /// snaplen: 0xFFFF, 100 | /// options: vec![], 101 | /// }; 102 | /// 103 | /// let packet = EnhancedPacketBlock { 104 | /// interface_id: 0, 105 | /// timestamp: Duration::from_secs(0), 106 | /// original_len: data.len() as u32, 107 | /// data: Cow::Borrowed(&data), 108 | /// options: vec![], 109 | /// }; 110 | /// 111 | /// let file = File::create("out.pcap").expect("Error creating file"); 112 | /// let mut pcap_ng_writer = PcapNgWriter::new(file).unwrap(); 113 | /// 114 | /// pcap_ng_writer.write_block(&interface.into_block()).unwrap(); 115 | /// pcap_ng_writer.write_block(&packet.into_block()).unwrap(); 116 | /// ``` 117 | pub fn write_block(&mut self, block: &Block) -> PcapResult { 118 | match block { 119 | Block::SectionHeader(a) => { 120 | self.section = a.clone().into_owned(); 121 | self.interfaces.clear(); 122 | }, 123 | Block::InterfaceDescription(a) => { 124 | self.interfaces.push(a.clone().into_owned()); 125 | }, 126 | Block::InterfaceStatistics(a) => { 127 | if a.interface_id as usize >= self.interfaces.len() { 128 | return Err(PcapError::InvalidInterfaceId(a.interface_id)); 129 | } 130 | }, 131 | Block::EnhancedPacket(a) => { 132 | if a.interface_id as usize >= self.interfaces.len() { 133 | return Err(PcapError::InvalidInterfaceId(a.interface_id)); 134 | } 135 | }, 136 | 137 | _ => (), 138 | } 139 | 140 | match self.section.endianness { 141 | Endianness::Big => block.write_to::(&mut self.writer).map_err(PcapError::IoError), 142 | Endianness::Little => block.write_to::(&mut self.writer).map_err(PcapError::IoError), 143 | } 144 | } 145 | 146 | /// Writes a [`PcapNgBlock`]. 147 | /// 148 | /// # Example 149 | /// ```rust,no_run 150 | /// use std::borrow::Cow; 151 | /// use std::fs::File; 152 | /// use std::time::Duration; 153 | /// 154 | /// use pcap_file::pcapng::blocks::enhanced_packet::EnhancedPacketBlock; 155 | /// use pcap_file::pcapng::blocks::interface_description::InterfaceDescriptionBlock; 156 | /// use pcap_file::pcapng::{PcapNgBlock, PcapNgWriter}; 157 | /// use pcap_file::DataLink; 158 | /// 159 | /// let data = [0u8; 10]; 160 | /// 161 | /// let interface = InterfaceDescriptionBlock { 162 | /// linktype: DataLink::ETHERNET, 163 | /// snaplen: 0xFFFF, 164 | /// options: vec![], 165 | /// }; 166 | /// 167 | /// let packet = EnhancedPacketBlock { 168 | /// interface_id: 0, 169 | /// timestamp: Duration::from_secs(0), 170 | /// original_len: data.len() as u32, 171 | /// data: Cow::Borrowed(&data), 172 | /// options: vec![], 173 | /// }; 174 | /// 175 | /// let file = File::create("out.pcap").expect("Error creating file"); 176 | /// let mut pcap_ng_writer = PcapNgWriter::new(file).unwrap(); 177 | /// 178 | /// pcap_ng_writer.write_pcapng_block(interface).unwrap(); 179 | /// pcap_ng_writer.write_pcapng_block(packet).unwrap(); 180 | /// ``` 181 | pub fn write_pcapng_block<'a, B: PcapNgBlock<'a>>(&mut self, block: B) -> PcapResult { 182 | self.write_block(&block.into_block()) 183 | } 184 | 185 | /// Writes a [`RawBlock`]. 186 | /// 187 | /// Doesn't check the validity of the written blocks. 188 | pub fn write_raw_block(&mut self, block: &RawBlock) -> PcapResult { 189 | return match self.section.endianness { 190 | Endianness::Big => inner::(&mut self.section, block, &mut self.writer), 191 | Endianness::Little => inner::(&mut self.section, block, &mut self.writer), 192 | }; 193 | 194 | fn inner(section: &mut SectionHeaderBlock, block: &RawBlock, writer: &mut W) -> PcapResult { 195 | if block.type_ == SECTION_HEADER_BLOCK { 196 | *section = block.clone().try_into_block::()?.into_owned().into_section_header().unwrap(); 197 | } 198 | 199 | block.write_to::(writer).map_err(PcapError::IoError) 200 | } 201 | } 202 | 203 | /// Consumes [`Self`], returning the wrapped writer. 204 | pub fn into_inner(self) -> W { 205 | self.writer 206 | } 207 | 208 | /// Gets a reference to the underlying writer. 209 | pub fn get_ref(&self) -> &W { 210 | &self.writer 211 | } 212 | 213 | /// Gets a mutable reference to the underlying writer. 214 | /// 215 | /// You should not be used unless you really know what you're doing 216 | pub fn get_mut(&mut self) -> &mut W { 217 | &mut self.writer 218 | } 219 | 220 | /// Returns the current [`SectionHeaderBlock`]. 221 | pub fn section(&self) -> &SectionHeaderBlock<'static> { 222 | &self.section 223 | } 224 | 225 | /// Returns all the current [`InterfaceDescriptionBlock`]. 226 | pub fn interfaces(&self) -> &[InterfaceDescriptionBlock<'static>] { 227 | &self.interfaces 228 | } 229 | } 230 | -------------------------------------------------------------------------------- /libs/pcap-file/src/read_buffer.rs: -------------------------------------------------------------------------------- 1 | use std::io::{Error, ErrorKind, Read}; 2 | 3 | use crate::PcapError; 4 | 5 | 6 | /// Internal structure that bufferize its input and allow to parse element from its buffer. 7 | #[derive(Debug)] 8 | pub(crate) struct ReadBuffer { 9 | /// Reader from which we read the data from 10 | reader: R, 11 | /// Internal buffer 12 | buffer: Vec, 13 | /// Current start position of the buffer 14 | pos: usize, 15 | /// Current end position of the buffer 16 | len: usize, 17 | } 18 | 19 | impl ReadBuffer { 20 | /// Creates a new ReadBuffer with capacity of 8_000_000 21 | pub fn new(reader: R) -> Self { 22 | Self::with_capacity(reader, 8_000_000) 23 | } 24 | 25 | /// Creates a new ReadBuffer with the given capacity 26 | pub fn with_capacity(reader: R, capacity: usize) -> Self { 27 | Self { reader, buffer: vec![0_u8; capacity], pos: 0, len: 0 } 28 | } 29 | 30 | /// Parse data from the internal buffer 31 | /// 32 | /// Safety 33 | /// 34 | /// The parser must NOT keep a reference to the buffer in input. 35 | pub fn parse_with<'a, 'b: 'a, 'c: 'a, F, O>(&'c mut self, mut parser: F) -> Result 36 | where 37 | F: FnMut(&'a [u8]) -> Result<(&'a [u8], O), PcapError>, 38 | F: 'b, 39 | O: 'a, 40 | { 41 | loop { 42 | let buf = &self.buffer[self.pos..self.len]; 43 | 44 | // Sound because 'b and 'c must outlive 'a so the buffer cannot be modified while someone has a ref on it 45 | let buf: &'a [u8] = unsafe { std::mem::transmute(buf) }; 46 | 47 | match parser(buf) { 48 | Ok((rem, value)) => { 49 | self.advance_with_slice(rem); 50 | return Ok(value); 51 | }, 52 | 53 | Err(PcapError::IncompleteBuffer) => { 54 | // The parsed data len should never be more than the buffer capacity 55 | if buf.len() == self.buffer.len() { 56 | return Err(PcapError::IoError(Error::from(ErrorKind::UnexpectedEof))); 57 | } 58 | 59 | let nb_read = self.fill_buf().map_err(PcapError::IoError)?; 60 | if nb_read == 0 { 61 | return Err(PcapError::IoError(Error::from(ErrorKind::UnexpectedEof))); 62 | } 63 | }, 64 | 65 | Err(e) => return Err(e), 66 | } 67 | } 68 | } 69 | 70 | /// Fill the inner buffer. 71 | /// Copy the remaining data inside buffer at its start and the fill the end part with data from the reader. 72 | fn fill_buf(&mut self) -> Result { 73 | // Copy the remaining data to the start of the buffer 74 | let rem_len = unsafe { 75 | let buf_ptr_mut = self.buffer.as_mut_ptr(); 76 | let rem_ptr_mut = buf_ptr_mut.add(self.pos); 77 | std::ptr::copy(rem_ptr_mut, buf_ptr_mut, self.len - self.pos); 78 | self.len - self.pos 79 | }; 80 | 81 | let nb_read = self.reader.read(&mut self.buffer[rem_len..])?; 82 | 83 | self.len = rem_len + nb_read; 84 | self.pos = 0; 85 | 86 | Ok(nb_read) 87 | } 88 | 89 | /// Advance the internal buffer position. 90 | fn advance(&mut self, nb_bytes: usize) { 91 | assert!(self.pos + nb_bytes <= self.len); 92 | self.pos += nb_bytes; 93 | } 94 | 95 | /// Advance the internal buffer position. 96 | fn advance_with_slice(&mut self, rem: &[u8]) { 97 | // Compute the length between the buffer and the slice 98 | let diff_len = (rem.as_ptr() as usize) 99 | .checked_sub(self.buffer().as_ptr() as usize) 100 | .expect("Rem is not a sub slice of self.buffer"); 101 | 102 | self.advance(diff_len) 103 | } 104 | 105 | /// Return the valid data of the internal buffer 106 | pub fn buffer(&self) -> &[u8] { 107 | &self.buffer[self.pos..self.len] 108 | } 109 | 110 | /// Return true there are some data that can be read 111 | pub fn has_data_left(&mut self) -> Result { 112 | // The buffer can be empty and the reader can still have data 113 | if self.buffer().is_empty() { 114 | let nb_read = self.fill_buf()?; 115 | if nb_read == 0 { 116 | return Ok(false); 117 | } 118 | } 119 | 120 | Ok(true) 121 | } 122 | 123 | /// Return the inner reader 124 | pub fn into_inner(self) -> R { 125 | self.reader 126 | } 127 | 128 | /// Return a reference over the inner reader 129 | pub fn get_ref(&self) -> &R { 130 | &self.reader 131 | } 132 | } 133 | 134 | #[cfg(test)] 135 | mod test { 136 | /* 137 | // Shouldn't compile 138 | #[test] 139 | fn parse_with_safety() { 140 | let a = &[0_u8; 10]; 141 | let b = &mut &a[..]; 142 | 143 | let input = vec![1_u8; 100]; 144 | let input_read = &mut &input[..]; 145 | let mut reader = super::ReadBuffer::new(input_read); 146 | 147 | unsafe { 148 | reader.parse_with(|buf| { 149 | *b = buf; 150 | Ok((buf, ())) 151 | }); 152 | } 153 | 154 | unsafe { 155 | reader.has_data_left(); 156 | } 157 | 158 | println!("{:?}", b); 159 | } 160 | */ 161 | } 162 | -------------------------------------------------------------------------------- /screenshots/AP1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/AP1.png -------------------------------------------------------------------------------- /screenshots/AP2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/AP2.png -------------------------------------------------------------------------------- /screenshots/AP3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/AP3.png -------------------------------------------------------------------------------- /screenshots/HS1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/HS1.png -------------------------------------------------------------------------------- /screenshots/STATIONS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/STATIONS.png -------------------------------------------------------------------------------- /screenshots/ap_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/ap_tab.png -------------------------------------------------------------------------------- /screenshots/attacaksummaries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/attacaksummaries.png -------------------------------------------------------------------------------- /screenshots/handshakes_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/handshakes_tab.png -------------------------------------------------------------------------------- /screenshots/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/logo.png -------------------------------------------------------------------------------- /screenshots/station_tab.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/station_tab.png -------------------------------------------------------------------------------- /screenshots/status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/status.png -------------------------------------------------------------------------------- /screenshots/wifi-hardware.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aluminum-ice/AngryOxide/9971ccd483cbac9341cc9518a4f96f7ffdce45fd/screenshots/wifi-hardware.png -------------------------------------------------------------------------------- /src/advancedtable/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod advtable; 2 | -------------------------------------------------------------------------------- /src/ascii.rs: -------------------------------------------------------------------------------- 1 | use rand::Rng; 2 | 3 | pub fn get_art(text: &str) -> String { 4 | let ascii_art = get_random_art(); 5 | let max_width = ascii_art.lines().map(|line| line.len()).max().unwrap_or(0); 6 | format!( 7 | "{}\n{:^width$} ", 8 | ascii_art, 9 | format!("========== {} ==========", text), 10 | width = max_width 11 | ) 12 | } 13 | 14 | pub fn get_random_art() -> String { 15 | let one = " 16 | ..%%%%% 17 | ... .%%%%%%\"\" 18 | .%%%%%%%/%%%%%\" 19 | ..... .%%%%%%%%%%%%%%\\ 20 | ..:::::::::::.. :;\"\" {_}\\%%/%%%%%%.. 21 | .:::::::::::::::::. {_}/{_}%%%%%%% 22 | ::::::::::::::::::::: \\\\// \"\"\"\\:: 23 | ::::::::::::::::::::::: \\\\// 24 | ---____-----__---------_____----....-----..--.....~-----_____-\\\\//---_____- 25 | -----___---___----____--__--_.----...----...----..---__--_-___\\\\//--___--__ 26 | --____----__-___-----____----_...-----..--..---..___-------__\\\\//_-----____ 27 | ___----____------____---__--___..--..-..----....___-----___--\\\\//_---__---- 28 | ---___--___--__---__----___---___..----------._---____-----__\\\\//___---__-- 29 | --___---_-___-------______------___--___----___--__------___-\\\\//__--__---- 30 | ____------____----_________------__ \\\\// 31 | --_____--__--____ ' \\\\// _ 32 | __--- @ , \" {_} 33 | . ' 34 | "; 35 | 36 | let two = " 37 | ******* 38 | ~ *---******* 39 | ~ *-----******* 40 | ~ *-------******* 41 | __ _ _!__ *-------******* 42 | _ / \\_ _/ \\ |::| ___ **-----******** ~ 43 | _/ \\_/^ \\/ ^\\/|::|\\|:| **---*****/^\\_ 44 | /\\/ ^ / ^ / ^ ___|::|_|:|_/\\_******/ ^ \\ 45 | / \\ _/ ^ ^ / |::|--|:|---| \\__/ ^ ^\\___ 46 | _/_^ \\/ ^ _/ ^ |::|::|:|-::| ^ /_ ^ ^ ^ \\_ 47 | / \\^ / /\\ / |::|--|:|:--| / \\ ^ \\ 48 | / \\/ / / |::|::|:|:-:| / ^ \\ ^ ^ \\ 49 | _Q / _Q _Q_Q / _Q _Q |::|::|:|:::|/ ^ \\ _Q ^ 50 | /_\\) /_\\)/_/\\\\) /_\\) /_\\) |::|::|:|:::| /_\\) 51 | _O|/O___O|/O_OO|/O__O|/O__O|/O__________________________O|/O__________ 52 | ////////////////////////////////////////////////////////////////////// 53 | "; 54 | 55 | let three = " 56 | ^^ @@@@@@@@@ 57 | ^^ ^^ @@@@@@@@@@@@@@@ 58 | @@@@@@@@@@@@@@@@@@ ^^ 59 | @@@@@@@@@@@@@@@@@@@@ 60 | ~~~~ ~~ ~~~~~ ~~~~~~~~ ~~ &&&&&&&&&&&&&&&&&&&& ~~~~~~~ ~~~~~~~~~~~ ~~~ 61 | ~ ~~ ~ ~ ~~~~~~~~~~~~~~~~~~~~ ~ ~~ ~~ ~ 62 | ~ ~~ ~~ ~~ ~~ ~~~~~~~~~~~~~ ~~~~ ~ ~~~ ~ ~~~ ~ ~~ 63 | ~ ~~ ~ ~ ~~~~~~ ~~ ~~~ ~~ ~ ~~ ~~ ~ 64 | ~ ~ ~ ~ ~ ~~ ~~~~~~ ~ ~~ ~ ~~ 65 | ~ ~ ~ ~ ~~ ~ ~ 66 | "; 67 | 68 | let four = " 69 | __ 70 | ,-_ (` ). 71 | |-_'-, ( ). 72 | |-_'-' _( '`. 73 | _ |-_'/ .=(`( . ) 74 | /;-,_ |-_' ( (.__.:-`-_.' 75 | /-.-;,-,___|' `( ) ) 76 | /;-;-;-;_;_/|\\_ _ _ _ _ ` __.:' ) 77 | x_( __`|_P_|`-;-;-;,| `--' 78 | |\\ \\ _|| `-;-;-' 79 | | \\` -_|. '-' 80 | | / /-_| `\\ 81 | |/ ,'-_| \\ 82 | /____|'-_|___\\ 83 | _..,____]__|_\\-_'|_[___,.._ 84 | ' ``'--,..,. 85 | "; 86 | 87 | let six = " 88 | ( ) 89 | ( ) 90 | ( ) 91 | ( ) 92 | ) ) 93 | ( ( /\\ 94 | (_) / \\ /\\ 95 | ________[_]________ /\\/ \\/ \\ 96 | /\\ /\\ ______ \\ / /\\/\\ /\\/\\ 97 | / \\ //_\\ \\ /\\ \\ /\\/\\/ \\/ \\ 98 | /\\ / /\\/\\ //___\\ \\__/ \\ \\/ 99 | / \\ /\\/ \\//_____\\ \\ |[]| \\ 100 | /\\/\\/\\/ //_______\\ \\|__| \\ 101 | / \\ /XXXXXXXXXX\\ \\ 102 | \\ /_I_II I__I_\\__________________\\ 103 | I_I| I__I_____[]_|_[]_____I 104 | I_II I__I_____[]_|_[]_____I 105 | I II__I I XXXXXXX I 106 | ~~~~~\" \"~~~~~~~~~~~~~~~~~~~~~~~~ 107 | "; 108 | 109 | let seven = " 110 | ` : | | | |: || : ` : | |+|: | : : :| . ` . 111 | ` : | :| || |: : ` | | :| : | : |: | . : 112 | .' ': || |: | ' ` || | : | |: : | . ` . :. 113 | `' || | ' | * ` : | | :| |*| : : :| 114 | * * ` | : : | . ` ' :| | :| . : : * :.|| 115 | .` | | | : .:| ` | || | : |: | | || 116 | ' . + ` | : .: . '| | : :| : . |:| || 117 | . . ` *| || : ` | | :| | : |:| | 118 | . . . || |.: * | || : : :||| 119 | . . . * . . ` |||. + + '| ||| . ||` 120 | . * . +:`|! . |||| :.||` 121 | + . ..!|* . | :`||+ |||` 122 | . + : |||` .| :| | | |.| ||` . 123 | * + ' + :|| |` :.+. || || | |:`|| ` 124 | . .||` . ..|| | |: '` `| | |` + 125 | . +++ || !|!: ` :| | 126 | + . . | . `|||.: .|| . . ` 127 | ' `|. . `:||| + ||' ` 128 | __ + * `' `'|. `: 129 | \"' `---\"\"\"----....____,..^---`^``----.,.___ `. `. . ____,.,- 130 | ___,--'\"\"`---\"' ^ ^ ^ ^ \"\"\"'---,..___ __,..---\"\"' 131 | --\"' ^ ``--..,__"; 132 | 133 | let options = [one, two, three, four, six, seven]; 134 | let mut rng = rand::thread_rng(); 135 | let random_index = rng.gen_range(0..options.len()); 136 | let chosen_option = options[random_index]; 137 | chosen_option.to_string() 138 | } 139 | -------------------------------------------------------------------------------- /src/database.rs: -------------------------------------------------------------------------------- 1 | use nl80211_ng::Interface; 2 | 3 | use rusqlite::{params, Connection, Result}; 4 | use std::time::Duration; 5 | use std::{ 6 | sync::{ 7 | self, 8 | atomic::{AtomicBool, Ordering}, 9 | mpsc::{self, Receiver, Sender}, 10 | Arc, Mutex, 11 | }, 12 | thread, 13 | }; 14 | 15 | use crate::pcapng::FrameData; 16 | 17 | pub struct DatabaseWriter { 18 | handle: Option>, 19 | alive: sync::Arc, 20 | tx: Sender, 21 | rx: Arc>>, 22 | filename: String, 23 | datasource_uuid: String, 24 | datasource: Interface, 25 | } 26 | 27 | impl DatabaseWriter { 28 | pub fn new(filename: &str, datasource_uuid: String, datasource: Interface) -> DatabaseWriter { 29 | let (tx, rx) = mpsc::channel(); 30 | 31 | DatabaseWriter { 32 | handle: None, 33 | alive: Arc::new(AtomicBool::new(false)), 34 | tx, 35 | rx: Arc::new(Mutex::new(rx)), 36 | filename: filename.to_owned(), 37 | datasource_uuid, 38 | datasource, 39 | } 40 | } 41 | 42 | pub fn send(&mut self, f: FrameData) { 43 | self.tx.send(f.clone()).unwrap(); 44 | } 45 | 46 | pub fn start(&mut self) { 47 | self.alive.store(true, Ordering::SeqCst); 48 | let alive = self.alive.clone(); 49 | let rx = self.rx.clone(); 50 | let filename = self.filename.clone(); 51 | let interface = self.datasource.clone(); 52 | let datasource = self.datasource_uuid.clone(); 53 | 54 | self.handle = Some(thread::spawn(move || { 55 | // Setup database file 56 | let conn = Connection::open(filename).unwrap(); 57 | let _ = setup_database(&conn, datasource, interface); 58 | 59 | while alive.load(Ordering::SeqCst) { 60 | let frx = 61 | if let Ok(frx) = rx.lock().unwrap().recv_timeout(Duration::from_millis(500)) { 62 | frx 63 | } else { 64 | continue; 65 | }; 66 | add_frame(&conn, &frx); 67 | } 68 | })); 69 | } 70 | 71 | pub fn stop(&mut self) { 72 | self.alive.store(false, Ordering::SeqCst); 73 | self.handle 74 | .take() 75 | .expect("Called stop on non-running thread") 76 | .join() 77 | .expect("Could not join spawned thread"); 78 | } 79 | } 80 | 81 | pub fn setup_database(conn: &Connection, datasource: String, interface: Interface) -> Result<()> { 82 | // Create tables: 83 | 84 | // Kismet 85 | conn.execute( 86 | "create table if not exists KISMET (kismet_version TEXT, db_version INT, db_module TEXT)", 87 | params![], 88 | )?; 89 | conn.execute( 90 | "INSERT INTO KISMET (kismet_version, db_version, db_module) values (?1, ?2, ?3)", 91 | [ 92 | String::from("2022.02.R1"), 93 | 8.to_string(), 94 | String::from("kismetlog"), 95 | ], 96 | )?; 97 | 98 | // Alerts 99 | conn.execute( 100 | "create table if not exists alerts (ts_sec integer, ts_usec integer, phyname TEXT, devmac TEXT, lat REAL, lon REAL, header TEXT, json BLOB )", 101 | params![], 102 | )?; 103 | // Data 104 | conn.execute( 105 | "create table if not exists data (ts_sec integer, ts_usec integer, phyname TEXT, devmac TEXT, lat REAL, lon REAL, alt REAL, speed REAL, heading REAL, datasource TEXT, type TEXT, json BLOB )", 106 | params![], 107 | )?; 108 | // Datasources 109 | conn.execute( 110 | "create table if not exists datasources (uuid TEXT, typestring TEXT, definition TEXT, name TEXT, interface TEXT, json BLOB, UNIQUE(uuid) ON CONFLICT REPLACE)", 111 | params![], 112 | )?; 113 | conn.execute( 114 | "INSERT INTO datasources (uuid, typestring, definition, name, interface) values (?1, ?2, ?3, ?4, $5)", 115 | [ 116 | datasource, 117 | String::from("linuxwifi"), 118 | interface.name_as_string(), 119 | interface.name_as_string(), 120 | interface.name_as_string(), 121 | ], 122 | )?; 123 | 124 | // Devices 125 | conn.execute( 126 | "create table if not exists devices (first_time INT, last_time INT, devkey TEXT, phyname TEXT, devmac TEXT, strongest_signal INT, min_lat REAL, min_lon REAL, max_lat REAL, max_lon REAL, avg_lat REAL, avg_lon REAL, bytes_data INT, type TEXT, device BLOB, UNIQUE(phyname, devmac) ON CONFLICT REPLACE)", 127 | params![], 128 | )?; 129 | // Messages 130 | conn.execute( 131 | "create table if not exists messages (ts_sec INT, lat REAL, lon REAL, msgtype TEXT, message TEXT )", 132 | params![], 133 | )?; 134 | // Packets 135 | conn.execute( 136 | "create table if not exists packets (ts_sec integer, ts_usec integer, phyname TEXT, sourcemac TEXT, destmac TEXT, transmac TEXT, frequency REAL, devkey TEXT, lat REAL, lon REAL, alt REAL, speed REAL, heading REAL, packet_len integer, signal integer, datasource TEXT, dlt integer, packet BLOB, error integer, tags TEXT, datarate REAL, hash integer, packetid integer )", 137 | params![], 138 | )?; 139 | // Snapshots 140 | conn.execute( 141 | "create table if not exists snapshots (ts_sec INT, ts_usec INT, lat REAL, lon REAL, snaptype TEXT, json BLOB )", 142 | params![], 143 | )?; 144 | 145 | Ok(()) 146 | } 147 | 148 | 149 | fn add_frame(conn: &Connection, frx: &FrameData) { 150 | let packet_data = PacketData::new(frx); 151 | let _result = conn.execute( 152 | "INSERT INTO packets (ts_sec, ts_usec, phyname, sourcemac, destmac, transmac, frequency, devkey, lat, lon, alt, speed, heading, packet_len, signal, datasource, dlt, packet, error, tags, datarate, hash, packetid) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?12, ?13, ?14, ?15, ?16, ?17, ?18, ?19, ?20, ?21, ?22, ?23)", 153 | params![ 154 | packet_data.ts_sec, 155 | packet_data.ts_usec, 156 | packet_data.phyname, 157 | packet_data.sourcemac, 158 | packet_data.destmac, 159 | packet_data.transmac, 160 | packet_data.frequency, 161 | packet_data.devkey, 162 | packet_data.lat, 163 | packet_data.lon, 164 | packet_data.alt, 165 | packet_data.speed, 166 | packet_data.heading, 167 | packet_data.packet_len, 168 | packet_data.signal, 169 | packet_data.datasource, 170 | packet_data.dlt, 171 | packet_data.packet, 172 | packet_data.error, 173 | packet_data.tags, 174 | packet_data.datarate, 175 | packet_data.hash, 176 | packet_data.packetid 177 | ], 178 | ); 179 | 180 | } 181 | 182 | #[derive(Debug, Clone)] 183 | pub struct PacketData { 184 | ts_sec: u64, 185 | ts_usec: u64, 186 | phyname: String, 187 | sourcemac: String, 188 | destmac: String, 189 | transmac: String, 190 | frequency: f64, // in KHz 191 | devkey: String, // Deprecated 192 | lat: f64, 193 | lon: f64, 194 | alt: f32, 195 | speed: f32, 196 | heading: f32, 197 | packet_len: usize, 198 | signal: i32, // in dBm or other 199 | datasource: String, 200 | dlt: i32, 201 | packet: Vec, // Raw binary packet content 202 | error: u8, 203 | tags: String, // Arbitrary space-separated list of tags 204 | datarate: f64, // in mbit/sec 205 | hash: u32, // CRC32 206 | packetid: u64, 207 | } 208 | 209 | impl PacketData { 210 | pub fn new(frx: &FrameData) -> Self { 211 | let lat = if let Some(gps) = &frx.gps_data { 212 | gps.lat.unwrap_or(0f64) 213 | } else { 214 | 0f64 215 | }; 216 | 217 | let lon = if let Some(gps) = &frx.gps_data { 218 | gps.lon.unwrap_or(0f64) 219 | } else { 220 | 0f64 221 | }; 222 | 223 | let alt = if let Some(gps) = &frx.gps_data { 224 | gps.alt.unwrap_or(0f32) 225 | } else { 226 | 0f32 227 | }; 228 | 229 | let speed = if let Some(gps) = &frx.gps_data { 230 | gps.speed.unwrap_or(0f32) 231 | } else { 232 | 0f32 233 | }; 234 | let heading = if let Some(gps) = &frx.gps_data { 235 | gps.heading.unwrap_or(0f32) 236 | } else { 237 | 0f32 238 | }; 239 | 240 | PacketData { 241 | ts_sec: frx.ts_sec(), 242 | ts_usec: frx.ts_usec(), 243 | phyname: "IEEE802.11".to_string(), 244 | sourcemac: frx.source.to_long_string(), 245 | destmac: frx.destination.to_long_string(), 246 | transmac: String::from("00:00:00:00:00:00"), 247 | frequency: frx.frequency.unwrap_or_default()*1000.0, 248 | devkey: String::from("0"), 249 | lat, 250 | lon, 251 | alt, 252 | speed, 253 | heading, 254 | packet_len: frx.data.len(), 255 | signal: frx.signal.unwrap_or_default(), 256 | datasource: frx.datasource.hyphenated().to_string(), 257 | dlt: 127, 258 | packet: frx.data.clone(), 259 | error: 0, 260 | tags: String::from(""), 261 | datarate: frx.datarate.unwrap_or_default(), 262 | hash: frx.crc32(), 263 | packetid: frx.packetid, 264 | } 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /src/eventhandler.rs: -------------------------------------------------------------------------------- 1 | 2 | use crossterm::event::{poll, Event, KeyCode, KeyEventKind, MouseEventKind}; 3 | 4 | use std::time::Duration; 5 | use std::{ 6 | sync::{ 7 | self, 8 | atomic::{AtomicBool, Ordering}, 9 | mpsc::{self, Receiver, Sender}, 10 | Arc, 11 | }, 12 | thread, 13 | }; 14 | 15 | pub enum EventType { 16 | Key(Event), 17 | Tick, 18 | } 19 | 20 | pub struct OxideEvent { 21 | pub event_type: EventType, 22 | } 23 | 24 | pub struct EventHandler { 25 | handle: Option>, 26 | alive: sync::Arc, 27 | tx: Sender, 28 | rx: Receiver, 29 | } 30 | 31 | impl EventHandler { 32 | pub fn new() -> EventHandler { 33 | let (tx, rx) = mpsc::channel(); 34 | 35 | EventHandler { 36 | handle: None, 37 | alive: Arc::new(AtomicBool::new(false)), 38 | tx, 39 | rx, 40 | } 41 | } 42 | 43 | pub fn get(&mut self) -> Option { 44 | if let Ok(event) = self.rx.try_recv() { 45 | return Some(event); 46 | } 47 | None 48 | } 49 | 50 | pub fn start(&mut self) { 51 | self.alive.store(true, Ordering::SeqCst); 52 | let alive = self.alive.clone(); 53 | let tx = self.tx.clone(); 54 | 55 | self.handle = Some(thread::spawn(move || { 56 | while alive.load(Ordering::SeqCst) { 57 | if poll(Duration::from_millis(50)).unwrap() { 58 | let event = crossterm::event::read().unwrap(); 59 | if let Event::Key(key) = event { 60 | if key.kind == KeyEventKind::Press { 61 | let _ = match key.code { 62 | KeyCode::Char('d') => tx.send(EventType::Key(event)), 63 | KeyCode::Char('a') => tx.send(EventType::Key(event)), 64 | KeyCode::Char('W') => tx.send(EventType::Key(event)), 65 | KeyCode::Char('w') => tx.send(EventType::Key(event)), 66 | KeyCode::Char('s') => tx.send(EventType::Key(event)), 67 | KeyCode::Char('S') => tx.send(EventType::Key(event)), 68 | KeyCode::Char('q') => tx.send(EventType::Key(event)), 69 | KeyCode::Char(' ') => tx.send(EventType::Key(event)), 70 | KeyCode::Char('e') => tx.send(EventType::Key(event)), 71 | KeyCode::Char('r') => tx.send(EventType::Key(event)), 72 | KeyCode::Char('n') => tx.send(EventType::Key(event)), 73 | KeyCode::Char('N') => tx.send(EventType::Key(event)), 74 | KeyCode::Char('y') => tx.send(EventType::Key(event)), 75 | KeyCode::Char('Y') => tx.send(EventType::Key(event)), 76 | KeyCode::Char('c') => tx.send(EventType::Key(event)), 77 | KeyCode::Char('C') => tx.send(EventType::Key(event)), 78 | KeyCode::Char('t') => tx.send(EventType::Key(event)), 79 | KeyCode::Char('T') => tx.send(EventType::Key(event)), 80 | KeyCode::Char('k') => tx.send(EventType::Key(event)), 81 | KeyCode::Char('l') => tx.send(EventType::Key(event)), 82 | KeyCode::Char('L') => tx.send(EventType::Key(event)), 83 | KeyCode::Up => tx.send(EventType::Key(event)), 84 | KeyCode::Down => tx.send(EventType::Key(event)), 85 | KeyCode::Left => tx.send(EventType::Key(event)), 86 | KeyCode::Right => tx.send(EventType::Key(event)), 87 | _ => Ok(()), 88 | }; 89 | } 90 | } else if let Event::Mouse(mouse) = event { 91 | let _ = match mouse.kind { 92 | MouseEventKind::ScrollDown => tx.send(EventType::Key(event)), 93 | MouseEventKind::ScrollUp => tx.send(EventType::Key(event)), 94 | _ => Ok(()), 95 | }; 96 | } 97 | } 98 | let _ = tx.send(EventType::Tick); 99 | } 100 | })); 101 | } 102 | 103 | pub fn stop(&mut self) { 104 | self.alive.store(false, Ordering::SeqCst); 105 | self.handle 106 | .take() 107 | .expect("Called stop on non-running thread") 108 | .join() 109 | .expect("Could not join spawned thread"); 110 | 111 | println!("Stopped PCAPNG Thread"); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/geofence.rs: -------------------------------------------------------------------------------- 1 | use geo::algorithm::haversine_distance::HaversineDistance; 2 | use geo_types::Point; 3 | use geoconvert::{LatLon, Mgrs}; 4 | use geomorph::coord::Coord; 5 | 6 | pub struct Geofence { 7 | pub target_coord: LatLon, 8 | pub target_radius: f64, 9 | pub mgrs: bool, 10 | } 11 | 12 | impl Geofence { 13 | pub fn new(target_grid: String, target_radius: f64) -> Result { 14 | let mut mgrs = false; 15 | let coord = if is_mgrs(target_grid.clone()) { 16 | let mgrs_string = Mgrs::parse_str(&target_grid).unwrap(); 17 | mgrs = true; 18 | LatLon::from_mgrs(&mgrs_string) 19 | } else { 20 | let parts: Vec<&str> = target_grid.split(',').collect(); 21 | if parts.len() != 2 { 22 | return Err("Input should be in 'lat,lng' format".to_string()); 23 | } 24 | let latitude = parts[0].parse::().map_err(|_| "Invalid latitude")?; 25 | let longitude = parts[1].parse::().map_err(|_| "Invalid longitude")?; 26 | LatLon::create(latitude, longitude).map_err(|_| "Invalid coordinates")? 27 | }; 28 | Ok(Geofence { 29 | target_coord: coord, 30 | target_radius, 31 | mgrs, 32 | }) 33 | } 34 | 35 | pub fn distance_to_target(&self, current_point: (f64, f64)) -> f64 { 36 | let current_coord_latlon = LatLon::create(current_point.0, current_point.1).unwrap(); 37 | self.target_coord.haversine(¤t_coord_latlon) 38 | } 39 | 40 | pub fn is_within_area(&self, current_point: (f64, f64)) -> bool { 41 | let current_coord = Coord::new(current_point.0, current_point.1); 42 | let target_point = Point::new(self.target_coord.longitude(), self.target_coord.latitude()); 43 | let current_point = Point::new(current_coord.lon, current_coord.lat); 44 | current_point.haversine_distance(&target_point) <= self.target_radius 45 | } 46 | } 47 | 48 | pub fn is_mgrs(grid: String) -> bool { 49 | Mgrs::parse_str(&grid).is_ok() 50 | } 51 | -------------------------------------------------------------------------------- /src/matrix.rs: -------------------------------------------------------------------------------- 1 | use crossterm::style::Color; 2 | use rand::{thread_rng, Rng}; 3 | use ratatui::{ 4 | buffer::Buffer, 5 | layout::Rect, 6 | widgets::{Clear, Widget}, 7 | }; 8 | 9 | #[derive(Clone)] 10 | struct MatrixSnowflake { 11 | x: u16, 12 | y: f32, 13 | color: Color, 14 | shape: char, 15 | vel: f32, 16 | } 17 | 18 | impl MatrixSnowflake { 19 | pub fn new(area: Rect) -> MatrixSnowflake { 20 | let mut rng = thread_rng(); 21 | let new_x = rng.gen_range(area.x..area.x + area.width); 22 | let velocity = rng.gen_range(0.04..0.12); 23 | 24 | MatrixSnowflake { 25 | x: new_x, 26 | y: area.y as f32, 27 | color: Color::Green, 28 | shape: rng.gen_range(33..127) as u8 as char, 29 | vel: velocity, 30 | } 31 | } 32 | 33 | pub fn within_bounds(&self, area: Rect) -> bool { 34 | let bottom_edge = area.y + area.height; 35 | (self.y as u16) < bottom_edge 36 | } 37 | } 38 | 39 | #[derive(Clone)] 40 | pub struct MatrixSnowstorm { 41 | snowflakes: Vec, 42 | density: usize, 43 | } 44 | 45 | impl MatrixSnowstorm { 46 | pub fn new(area: Rect) -> MatrixSnowstorm { 47 | MatrixSnowstorm::frame( 48 | MatrixSnowstorm { 49 | snowflakes: Vec::new(), 50 | density: 50, 51 | }, 52 | area, 53 | ) 54 | } 55 | 56 | pub fn frame(mut snowstorm: MatrixSnowstorm, area: Rect) -> MatrixSnowstorm { 57 | let mut rng = thread_rng(); 58 | 59 | // Move all the snowflakes, remove if outside the area 60 | snowstorm.snowflakes.retain_mut(|snowflake| { 61 | snowflake.y += snowflake.vel; 62 | snowflake.within_bounds(area) 63 | }); 64 | 65 | // Generate new snowflakes... maybe 66 | for _ in 0..snowstorm.density { 67 | if rng.gen_bool(snowstorm.density as f64 / 4000.0) { 68 | snowstorm.snowflakes.push(MatrixSnowflake::new(area)); 69 | } 70 | } 71 | 72 | snowstorm 73 | } 74 | } 75 | 76 | impl Widget for MatrixSnowstorm { 77 | fn render(self, area: Rect, buf: &mut Buffer) { 78 | // Clear the area 79 | Clear.render(area, buf); 80 | 81 | // Set the snowflakes 82 | for snowflake in self.snowflakes { 83 | buf.get_mut(snowflake.x, snowflake.y as u16) 84 | .set_char(snowflake.shape) 85 | .set_fg(snowflake.color.into()); 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /src/oui.rs: -------------------------------------------------------------------------------- 1 | 2 | use libwifi::frame::components::MacAddress; 3 | 4 | // Define bitmasks for different OUI lengths 5 | const OUI_24_BIT_MASK: u64 = 0x0000FFFFFF000000; 6 | const OUI_28_BIT_MASK: u64 = 0x0000FFFFFFF00000; 7 | const OUI_36_BIT_MASK: u64 = 0x0000FFFFFFFFF000; 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct OuiRecord { 11 | oui: u64, // Store OUI as an integer 12 | oui_length: u32, 13 | short_name: String, 14 | long_name: String, 15 | } 16 | 17 | impl OuiRecord { 18 | fn new(oui: u64, oui_length: u32, short_name: String, long_name: String) -> OuiRecord { 19 | OuiRecord { 20 | oui, 21 | oui_length, 22 | short_name, 23 | long_name, 24 | } 25 | } 26 | 27 | pub fn short_name(&self) -> String { 28 | self.short_name.clone() 29 | } 30 | 31 | pub fn long_name(&self) -> String { 32 | self.long_name.clone() 33 | } 34 | 35 | pub fn oui(&self) -> String { 36 | format!("{:02X}/{}", self.oui, self.oui_length) 37 | } 38 | } 39 | 40 | #[derive(Debug, Default)] 41 | pub struct OuiDatabase { 42 | records: Vec, // Use u32 as the key 43 | } 44 | 45 | impl OuiDatabase { 46 | pub fn new() -> OuiDatabase { 47 | let mut db = OuiDatabase { 48 | records: Vec::new(), 49 | }; 50 | db.parse_and_load_data(); 51 | db 52 | } 53 | 54 | pub fn record_count(&self) -> usize { 55 | self.records.len() 56 | } 57 | 58 | fn parse_and_load_data(&mut self) { 59 | let data = include_str!("../assets/manuf"); 60 | 61 | for line in data.lines() { 62 | if line.starts_with('#') || line.trim().is_empty() { 63 | continue; // Skip comments and empty lines 64 | } 65 | 66 | let parts: Vec<&str> = line.split('\t').collect(); 67 | if parts.len() >= 3 { 68 | let oui_str = parts[0].split('/').next().unwrap_or("").trim(); 69 | let oui_len_str = parts[0].split('/').last().unwrap_or("").trim(); 70 | let oui = u64::from_str_radix(&oui_str.replace(':', ""), 16).unwrap_or(0); 71 | let oui_length: u32 = oui_len_str.parse().unwrap_or(24); 72 | let oui = match oui_length { 73 | 24 => oui << (48 - 24), 74 | 28 => oui << (48 - 32), 75 | 36 => oui << (48 - 40), 76 | _ => 0, 77 | }; 78 | let short_name = parts[1].trim().to_string(); 79 | let long_name = parts[2].trim().to_string(); 80 | let record = OuiRecord::new(oui, oui_length, short_name, long_name); 81 | self.records.push(record); 82 | } 83 | } 84 | } 85 | 86 | pub fn search(&self, mac_address: &MacAddress) -> Option { 87 | let mac_int: u64 = mac_address.to_u64(); 88 | 89 | for record in &self.records { 90 | let mask = match record.oui_length { 91 | 24 => OUI_24_BIT_MASK, 92 | 28 => OUI_28_BIT_MASK, 93 | 36 => OUI_36_BIT_MASK, 94 | _ => continue, 95 | }; 96 | 97 | if mac_int & mask == record.oui & mask { 98 | return Some(record.clone()); 99 | } 100 | } 101 | None 102 | } 103 | } 104 | 105 | /* 106 | 00:50:C5 ADSTechnolog ADS Technologies, Inc 107 | 00:69:67:E0/28 TianjinLianw Tianjin Lianwu Technology Co., Ltd. 108 | 00:50:C2:FF:F0/36 MSRSolutions MSR-Solutions GmbH 109 | */ 110 | -------------------------------------------------------------------------------- /src/pcapng.rs: -------------------------------------------------------------------------------- 1 | use byteorder::LE; 2 | use crc32fast::Hasher; 3 | use libwifi::frame::components::MacAddress; 4 | use nl80211_ng::Interface; 5 | use pcap_file::pcapng::blocks::enhanced_packet::{EnhancedPacketBlock, EnhancedPacketOption}; 6 | use pcap_file::pcapng::blocks::interface_description::{ 7 | InterfaceDescriptionBlock, InterfaceDescriptionOption, 8 | }; 9 | use pcap_file::pcapng::blocks::opt_common::CustomBinaryOption; 10 | use pcap_file::pcapng::blocks::section_header::{SectionHeaderBlock, SectionHeaderOption}; 11 | use pcap_file::pcapng::PcapNgWriter; 12 | use pcap_file::{DataLink, Endianness}; 13 | use std::borrow::Cow; 14 | use std::fs::File; 15 | use std::time::{Duration, UNIX_EPOCH}; 16 | use std::{ 17 | sync::{ 18 | self, 19 | atomic::{AtomicBool, Ordering}, 20 | mpsc::{self, Receiver, Sender}, 21 | Arc, Mutex, 22 | }, 23 | thread, 24 | time::SystemTime, 25 | }; 26 | use uname::uname; 27 | use uuid::Uuid; 28 | 29 | use crate::gps::GpsData; 30 | 31 | #[derive(Clone, Debug)] 32 | pub struct FrameData { 33 | pub timestamp: SystemTime, 34 | pub packetid: u64, 35 | pub gps_data: Option, 36 | pub data: Vec, 37 | pub source: MacAddress, 38 | pub destination: MacAddress, 39 | pub frequency: Option, 40 | pub signal: Option, 41 | pub datarate: Option, 42 | pub datasource: Uuid, 43 | } 44 | 45 | impl FrameData { 46 | pub fn new( 47 | timestamp: SystemTime, 48 | packetid: u64, 49 | data: Vec, 50 | gps_data: Option, 51 | source: MacAddress, 52 | destination: MacAddress, 53 | frequency: Option, 54 | signal: Option, 55 | datarate: Option, 56 | datasource: Uuid, 57 | ) -> Self { 58 | FrameData { 59 | timestamp, 60 | packetid, 61 | gps_data, 62 | data, 63 | source, 64 | destination, 65 | frequency, 66 | signal, 67 | datarate, 68 | datasource, 69 | } 70 | } 71 | 72 | pub fn ts_sec(&self) -> u64 { 73 | match self.timestamp.duration_since(UNIX_EPOCH) { 74 | Ok(duration) => duration.as_secs(), 75 | Err(_) => 0, // Handle timestamp before UNIX_EPOCH, return 0 or handle as needed 76 | } 77 | } 78 | 79 | pub fn ts_usec(&self) -> u64 { 80 | match self.timestamp.duration_since(UNIX_EPOCH) { 81 | Ok(duration) => duration.subsec_micros() as u64, 82 | Err(_) => 0, // Handle timestamp before UNIX_EPOCH, return 0 or handle as needed 83 | } 84 | } 85 | 86 | pub fn crc32(&self) -> u32 { 87 | let mut hasher = Hasher::new(); 88 | hasher.update(&self.data); 89 | hasher.finalize() 90 | } 91 | } 92 | 93 | pub struct PcapWriter { 94 | handle: Option>, 95 | alive: sync::Arc, 96 | tx: Sender, 97 | rx: Arc>>, 98 | writer: Arc>>, 99 | filename: String, 100 | } 101 | 102 | impl PcapWriter { 103 | pub fn new(interface: &Interface, filename: &str) -> PcapWriter { 104 | let (tx, rx) = mpsc::channel(); 105 | 106 | let file = File::create(filename).expect("Error creating file"); 107 | let (os, arch) = if let Ok(info) = uname() { 108 | ( 109 | format!("{} {}", info.sysname, info.release), 110 | info.machine.to_string(), 111 | ) 112 | } else { 113 | ("Unknown".to_string(), "Unknown".to_string()) 114 | }; 115 | 116 | let application = format!("AngryOxide {}", env!("CARGO_PKG_VERSION")); 117 | 118 | let shb = SectionHeaderBlock { 119 | endianness: Endianness::native(), 120 | major_version: 1, 121 | minor_version: 0, 122 | section_length: -1, 123 | options: vec![ 124 | SectionHeaderOption::UserApplication(Cow::from(application)), 125 | SectionHeaderOption::OS(Cow::from(os)), 126 | SectionHeaderOption::Hardware(Cow::from(arch)), 127 | ], 128 | }; 129 | 130 | let mut pcap_writer = PcapNgWriter::with_section_header(file, shb).unwrap(); 131 | 132 | let mac = interface.mac.clone().unwrap(); 133 | let interface = InterfaceDescriptionBlock { 134 | linktype: DataLink::IEEE802_11_RADIOTAP, 135 | snaplen: 0x0000, 136 | options: vec![ 137 | InterfaceDescriptionOption::IfName(Cow::from(interface.name_as_string())), 138 | InterfaceDescriptionOption::IfHardware(Cow::from(interface.driver_as_string())), 139 | InterfaceDescriptionOption::IfMacAddr(Cow::from(mac)), 140 | ], 141 | }; 142 | 143 | let _ = pcap_writer.write_pcapng_block(interface); 144 | 145 | PcapWriter { 146 | handle: None, 147 | alive: Arc::new(AtomicBool::new(false)), 148 | tx, 149 | rx: Arc::new(Mutex::new(rx)), 150 | writer: Arc::new(Mutex::new(pcap_writer)), 151 | filename: filename.to_owned(), 152 | } 153 | } 154 | 155 | pub fn check_size(&self) -> u64 { 156 | self.writer 157 | .lock() 158 | .unwrap() 159 | .get_ref() 160 | .metadata() 161 | .unwrap() 162 | .len() 163 | } 164 | 165 | pub fn send(&mut self, f: FrameData) { 166 | self.tx.send(f.clone()).unwrap(); 167 | } 168 | 169 | pub fn start(&mut self) { 170 | self.alive.store(true, Ordering::SeqCst); 171 | let alive = self.alive.clone(); 172 | let rx = self.rx.clone(); 173 | let writer = self.writer.clone(); 174 | 175 | self.handle = Some(thread::spawn(move || { 176 | while alive.load(Ordering::SeqCst) { 177 | let frx = 178 | if let Ok(frx) = rx.lock().unwrap().recv_timeout(Duration::from_millis(500)) { 179 | frx 180 | } else { 181 | continue; 182 | }; 183 | 184 | if let Some(gps) = frx.gps_data { 185 | let option_block = gps.create_option_block(); // Create and store the block 186 | let custom_binary_option = CustomBinaryOption::from_slice::( 187 | 2989, 188 | &option_block, // Borrow from the stored block 189 | ) 190 | .unwrap(); 191 | let packet = EnhancedPacketBlock { 192 | interface_id: 0, 193 | timestamp: frx.timestamp.duration_since(UNIX_EPOCH).unwrap(), 194 | original_len: frx.data.len() as u32, 195 | data: frx.data.into(), 196 | options: vec![EnhancedPacketOption::CustomBinary(custom_binary_option)], 197 | }; 198 | 199 | let _ = writer.lock().unwrap().write_pcapng_block(packet); 200 | } else { 201 | let packet = EnhancedPacketBlock { 202 | interface_id: 0, 203 | timestamp: frx.timestamp.duration_since(UNIX_EPOCH).unwrap(), 204 | original_len: frx.data.len() as u32, 205 | data: frx.data.into(), 206 | options: vec![], 207 | }; 208 | 209 | let _ = writer.lock().unwrap().write_pcapng_block(packet); 210 | }; 211 | } 212 | })); 213 | } 214 | 215 | pub fn stop(&mut self) { 216 | self.alive.store(false, Ordering::SeqCst); 217 | self.handle 218 | .take() 219 | .expect("Called stop on non-running thread") 220 | .join() 221 | .expect("Could not join spawned thread"); 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/rawsocks.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | io, mem, 3 | os::fd::{AsRawFd, OwnedFd}, 4 | }; 5 | 6 | use libc::{ 7 | packet_mreq, sockaddr_ll, ETH_ALEN, ETH_P_ALL, PACKET_MR_PROMISC, SOL_PACKET, SO_PRIORITY, 8 | }; 9 | use nix::{ 10 | fcntl::{fcntl, FcntlArg, OFlag}, 11 | sys::socket::{socket, AddressFamily, SockFlag, SockProtocol, SockType}, 12 | }; 13 | use procfs::KernelVersion; 14 | 15 | pub fn open_socket_tx(ifindex: i32) -> Result { 16 | let mut saddr: sockaddr_ll = unsafe { mem::zeroed() }; 17 | let mut mrq: packet_mreq = unsafe { mem::zeroed() }; 18 | let prioval = 20; 19 | 20 | let fd_socket_tx = socket( 21 | AddressFamily::Packet, 22 | SockType::Raw, 23 | SockFlag::SOCK_CLOEXEC, 24 | SockProtocol::EthAll, 25 | ) 26 | .map_err(|e| e.to_string())?; 27 | 28 | mrq.mr_ifindex = ifindex; 29 | mrq.mr_type = PACKET_MR_PROMISC as u16; 30 | 31 | let ret = unsafe { 32 | libc::setsockopt( 33 | fd_socket_tx.as_raw_fd(), 34 | SOL_PACKET, 35 | libc::PACKET_ADD_MEMBERSHIP, 36 | &mrq as *const _ as *const libc::c_void, 37 | mem::size_of::() as libc::socklen_t, 38 | ) 39 | }; 40 | 41 | if ret < 0 { 42 | return Err("Failed to set PACKET_ADD_MEMBERSHIP option".to_string()); 43 | } 44 | 45 | unsafe { 46 | libc::setsockopt( 47 | fd_socket_tx.as_raw_fd(), 48 | SOL_PACKET, 49 | SO_PRIORITY, 50 | &prioval as *const _ as *const libc::c_void, 51 | mem::size_of::() as libc::socklen_t, 52 | ) 53 | }; 54 | 55 | saddr.sll_family = libc::AF_PACKET as u16; // Use AF_PACKET 56 | saddr.sll_protocol = (ETH_P_ALL as u16).to_be(); 57 | saddr.sll_ifindex = ifindex; 58 | saddr.sll_halen = ETH_ALEN as u8; // Directly set ETH_ALEN, no need for to_be() 59 | 60 | let bind_ret = unsafe { 61 | libc::bind( 62 | fd_socket_tx.as_raw_fd(), 63 | (&saddr as *const libc::sockaddr_ll).cast(), 64 | mem::size_of::() as libc::socklen_t, // Use the size of sockaddr_ll 65 | ) 66 | }; 67 | 68 | if bind_ret < 0 { 69 | let error = io::Error::last_os_error(); 70 | return Err(format!("Bind failed: {}", error)); 71 | } 72 | 73 | /* saddr.sll_family = libc::PF_PACKET as u16; 74 | saddr.sll_protocol = (ETH_P_ALL as u16).to_be(); 75 | saddr.sll_ifindex = ifindex; 76 | saddr.sll_halen = (ETH_ALEN as u8).to_be(); 77 | saddr.sll_pkttype = 3; 78 | 79 | let bind_ret = unsafe { 80 | libc::bind( 81 | fd_socket_tx.as_raw_fd(), 82 | (&saddr as *const libc::sockaddr_ll).cast(), 83 | saddr.sll_addr.len().try_into().unwrap(), 84 | ) 85 | }; 86 | println!("BIND RET: {bind_ret}"); */ 87 | 88 | let socket_tx_flags = 89 | fcntl(fd_socket_tx.as_raw_fd(), FcntlArg::F_GETFL).map_err(|e| e.to_string())?; 90 | 91 | let new_flags = OFlag::from_bits_truncate(socket_tx_flags | OFlag::O_NONBLOCK.bits()); 92 | fcntl(fd_socket_tx.as_raw_fd(), FcntlArg::F_SETFL(new_flags)).map_err(|e| e.to_string())?; 93 | 94 | Ok(fd_socket_tx) 95 | } 96 | 97 | pub fn open_socket_rx(ifindex: i32) -> Result { 98 | let mut saddr: sockaddr_ll = unsafe { mem::zeroed() }; 99 | let mut mrq: packet_mreq = unsafe { mem::zeroed() }; 100 | let prioval = 20; 101 | 102 | let fd_socket_rx = socket( 103 | AddressFamily::Packet, 104 | SockType::Raw, 105 | SockFlag::SOCK_CLOEXEC, 106 | SockProtocol::EthAll, 107 | ) 108 | .map_err(|e| e.to_string())?; 109 | 110 | mrq.mr_ifindex = ifindex; 111 | mrq.mr_type = PACKET_MR_PROMISC as u16; 112 | 113 | let ret = unsafe { 114 | libc::setsockopt( 115 | fd_socket_rx.as_raw_fd(), 116 | SOL_PACKET, 117 | libc::PACKET_ADD_MEMBERSHIP, 118 | &mrq as *const _ as *const libc::c_void, 119 | mem::size_of::() as libc::socklen_t, 120 | ) 121 | }; 122 | if ret < 0 { 123 | return Err("Failed to set PACKET_ADD_MEMBERSHIP option".to_string()); 124 | } 125 | 126 | unsafe { 127 | libc::setsockopt( 128 | fd_socket_rx.as_raw_fd(), 129 | SOL_PACKET, 130 | SO_PRIORITY, 131 | &prioval as *const _ as *const libc::c_void, 132 | mem::size_of::() as libc::socklen_t, 133 | ) 134 | }; 135 | 136 | // New: Ignoring outgoing packets (Linux 4.20 and later) 137 | if KernelVersion::current().is_ok() 138 | && KernelVersion::current().unwrap() > KernelVersion::new(4, 20, 0) 139 | { 140 | let enable = 1; 141 | let ret = unsafe { 142 | libc::setsockopt( 143 | fd_socket_rx.as_raw_fd(), 144 | SOL_PACKET, 145 | 23, 146 | &enable as *const _ as *const libc::c_void, 147 | mem::size_of::() as libc::socklen_t, 148 | ) 149 | }; 150 | if ret < 0 { 151 | eprintln!("PACKET_IGNORE_OUTGOING is not supported by kernel..."); 152 | } 153 | } 154 | 155 | saddr.sll_family = libc::AF_PACKET as u16; 156 | saddr.sll_protocol = (ETH_P_ALL as u16).to_be(); 157 | saddr.sll_ifindex = ifindex; 158 | saddr.sll_halen = (ETH_ALEN as u8).to_be(); 159 | saddr.sll_pkttype = 3; 160 | 161 | unsafe { 162 | libc::bind( 163 | fd_socket_rx.as_raw_fd(), 164 | (&saddr as *const libc::sockaddr_ll).cast(), 165 | mem::size_of::() as libc::socklen_t, 166 | ) 167 | }; 168 | 169 | let socket_rx_flags = 170 | fcntl(fd_socket_rx.as_raw_fd(), FcntlArg::F_GETFL).map_err(|e| e.to_string())?; 171 | 172 | let new_flags = OFlag::from_bits_truncate(socket_rx_flags | OFlag::O_NONBLOCK.bits()); 173 | fcntl(fd_socket_rx.as_raw_fd(), FcntlArg::F_SETFL(new_flags)).map_err(|e| e.to_string())?; 174 | 175 | Ok(fd_socket_rx) 176 | } 177 | -------------------------------------------------------------------------------- /src/snowstorm.rs: -------------------------------------------------------------------------------- 1 | use rand::{thread_rng, Rng}; 2 | use ratatui::{ 3 | buffer::Buffer, 4 | layout::Rect, 5 | style::Color, 6 | widgets::{Clear, Widget}, 7 | }; 8 | 9 | const SNOW_1: char = '❄'; 10 | const SNOW_2: char = '❆'; 11 | const SNOW_3: char = '❋'; 12 | const SNOW_4: char = '❅'; 13 | const SNOW_5: char = '❊'; 14 | const SNOW_6: char = '❉'; 15 | 16 | const SNOWFLAKES: [char; 6] = [SNOW_1, SNOW_4, SNOW_2, SNOW_6, SNOW_5, SNOW_3]; 17 | 18 | const GRAYSCALE: [Color; 4] = [ 19 | Color::Indexed(255), 20 | Color::Indexed(250), 21 | Color::Indexed(245), 22 | Color::Indexed(240), 23 | ]; 24 | 25 | #[derive(Clone)] 26 | pub struct Snowstorm { 27 | snowflakes: Vec, 28 | density: usize, 29 | rainbow: bool, 30 | } 31 | 32 | impl Snowstorm { 33 | pub fn new(area: Rect) -> Snowstorm { 34 | Snowstorm::frame( 35 | Snowstorm { 36 | snowflakes: Vec::new(), 37 | density: 20, 38 | rainbow: false, 39 | }, 40 | area, 41 | ) 42 | } 43 | 44 | pub fn new_rainbow(area: Rect) -> Snowstorm { 45 | Snowstorm::frame( 46 | Snowstorm { 47 | snowflakes: Vec::new(), 48 | density: 20, 49 | rainbow: true, 50 | }, 51 | area, 52 | ) 53 | } 54 | 55 | pub fn frame(mut snowstorm: Snowstorm, area: Rect) -> Snowstorm { 56 | let mut rng = thread_rng(); 57 | 58 | // Move all the snowflakes, remove if outside the area 59 | snowstorm.snowflakes.retain_mut(|snowflake| { 60 | // Slow down vertical movement by using a smaller increment 61 | snowflake.y += snowflake.vel; 62 | 63 | if rng.gen_bool(0.001) { 64 | let drift = rng.gen_range(-1..=1); 65 | snowflake.x = ((snowflake.x as i32) + (drift) as i32) as u16; 66 | } 67 | 68 | snowflake.within_bounds(area) 69 | }); 70 | 71 | // Generate new snowflakes... maybe 72 | for _ in 0..snowstorm.density { 73 | if rng.gen_bool(snowstorm.density as f64 / 5000.0) { 74 | snowstorm 75 | .snowflakes 76 | .push(Snowflake::new(area, snowstorm.rainbow)); 77 | } 78 | } 79 | 80 | snowstorm 81 | } 82 | } 83 | 84 | impl Widget for Snowstorm { 85 | fn render(self, area: Rect, buf: &mut Buffer) { 86 | // Clear the area 87 | Clear.render(area, buf); 88 | // Set the snowflakes 89 | for snowflake in self.snowflakes { 90 | buf.get_mut(snowflake.x, snowflake.y as u16) 91 | .set_char(snowflake.shape) 92 | .set_fg(snowflake.color); 93 | } 94 | } 95 | } 96 | #[derive(Clone)] 97 | struct Snowflake { 98 | x: u16, 99 | y: f32, 100 | color: Color, 101 | shape: char, 102 | vel: f32, 103 | } 104 | 105 | impl Snowflake { 106 | pub fn new(area: Rect, rainbow: bool) -> Snowflake { 107 | // Set it to a grayscale color at random to create depth 108 | let mut color = GRAYSCALE[rand::random::() % 4]; 109 | 110 | // If we doing the rainbow thing 111 | if rainbow { 112 | let rd_r = rand::random::(); 113 | let rd_g = rand::random::(); 114 | let rd_b = rand::random::(); 115 | color = Color::Rgb(rd_r, rd_g, rd_b) 116 | } 117 | 118 | let left_edge = area.x; 119 | let right_edge = area.x + area.width; 120 | 121 | let mut rng = thread_rng(); 122 | let new_x = rng.gen_range(left_edge..right_edge); 123 | 124 | let velocity = rng.gen_range(0.04..0.08); 125 | 126 | Snowflake { 127 | x: new_x, 128 | y: area.y as f32, 129 | color, 130 | shape: SNOWFLAKES[rand::random::() % 6], 131 | vel: velocity, 132 | } 133 | } 134 | 135 | pub fn within_bounds(&self, area: Rect) -> bool { 136 | let left_edge = area.x; 137 | let right_edge = area.x + area.width; 138 | let top_edge = area.y; 139 | let bottom_edge = area.y + area.height; 140 | 141 | self.x >= left_edge 142 | && self.x <= right_edge 143 | && (self.y as u16) >= top_edge 144 | && (self.y as u16) < bottom_edge 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/status.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use std::fmt; 3 | 4 | // Define an enum for message types 5 | 6 | #[derive(Clone)] 7 | pub enum MessageType { 8 | Error, 9 | Warning, 10 | Info, 11 | Priority, 12 | Status, 13 | } 14 | 15 | impl fmt::Display for MessageType { 16 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 17 | let message_type_str = match self { 18 | MessageType::Error => "Error", 19 | MessageType::Warning => "Warning", 20 | MessageType::Info => "Info", 21 | MessageType::Priority => "Priority", 22 | MessageType::Status => "Status", 23 | }; 24 | write!(f, "{}", message_type_str) 25 | } 26 | } 27 | 28 | impl MessageType { 29 | pub fn to_str(&self) -> String { 30 | match self { 31 | MessageType::Error => "Error".to_owned(), 32 | MessageType::Warning => "Warning".to_owned(), 33 | MessageType::Info => "Info".to_owned(), 34 | MessageType::Priority => "Priority".to_owned(), 35 | MessageType::Status => "Status".to_owned(), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Clone)] 41 | pub struct StatusMessage { 42 | pub timestamp: DateTime, 43 | pub message_type: MessageType, 44 | pub content: String, 45 | } 46 | 47 | impl StatusMessage { 48 | pub fn new(message_type: MessageType, content: String) -> Self { 49 | StatusMessage { 50 | timestamp: Utc::now(), 51 | message_type, 52 | content, 53 | } 54 | } 55 | } 56 | 57 | pub struct MessageLog { 58 | messages: Vec, 59 | headless: bool, 60 | max_size: usize, // New field to store the maximum number of messages 61 | } 62 | 63 | impl MessageLog { 64 | pub fn new(headless: bool, max_size: Option) -> Self { 65 | MessageLog { 66 | messages: Vec::new(), 67 | headless, 68 | max_size: max_size.unwrap_or(500), 69 | } 70 | } 71 | 72 | pub fn add_message(&mut self, message: StatusMessage) { 73 | if self.messages.len() == self.max_size { 74 | self.messages.remove(0); 75 | } 76 | 77 | self.messages.push(message.clone()); 78 | 79 | if self.headless { 80 | let color = match message.message_type { 81 | MessageType::Error => "\x1b[31m", 82 | MessageType::Warning => "\x1b[33m", 83 | MessageType::Info => "\x1b[0m", 84 | MessageType::Priority => "\x1b[32m", 85 | MessageType::Status => "\x1b[36m", 86 | }; 87 | let white = "\x1b[0m"; 88 | println!( 89 | "{}{} | {:^8} | {}{}", 90 | color, 91 | message.timestamp.format("%Y-%m-%d %H:%M:%S UTC"), 92 | message.message_type.to_str(), 93 | message.content, 94 | white, 95 | ) 96 | } 97 | } 98 | 99 | // Other methods remain unchanged 100 | pub fn get_all_messages(&self) -> Vec { 101 | self.messages.clone() 102 | } 103 | 104 | pub fn size(&self) -> usize { 105 | self.messages.len() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/tabbedblock/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod tab; 2 | pub mod tabbedblock; 3 | -------------------------------------------------------------------------------- /src/tabbedblock/tab.rs: -------------------------------------------------------------------------------- 1 | //! This module holds the [`tab`] element and its related configuration types. 2 | //! A tab is a piece of [`Block`](crate::widgets::Block) configuration. 3 | 4 | use strum::{Display, EnumString}; 5 | 6 | use ratatui::{layout::Alignment, text::Line}; 7 | 8 | /// A [`Block`](crate::widgets::Block) tab. 9 | /// 10 | /// It can be aligned (see [`Alignment`]) and positioned (see [`Position`]). 11 | /// 12 | /// # Example 13 | /// 14 | /// tab with no style. 15 | /// ``` 16 | /// use ratatui::widgets::block::tab; 17 | /// 18 | /// tab::from("tab"); 19 | /// ``` 20 | /// 21 | /// Blue tab on a white background (via [`Stylize`](crate::style::Stylize) trait). 22 | /// ``` 23 | /// use ratatui::{prelude::*, widgets::block::*}; 24 | /// 25 | /// tab::from("tab".blue().on_white()); 26 | /// ``` 27 | /// 28 | /// tab with multiple styles (see [`Line`] and [`Stylize`](crate::style::Stylize)). 29 | /// ``` 30 | /// use ratatui::{prelude::*, widgets::block::*}; 31 | /// 32 | /// tab::from(Line::from(vec!["Q".white().underlined(), "uit".gray()])); 33 | /// ``` 34 | /// 35 | /// Complete example 36 | /// ``` 37 | /// use ratatui::{ 38 | /// prelude::*, 39 | /// widgets::{block::*, *}, 40 | /// }; 41 | /// 42 | /// tab::from("tab") 43 | /// .position(Position::Top) 44 | /// .alignment(Alignment::Right); 45 | /// ``` 46 | #[derive(Debug, Default, Clone, Eq, PartialEq, Hash)] 47 | pub struct Tab<'a> { 48 | /// tab content 49 | pub content: Line<'a>, 50 | 51 | /// tab index 52 | pub index: usize, 53 | 54 | /// tab alignment 55 | /// 56 | /// If [`None`], defaults to the alignment defined with 57 | /// [`Block::tab_alignment`](crate::widgets::Block::tab_alignment) in the associated 58 | /// [`Block`](crate::widgets::Block). 59 | pub alignment: Option, 60 | 61 | /// tab position 62 | /// 63 | /// If [`None`], defaults to the position defined with 64 | /// [`Block::tab_position`](crate::widgets::Block::tab_position) in the associated 65 | /// [`Block`](crate::widgets::Block). 66 | pub position: Option, 67 | } 68 | 69 | /// Defines the [tab](crate::widgets::block::tab) position. 70 | /// 71 | /// The tab can be positioned on top or at the bottom of the block. 72 | /// Defaults to [`Position::Top`]. 73 | /// 74 | /// # Example 75 | /// 76 | /// ``` 77 | /// use ratatui::widgets::{block::*, *}; 78 | /// 79 | /// Block::new().tab(tab::from("tab").position(Position::Bottom)); 80 | /// ``` 81 | #[derive(Debug, Default, Display, EnumString, Clone, Copy, PartialEq, Eq, Hash)] 82 | pub enum Position { 83 | /// Position the tab at the top of the block. 84 | /// 85 | /// This is the default. 86 | #[default] 87 | Top, 88 | /// Position the tab at the bottom of the block. 89 | Bottom, 90 | } 91 | 92 | impl<'a> Tab<'a> { 93 | /// Set the tab content. 94 | pub fn content(mut self, content: T) -> Tab<'a> 95 | where 96 | T: Into>, 97 | { 98 | self.content = content.into(); 99 | self 100 | } 101 | 102 | /// Set the tab alignment. 103 | #[must_use = "method moves the value of self and returns the modified value"] 104 | pub fn alignment(mut self, alignment: Alignment) -> Tab<'a> { 105 | self.alignment = Some(alignment); 106 | self 107 | } 108 | 109 | /// Set the tab position. 110 | #[must_use = "method moves the value of self and returns the modified value"] 111 | pub fn position(mut self, position: Position) -> Tab<'a> { 112 | self.position = Some(position); 113 | self 114 | } 115 | } 116 | 117 | impl<'a, T> From for Tab<'a> 118 | where 119 | T: Into>, 120 | { 121 | fn from(value: T) -> Self { 122 | Self::default().content(value.into()) 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/targets.rs: -------------------------------------------------------------------------------- 1 | use globset::Glob; 2 | use libwifi::frame::components::MacAddress; 3 | use rand::seq::SliceRandom; 4 | 5 | use crate::devices::AccessPoint; 6 | 7 | trait IsTarget { 8 | fn target_match(&self, ap: &AccessPoint) -> bool; 9 | } 10 | 11 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 12 | pub struct TargetSSID { 13 | pub ssid: String, 14 | } 15 | 16 | impl IsTarget for TargetSSID { 17 | fn target_match(&self, ap: &AccessPoint) -> bool { 18 | if let Some(ssid) = ap.ssid.clone() { 19 | if Glob::new(&self.ssid) 20 | .unwrap() 21 | .compile_matcher() 22 | .is_match(ssid) 23 | { 24 | return true; 25 | } 26 | } 27 | false 28 | } 29 | } 30 | 31 | impl TargetSSID { 32 | pub fn new(ssid: &str) -> Self { 33 | TargetSSID { 34 | ssid: ssid.to_owned(), 35 | } 36 | } 37 | 38 | fn match_ssid(&self, ssid: String) -> bool { 39 | if ssid == self.ssid { 40 | return true; 41 | } 42 | false 43 | } 44 | } 45 | 46 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 47 | pub struct TargetMAC { 48 | pub addr: MacAddress, 49 | } 50 | 51 | impl IsTarget for TargetMAC { 52 | fn target_match(&self, ap: &AccessPoint) -> bool { 53 | if ap.mac_address == self.addr { 54 | return true; 55 | } 56 | false 57 | } 58 | } 59 | 60 | impl TargetMAC { 61 | pub fn new(addr: MacAddress) -> Self { 62 | TargetMAC { addr } 63 | } 64 | } 65 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 66 | pub enum Target { 67 | MAC(TargetMAC), 68 | SSID(TargetSSID), 69 | } 70 | 71 | impl Target { 72 | pub fn get_string(&self) -> String { 73 | match self { 74 | Target::MAC(tgt) => tgt.addr.to_string(), 75 | Target::SSID(tgt) => tgt.ssid.clone(), 76 | } 77 | } 78 | } 79 | 80 | pub struct TargetList { 81 | targets: Vec, 82 | } 83 | 84 | impl TargetList { 85 | pub fn new() -> Self { 86 | TargetList { 87 | targets: Vec::new(), 88 | } 89 | } 90 | 91 | pub fn from_vec(targets: Vec) -> Self { 92 | TargetList { targets } 93 | } 94 | 95 | pub fn add(&mut self, target: Target) { 96 | self.targets.push(target); 97 | } 98 | 99 | pub fn empty(&self) -> bool { 100 | self.targets.is_empty() 101 | } 102 | 103 | /// Will check if the AP is a target 104 | pub fn is_target(&mut self, ap: &mut AccessPoint) -> bool { 105 | if self.empty() { 106 | return true; 107 | }; 108 | 109 | for target in &self.targets { 110 | match target { 111 | Target::MAC(tgt) => { 112 | if tgt.target_match(ap) { 113 | if let Some(ssid) = &ap.ssid { 114 | if !self.is_target_ssid(ssid) { 115 | self.add(Target::SSID(TargetSSID { 116 | ssid: ssid.to_string(), 117 | })); 118 | } 119 | } 120 | if !ap.is_whitelisted() { 121 | ap.is_target = true; 122 | } 123 | return true; 124 | } 125 | } 126 | Target::SSID(tgt) => { 127 | if tgt.target_match(ap) { 128 | if !self.is_target_mac(&ap.mac_address) { 129 | self.add(Target::MAC(TargetMAC { 130 | addr: ap.mac_address, 131 | })) 132 | } 133 | if !ap.is_whitelisted() { 134 | ap.is_target = true; 135 | } 136 | return true; 137 | } 138 | } 139 | } 140 | } 141 | false 142 | } 143 | 144 | pub fn get_targets(&mut self, ap: &mut AccessPoint) -> Vec { 145 | if self.empty() { 146 | return vec![]; 147 | }; 148 | let mut matches: Vec = Vec::new(); 149 | 150 | for target in self.targets.clone() { 151 | match target { 152 | Target::MAC(ref tgt) => { 153 | if tgt.target_match(ap) { 154 | if let Some(ssid) = &ap.ssid { 155 | if !self.is_target_ssid(ssid) { 156 | self.add(Target::SSID(TargetSSID { 157 | ssid: ssid.to_string(), 158 | })); 159 | } 160 | } 161 | if !ap.is_whitelisted() { 162 | ap.is_target = true; 163 | } 164 | matches.push(target); 165 | } 166 | } 167 | Target::SSID(ref tgt) => { 168 | if tgt.target_match(ap) { 169 | if !self.is_target_mac(&ap.mac_address) { 170 | self.add(Target::MAC(TargetMAC { 171 | addr: ap.mac_address, 172 | })) 173 | } 174 | if !ap.is_whitelisted() { 175 | ap.is_target = true; 176 | } 177 | matches.push(target); 178 | } 179 | } 180 | } 181 | } 182 | matches 183 | } 184 | 185 | pub fn is_actual_target_mac(&self, mac: &MacAddress) -> bool { 186 | 187 | for target in &self.targets { 188 | match target { 189 | Target::MAC(tgt) => { 190 | if tgt.addr == *mac { 191 | return true; 192 | } 193 | } 194 | Target::SSID(_) => {} // do nothing 195 | } 196 | } 197 | false 198 | } 199 | 200 | pub fn is_actual_target_ssid(&self, ssid: &str) -> bool { 201 | 202 | for target in &self.targets { 203 | match target { 204 | Target::MAC(_) => {} // do nothing, we don't have anything to compare to here. 205 | Target::SSID(tgt) => { 206 | if tgt.match_ssid(ssid.to_owned()) { 207 | return true; 208 | } 209 | } 210 | } 211 | } 212 | false 213 | } 214 | 215 | pub fn is_target_mac(&self, mac: &MacAddress) -> bool { 216 | if self.empty() { 217 | return true; 218 | }; 219 | 220 | for target in &self.targets { 221 | match target { 222 | Target::MAC(tgt) => { 223 | if tgt.addr == *mac { 224 | return true; 225 | } 226 | } 227 | Target::SSID(_) => {} // do nothing 228 | } 229 | } 230 | false 231 | } 232 | 233 | pub fn is_target_ssid(&self, ssid: &str) -> bool { 234 | if self.empty() { 235 | return true; 236 | }; 237 | 238 | for target in &self.targets { 239 | match target { 240 | Target::MAC(_) => {} // do nothing, we don't have anything to compare to here. 241 | Target::SSID(tgt) => { 242 | if tgt.match_ssid(ssid.to_owned()) { 243 | return true; 244 | } 245 | } 246 | } 247 | } 248 | false 249 | } 250 | 251 | pub fn has_ssid(&self) -> bool { 252 | for target in &self.targets { 253 | match target { 254 | Target::MAC(_) => continue, 255 | Target::SSID(_) => return true, 256 | } 257 | } 258 | false 259 | } 260 | 261 | pub fn get_random_ssid(&self) -> Option { 262 | if self.empty() { 263 | return None; 264 | } 265 | if !self.has_ssid() { 266 | return None; 267 | } 268 | loop { 269 | let tgt = self.targets.choose(&mut rand::thread_rng()).unwrap(); 270 | if let Target::SSID(tgt) = tgt { 271 | return Some(tgt.ssid.clone()); 272 | } 273 | } 274 | } 275 | 276 | pub fn get_string(&self) -> String { 277 | self.targets 278 | .iter() 279 | .map(|target| match target { 280 | Target::MAC(mac_target) => format!("MAC: {}", mac_target.addr), 281 | Target::SSID(ssid_target) => format!("SSID: {}", ssid_target.ssid), 282 | }) 283 | .collect::>() 284 | .join(", ") 285 | } 286 | 287 | pub fn get_ref(&self) -> &Vec { 288 | &self.targets 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use libwifi::frame::components::WpsInformation; 3 | use libwifi::frame::{EapolKey, KeyInformation}; 4 | use radiotap::field::ext::TimeUnit; 5 | use std::fmt::Write; 6 | use std::fs::File; 7 | use std::io; 8 | use std::net::IpAddr; 9 | use std::str::FromStr; 10 | use std::time::{Duration, SystemTime, UNIX_EPOCH}; 11 | 12 | pub fn epoch_to_string(epoch: u64) -> String { 13 | match UNIX_EPOCH.checked_add(Duration::from_secs(epoch)) { 14 | Some(epoch_time) => match SystemTime::now().duration_since(epoch_time) { 15 | Ok(duration_since) => { 16 | let elapsed_seconds = duration_since.as_secs(); 17 | if elapsed_seconds > 3600 { 18 | format!("{}h", elapsed_seconds / 3600) 19 | } else if duration_since.as_secs() > 60 { 20 | format!("{}m", elapsed_seconds / 60) 21 | } else { 22 | format!("{}s", elapsed_seconds) 23 | } 24 | } 25 | Err(_) => "Time is in the future".to_string(), 26 | }, 27 | None => "Invalid timestamp".to_string(), 28 | } 29 | } 30 | 31 | pub fn slice_to_hex_string(bytes: &[u8]) -> String { 32 | bytes.iter().fold(String::new(), |mut output, b| { 33 | let _ = write!(output, "{b:02x}"); 34 | output 35 | }) 36 | } 37 | 38 | pub fn epoch_to_iso_string(epoch: u64) -> String { 39 | match UNIX_EPOCH.checked_add(Duration::from_secs(epoch)) { 40 | Some(epoch_time) => DateTime::::from(epoch_time).format("%+").to_string(), 41 | None => "Invalid timestamp".to_string(), 42 | } 43 | } 44 | 45 | pub fn system_time_to_iso8601(system_time: SystemTime) -> String { 46 | let datetime: DateTime = system_time.into(); 47 | datetime.to_rfc3339() 48 | } 49 | 50 | pub fn key_info_to_json_str(keyinfo: KeyInformation) -> String { 51 | format!( 52 | "{{\"descriptor_version\": {},\"key_type\": {},\"key_index\": {},\"install\": {},\"key_ack\": {},\"key_mic\": {},\"secure\": {},\"error\": {},\"request\": {},\"encrypted_key_data\": {},\"smk_message\": {}}}", 53 | keyinfo.descriptor_version, 54 | keyinfo.key_type, 55 | keyinfo.key_index, 56 | keyinfo.install, 57 | keyinfo.key_ack, 58 | keyinfo.key_mic, 59 | keyinfo.secure, 60 | keyinfo.error, 61 | keyinfo.request, 62 | keyinfo.encrypted_key_data, 63 | keyinfo.smk_message 64 | ) 65 | } 66 | 67 | pub fn eapol_to_json_str(key: &EapolKey) -> String { 68 | format!("{{\"protocol_version\": {},\"timestamp\": \"{}\",\"key_information\": {},\"key_length\": {},\"replay_counter\": {},\"key_nonce\": \"{}\",\"key_iv\": \"{}\",\"key_rsc\": {},\"key_id\": {},\"key_mic\": \"{}\",\"key_data\": \"{}\"}}", 69 | key.protocol_version, 70 | system_time_to_iso8601(key.timestamp), 71 | key_info_to_json_str(key.parse_key_information()), 72 | key.key_length, 73 | key.replay_counter, 74 | slice_to_hex_string(&key.key_nonce), 75 | slice_to_hex_string(&key.key_iv), 76 | key.key_rsc, 77 | &key.key_id, 78 | slice_to_hex_string(&key.key_mic), 79 | slice_to_hex_string(&key.key_data)) 80 | } 81 | 82 | pub fn option_bool_to_json_string(option: Option) -> String { 83 | match option { 84 | Some(true) => "true".to_string(), 85 | Some(false) => "false".to_string(), 86 | None => "\"none\"".to_string(), 87 | } 88 | } 89 | 90 | pub fn merge_with_newline(vec1: Vec, vec2: Vec) -> Vec { 91 | let min_length = std::cmp::min(vec1.len(), vec2.len()); 92 | 93 | // Iterate up to the shortest length, merging corresponding elements with a newline 94 | let mut merged = Vec::with_capacity(min_length); 95 | for i in 0..min_length { 96 | let new_str = format!("{}\n{}", vec1[i], vec2[i]); 97 | merged.push(new_str); 98 | } 99 | 100 | merged 101 | } 102 | 103 | pub fn ts_to_system_time(timestamp: u64, unit: TimeUnit) -> SystemTime { 104 | match unit { 105 | TimeUnit::Milliseconds => UNIX_EPOCH + Duration::from_millis(timestamp), 106 | TimeUnit::Microseconds => UNIX_EPOCH + Duration::from_micros(timestamp), 107 | TimeUnit::Nanoseconds => UNIX_EPOCH + Duration::from_nanos(timestamp), 108 | } 109 | } 110 | 111 | pub fn parse_ip_address_port(input: &str) -> Result<(IpAddr, u16), &'static str> { 112 | let parts: Vec<&str> = input.split(':').collect(); 113 | 114 | // Check if there are exactly two parts 115 | if parts.len() != 2 { 116 | return Err("Input should be in the format IP_ADDRESS:PORT"); 117 | } 118 | 119 | // Parse IP address 120 | let ip = match IpAddr::from_str(parts[0]) { 121 | Ok(ip) => ip, 122 | Err(_) => return Err("Invalid IP address"), 123 | }; 124 | 125 | // Parse port 126 | let port = match parts[1].parse::() { 127 | Ok(port) => port, 128 | Err(_) => return Err("Invalid port number"), 129 | }; 130 | 131 | Ok((ip, port)) 132 | } 133 | 134 | pub fn is_file_less_than_100mb(file: &File) -> io::Result { 135 | let metadata = file.metadata()?; 136 | Ok(metadata.len() < 100 * 1024 * 1024) 137 | } 138 | 139 | pub fn max_column_widths(headers: &[String], rows: &[(Vec, u16)]) -> Vec { 140 | let mut max_widths = headers.iter().map(|h| h.len()).collect::>(); 141 | 142 | for (row_data, _) in rows { 143 | for (i, cell) in row_data.iter().enumerate() { 144 | let adjusted_length = cell 145 | .chars() 146 | .fold(0, |acc, ch| acc + if ch == '✅' { 2 } else { 1 }); 147 | max_widths[i] = max_widths[i].max(adjusted_length); 148 | } 149 | } 150 | 151 | max_widths 152 | } 153 | 154 | pub fn format_row(row: &[String], widths: &[usize]) -> String { 155 | row.iter() 156 | .enumerate() 157 | .map(|(i, cell)| { 158 | // Count the number of special characters 159 | let special_chars_count = cell.chars().filter(|&ch| ch == '✅').count(); 160 | // Adjust width by reducing 1 space for each special character 161 | let adjusted_width = if special_chars_count > 0 { 162 | widths[i].saturating_sub(special_chars_count) 163 | } else { 164 | widths[i] 165 | }; 166 | format!("{:width$}", cell, width = adjusted_width) 167 | }) 168 | .collect::>() 169 | .join(" | ") 170 | } 171 | 172 | pub fn wps_to_json(wps_info: &Option) -> String { 173 | if let Some(wps) = wps_info { 174 | format!("{{\"setup_state\": \"{:?}\", \"manufacturer\": \"{}\", \"model\": \"{}\", \"model_number\": \"{}\", \"serial_number\": \"{}\", \"primary_device_type\": \"{}\", \"device_name\": \"{}\"}}", 175 | wps.setup_state, 176 | wps.manufacturer, 177 | wps.model, 178 | wps.model_number, 179 | wps.serial_number, 180 | wps.primary_device_type, 181 | wps.device_name) 182 | } else { 183 | "{}".to_string() 184 | } 185 | } 186 | 187 | pub fn sanitize_essid(filename: &str) -> String { 188 | filename 189 | .replace("<", "_") 190 | .replace(">", "_") 191 | .replace(":", "_") 192 | .replace("\"", "_") 193 | .replace("/", "_") 194 | .replace("\\", "_") 195 | .replace("|", "_") 196 | .replace("?", "_") 197 | .replace("+", "_") 198 | .replace("*", "_") 199 | .replace(" ", "_") 200 | .replace("\0", "_") 201 | .chars() 202 | .collect() 203 | } 204 | 205 | pub fn strip_comment(line: &str) -> &str { 206 | line.split_once('#') 207 | .map(|(line_without_comment, _)| line_without_comment) 208 | .unwrap_or(line) 209 | .trim_end() 210 | } 211 | 212 | #[cfg(test)] 213 | mod tests { 214 | use super::*; 215 | 216 | #[test] 217 | fn test_sanitize_filename() { 218 | let cases = vec![ 219 | ("", "_invalid_file_name__"), 220 | ("normal_filename.txt", "normal_filename.txt"), 221 | ("hidden?.txt", "hidden_.txt"), 222 | ("blank space", "blank_space"), 223 | ("con", "con"), 224 | ("zEQQ3XJPK+30/+aP", "zEQQ3XJPK_30__aP"), // Added your specific case here 225 | ]; 226 | 227 | for (input, expected) in cases { 228 | assert_eq!( 229 | sanitize_essid(input), 230 | expected.to_string(), 231 | "Failed on input: {}", 232 | input 233 | ); 234 | } 235 | } 236 | 237 | #[test] 238 | fn test_strip_comment() { 239 | assert_eq!(strip_comment("hello world"), "hello world"); 240 | assert_eq!(strip_comment("hello # world"), "hello"); 241 | assert_eq!(strip_comment("hello # world # test"), "hello"); 242 | assert_eq!(strip_comment("hello "), "hello"); 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/whitelist.rs: -------------------------------------------------------------------------------- 1 | use globset::Glob; 2 | use libwifi::frame::components::MacAddress; 3 | use rand::seq::SliceRandom; 4 | 5 | use crate::devices::AccessPoint; 6 | 7 | trait IsWhitelisted { 8 | fn whitelist_match(&self, ap: &AccessPoint) -> bool; 9 | } 10 | 11 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 12 | pub struct WhiteSSID { 13 | pub ssid: String, 14 | } 15 | 16 | impl IsWhitelisted for WhiteSSID { 17 | fn whitelist_match(&self, ap: &AccessPoint) -> bool { 18 | if let Some(ssid) = ap.ssid.clone() { 19 | if Glob::new(&self.ssid) 20 | .unwrap() 21 | .compile_matcher() 22 | .is_match(ssid) 23 | { 24 | return true; 25 | } 26 | } 27 | false 28 | } 29 | } 30 | 31 | impl WhiteSSID { 32 | pub fn new(ssid: &str) -> Self { 33 | WhiteSSID { 34 | ssid: ssid.to_owned(), 35 | } 36 | } 37 | 38 | fn match_ssid(&self, ssid: String) -> bool { 39 | if ssid == self.ssid { 40 | return true; 41 | } 42 | false 43 | } 44 | } 45 | 46 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 47 | pub struct WhiteMAC { 48 | pub addr: MacAddress, 49 | } 50 | 51 | impl IsWhitelisted for WhiteMAC { 52 | fn whitelist_match(&self, ap: &AccessPoint) -> bool { 53 | if ap.mac_address == self.addr { 54 | return true; 55 | } 56 | false 57 | } 58 | } 59 | 60 | impl WhiteMAC { 61 | pub fn new(addr: MacAddress) -> Self { 62 | WhiteMAC { addr } 63 | } 64 | } 65 | #[derive(Eq, PartialEq, Hash, Clone, Debug)] 66 | pub enum White { 67 | MAC(WhiteMAC), 68 | SSID(WhiteSSID), 69 | } 70 | 71 | impl White { 72 | pub fn get_string(&self) -> String { 73 | match self { 74 | White::MAC(tgt) => tgt.addr.to_string(), 75 | White::SSID(tgt) => tgt.ssid.clone(), 76 | } 77 | } 78 | } 79 | 80 | pub struct WhiteList { 81 | devices: Vec, 82 | } 83 | 84 | impl WhiteList { 85 | pub fn new() -> Self { 86 | WhiteList { 87 | devices: Vec::new(), 88 | } 89 | } 90 | 91 | pub fn from_vec(devices: Vec) -> Self { 92 | WhiteList { devices } 93 | } 94 | 95 | pub fn add(&mut self, device: White) { 96 | self.devices.push(device); 97 | } 98 | 99 | pub fn empty(&self) -> bool { 100 | self.devices.is_empty() 101 | } 102 | 103 | /// Will check if the AP is a target, but will also mark the 104 | pub fn is_whitelisted(&mut self, ap: &mut AccessPoint) -> bool { 105 | if self.empty() { 106 | return false; 107 | }; 108 | 109 | for device in &self.devices { 110 | match device { 111 | White::MAC(tgt) => { 112 | if tgt.whitelist_match(ap) { 113 | if let Some(ssid) = &ap.ssid { 114 | if !self.is_whitelisted_ssid(ssid) { 115 | self.add(White::SSID(WhiteSSID { 116 | ssid: ssid.to_string(), 117 | })); 118 | } 119 | } 120 | if ! ap.is_target() { 121 | ap.is_whitelisted = true; 122 | } 123 | return true; 124 | } 125 | } 126 | White::SSID(tgt) => { 127 | if tgt.whitelist_match(ap) { 128 | if !self.is_whitelisted_mac(&ap.mac_address) { 129 | self.add(White::MAC(WhiteMAC { 130 | addr: ap.mac_address, 131 | })) 132 | } 133 | if ! ap.is_target() { 134 | ap.is_whitelisted = true; 135 | } 136 | return true; 137 | } 138 | } 139 | } 140 | } 141 | false 142 | } 143 | 144 | pub fn get_whitelisted(&mut self, ap: &mut AccessPoint) -> Vec { 145 | if self.empty() { 146 | return vec![]; 147 | }; 148 | let mut matches: Vec = Vec::new(); 149 | 150 | for target in self.devices.clone() { 151 | match target { 152 | White::MAC(ref tgt) => { 153 | if tgt.whitelist_match(ap) { 154 | if let Some(ssid) = &ap.ssid { 155 | if !self.is_whitelisted_ssid(ssid) { 156 | self.add(White::SSID(WhiteSSID { 157 | ssid: ssid.to_string(), 158 | })); 159 | } 160 | } 161 | if ! ap.is_target() { 162 | ap.is_whitelisted = true; 163 | } 164 | matches.push(target); 165 | } 166 | } 167 | White::SSID(ref tgt) => { 168 | if tgt.whitelist_match(ap) { 169 | if !self.is_whitelisted_mac(&ap.mac_address) { 170 | self.add(White::MAC(WhiteMAC { 171 | addr: ap.mac_address, 172 | })) 173 | } 174 | if ! ap.is_target() { 175 | ap.is_whitelisted = true; 176 | } 177 | matches.push(target); 178 | } 179 | } 180 | } 181 | } 182 | matches 183 | } 184 | 185 | pub fn is_whitelisted_mac(&self, mac: &MacAddress) -> bool { 186 | if self.empty() { 187 | return true; 188 | }; 189 | 190 | for target in &self.devices { 191 | match target { 192 | White::MAC(tgt) => { 193 | if tgt.addr == *mac { 194 | return true; 195 | } 196 | } 197 | White::SSID(_) => {} // do nothing 198 | } 199 | } 200 | false 201 | } 202 | 203 | pub fn is_whitelisted_ssid(&self, ssid: &str) -> bool { 204 | if self.empty() { 205 | return true; 206 | }; 207 | 208 | for target in &self.devices { 209 | match target { 210 | White::MAC(_) => {} // do nothing, we don't have anything to compare to here. 211 | White::SSID(tgt) => { 212 | if tgt.match_ssid(ssid.to_owned()) { 213 | return true; 214 | } 215 | } 216 | } 217 | } 218 | false 219 | } 220 | 221 | pub fn has_ssid(&self) -> bool { 222 | for device in &self.devices { 223 | match device { 224 | White::MAC(_) => continue, 225 | White::SSID(_) => return true, 226 | } 227 | } 228 | false 229 | } 230 | 231 | pub fn get_random_ssid(&self) -> Option { 232 | if self.empty() { 233 | return None; 234 | } 235 | if !self.has_ssid() { 236 | return None; 237 | } 238 | loop { 239 | let tgt = self.devices.choose(&mut rand::thread_rng()).unwrap(); 240 | if let White::SSID(tgt) = tgt { 241 | return Some(tgt.ssid.clone()); 242 | } 243 | } 244 | } 245 | 246 | pub fn get_string(&self) -> String { 247 | self.devices 248 | .iter() 249 | .map(|target| match target { 250 | White::MAC(mac_target) => format!("MAC: {}", mac_target.addr), 251 | White::SSID(ssid_target) => format!("SSID: {}", ssid_target.ssid), 252 | }) 253 | .collect::>() 254 | .join(", ") 255 | } 256 | 257 | pub fn get_ref(&self) -> &Vec { 258 | &self.devices 259 | } 260 | } 261 | --------------------------------------------------------------------------------